diff --git a/smallboot-api/.gitignore b/smallboot-api/.gitignore new file mode 100644 index 0000000..0befc27 --- /dev/null +++ b/smallboot-api/.gitignore @@ -0,0 +1,17 @@ +target/ +!**/src/main/**/target/ +!**/src/test/**/target/ + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr +**/src/main/resources/rebel.xml +**/src/main/resources/rebel-remote.xml +**/**/src/main/resources/rebel.xml +**/**/src/main/resources/rebel-remote.xml + +# 业务相关 +logs +docker/*.jar diff --git a/smallboot-api/README.md b/smallboot-api/README.md new file mode 100644 index 0000000..500901d --- /dev/null +++ b/smallboot-api/README.md @@ -0,0 +1,22 @@ +# 说明 + +### 组件&服务 部署 + +mysql、redis、minio + +见[部署文档](doc/部署) + +### 本地运行 + +1. 修改`app/src/main/resources/application-dev.yml`配置 +2. 启动`App` + +--- + +### 数据清理 + +```sql +SELECT CONCAT('DELETE FROM ', TABLE_NAME, ' where is_deleted=1;') +FROM information_schema.TABLES +WHERE table_schema = 'smallboot'; +``` \ No newline at end of file diff --git a/smallboot-api/app/pom.xml b/smallboot-api/app/pom.xml new file mode 100644 index 0000000..198493a --- /dev/null +++ b/smallboot-api/app/pom.xml @@ -0,0 +1,56 @@ + + + + 4.0.0 + + + smallboot-api + com.zhengqing + ${revision} + + + app + + ${project.artifactId} + ${revision} + jar + + 业务开发模块 + + + + + + + com.zhengqing + core + + + servlet-api + javax.servlet + + + + + + com.zhengqing + system + + + + + + + ${project.name} + + + org.springframework.boot + spring-boot-maven-plugin + + + + + + diff --git a/smallboot-api/app/src/main/java/com/zhengqing/app/App.java b/smallboot-api/app/src/main/java/com/zhengqing/app/App.java new file mode 100644 index 0000000..deb6d13 --- /dev/null +++ b/smallboot-api/app/src/main/java/com/zhengqing/app/App.java @@ -0,0 +1,17 @@ +package com.zhengqing.app; + +import com.zhengqing.common.base.constant.ServiceConstant; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.transaction.annotation.EnableTransactionManagement; + + +@SpringBootApplication +@EnableTransactionManagement +@ComponentScan(basePackages = {ServiceConstant.SERVICE_BASE_PACKAGE}) +public class App { + public static void main(String[] args) { + SpringApplication.run(App.class, args); + } +} diff --git a/smallboot-api/app/src/main/resources/application-dev.yml b/smallboot-api/app/src/main/resources/application-dev.yml new file mode 100644 index 0000000..f744300 --- /dev/null +++ b/smallboot-api/app/src/main/resources/application-dev.yml @@ -0,0 +1,32 @@ +# TODO 在这里修改相关配置信息 +# ================================== ↓↓↓↓↓↓ smallboot配置 ↓↓↓↓↓↓ ================================== +smallboot: + ip: 127.0.0.1 + minio: + url: http://172.16.16.244:9001 + accessKey: admin + secretKey: password + bucketName: test + # redis密码 + redis: + database: 0 + host: ${smallboot.ip} + port: 6379 + password: 123456 + # mysql数据源连接参数 + mysql: + # MySQL在高版本需要指明是否进行SSL连接 解决则加上 &useSSL=false + # &serverTimezone=Asia/Shanghai :解决fastjson时差13小时问题 + # rewriteBatchedStatements=true =》可实现多条sql合并提交给mysql,解决MybatisPlus批量插入慢问题 + url-params: ?allowMultiQueries=true&useUnicode=true&characterEncoding=UTF8&zeroDateTimeBehavior=convertToNull&useSSL=false&serverTimezone=Asia/Shanghai&rewriteBatchedStatements=true + master: + ip: ${smallboot.ip} + port: 3306 + db-name: smallboot + username: root + password: root + url-params: ${smallboot.mysql.url-params} + # sql日志,默认关闭 true:开启 false:关闭 + mybatis-plus-sql-log: false + # api日志,默认关闭 true:开启 false:关闭 + api-log: true \ No newline at end of file diff --git a/smallboot-api/app/src/main/resources/application.yml b/smallboot-api/app/src/main/resources/application.yml new file mode 100644 index 0000000..2c208c0 --- /dev/null +++ b/smallboot-api/app/src/main/resources/application.yml @@ -0,0 +1,27 @@ +server: + port: 888 + +spring: + application: + name: app # 应用名称 + profiles: + active: dev # 环境配置 + # 聚合各个基础组件配置 + include: + - auth + - base + - db + - file + - log + - redis + - swagger + - web + +management: + endpoints: + web: + exposure: + include: '*' # 暴露监控端点 +# security: +# enabled: false # 关闭安全验证 + diff --git a/smallboot-api/app/src/test/java/com/zhengqing/app/AppTest.java b/smallboot-api/app/src/test/java/com/zhengqing/app/AppTest.java new file mode 100644 index 0000000..b3d3b17 --- /dev/null +++ b/smallboot-api/app/src/test/java/com/zhengqing/app/AppTest.java @@ -0,0 +1,46 @@ +package com.zhengqing.app; + +import cn.hutool.core.date.DatePattern; +import cn.hutool.core.date.DateUtil; +import cn.hutool.core.lang.UUID; +import cn.hutool.crypto.SecureUtil; +import cn.hutool.crypto.symmetric.AES; +import com.zhengqing.common.base.constant.AppConstant; +import com.zhengqing.common.base.util.AutoUpgradeUtil; +import lombok.extern.slf4j.Slf4j; +import org.junit.Test; + +import java.util.Date; + +@Slf4j +public class AppTest { + + @Test + public void test() throws Exception { + System.out.println("hello"); + } + + @Test + public void test02() throws Exception { + AES aes = SecureUtil.aes(AppConstant.AES_KEY); + // 加密 + String encrypt = aes.encryptHex("123456"); + System.out.println(encrypt); + // 解密 + String decryptStr = aes.decryptStr(encrypt); + System.out.println(decryptStr); + } + + @Test + public void test03() throws Exception { + String today = DateUtil.format(new Date(), DatePattern.PURE_DATE_PATTERN); + String s = AutoUpgradeUtil.autoUpgrade(null); + System.out.println(s); + } + + @Test + public void test04() throws Exception { + System.out.println(UUID.randomUUID().toString()); + } + +} diff --git a/smallboot-api/app/src/test/java/com/zhengqing/app/ColorMain.java b/smallboot-api/app/src/test/java/com/zhengqing/app/ColorMain.java new file mode 100644 index 0000000..aba7367 --- /dev/null +++ b/smallboot-api/app/src/test/java/com/zhengqing/app/ColorMain.java @@ -0,0 +1,13 @@ +package com.zhengqing.app; + +import lombok.extern.slf4j.Slf4j; +import org.junit.jupiter.api.Test; + +@Slf4j +class ColorMain { + + @Test + public void test() throws Exception { + + } +} \ No newline at end of file diff --git a/smallboot-api/common/auth/pom.xml b/smallboot-api/common/auth/pom.xml new file mode 100644 index 0000000..a043c9b --- /dev/null +++ b/smallboot-api/common/auth/pom.xml @@ -0,0 +1,49 @@ + + + 4.0.0 + + + common + com.zhengqing + ${revision} + + + auth + + ${project.artifactId} + ${revision} + jar + + + + + + + com.zhengqing + base + + + + + cn.dev33 + sa-token-spring-boot-starter + 1.27.0 + + + + + cn.dev33 + sa-token-dao-redis-jackson + 1.27.0 + + + + + org.apache.commons + commons-pool2 + + + + diff --git a/smallboot-api/common/auth/src/main/java/com/zhengqing/common/auth/api/TestController.java b/smallboot-api/common/auth/src/main/java/com/zhengqing/common/auth/api/TestController.java new file mode 100644 index 0000000..49f12d4 --- /dev/null +++ b/smallboot-api/common/auth/src/main/java/com/zhengqing/common/auth/api/TestController.java @@ -0,0 +1,94 @@ +package com.zhengqing.common.auth.api; + + +import cn.dev33.satoken.SaManager; +import cn.dev33.satoken.config.SaTokenConfig; +import cn.dev33.satoken.stp.StpUtil; +import cn.dev33.satoken.util.SaResult; +import cn.hutool.core.date.DateTime; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +/** + *

测试api

+ * + * @author zhengqingya + * @description + * @date 2021/10/5 2:36 下午 + */ +@Slf4j +@RestController +@RequestMapping("/test") +@Api(tags = "测试api") +public class TestController { + + @GetMapping("time") + @ApiOperation("time") + public String time() { + log.info("time: {}", DateTime.now()); + return DateTime.now().toString(); + } + + @GetMapping("getSaTokenConfig") + @ApiOperation("Sa-Token配置") + public SaTokenConfig getSaTokenConfig() { + log.info("Sa-Token配置:{}", SaManager.getConfig()); + return SaManager.getConfig(); + } + + @GetMapping("doLogin") + @ApiOperation("登录") + public String doLogin(String username, String password) { + // 此处仅作模拟示例,真实项目需要从数据库中查询数据进行比对 + if ("zhang".equals(username) && "123456".equals(password)) { + StpUtil.logout(); + StpUtil.login(10001); + return "登录成功" + StpUtil.getLoginId(); + } + StpUtil.logout(); + return "登录失败"; + } + + @GetMapping("isLogin") + @ApiOperation("查询登录状态") + public String isLogin() { + return "当前会话是否登录:" + StpUtil.isLogin(); + } + + @GetMapping("logout") + @ApiOperation("退出登录") + public String logout(String loginId) { + StpUtil.logoutByLoginId(loginId); + return "SUCCESS"; + } + + @GetMapping("checkLogin") + @ApiOperation("检查是否登录") + public String checkLogin() { + try { + StpUtil.checkLogin(); + } catch (Exception e) { + log.info("登录认证失效:{}", e.getMessage()); + return "FAIL:" + e.getMessage(); + } + log.info("登录了..."); + return "SUCCESS"; + } + + @GetMapping("tokenInfo") + @ApiOperation("查询Token信息") + public SaResult tokenInfo() { + return SaResult.data(StpUtil.getTokenInfo()); + } + + @GetMapping("getTokenValueByLoginId") + @ApiOperation("获取账号id为10001的token令牌值") + public String getTokenValueByLoginId() { + return StpUtil.getTokenValueByLoginId(10001); + } + +} diff --git a/smallboot-api/common/auth/src/main/java/com/zhengqing/common/auth/config/SaTokenCustomConfig.java b/smallboot-api/common/auth/src/main/java/com/zhengqing/common/auth/config/SaTokenCustomConfig.java new file mode 100644 index 0000000..8b0775d --- /dev/null +++ b/smallboot-api/common/auth/src/main/java/com/zhengqing/common/auth/config/SaTokenCustomConfig.java @@ -0,0 +1,30 @@ +package com.zhengqing.common.auth.config; + +import cn.dev33.satoken.strategy.SaStrategy; +import cn.dev33.satoken.util.SaFoxUtil; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Configuration; + +/** + *

自定义sa-token生成策略

+ * + * @author zhengqingya + * @description + * @date 2021/11/3 12:06 + */ +@Configuration +public class SaTokenCustomConfig { + + /** + * 重写 Sa-Token 框架内部算法策略 + */ + @Autowired + public void rewriteSaStrategy() { + // 重写 Token 生成策略 + SaStrategy.me.createToken = (loginId, loginType) -> { + // 随机60位长度字符串 + return SaFoxUtil.getRandomString(60); + }; + } + +} diff --git a/smallboot-api/common/auth/src/main/java/com/zhengqing/common/auth/config/SaTokenUrlConfig.java b/smallboot-api/common/auth/src/main/java/com/zhengqing/common/auth/config/SaTokenUrlConfig.java new file mode 100644 index 0000000..f9e160d --- /dev/null +++ b/smallboot-api/common/auth/src/main/java/com/zhengqing/common/auth/config/SaTokenUrlConfig.java @@ -0,0 +1,32 @@ +package com.zhengqing.common.auth.config; + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Configuration; + +import java.util.List; + + +/** + *

Sa-Token 拦截/开放 URL 配置类

+ * + * @author zhengqingya + * @description + * @date 2021/11/3 8:54 下午 + */ +@Data +@Configuration +@ConfigurationProperties(prefix = "sa-token", ignoreUnknownFields = true) +public class SaTokenUrlConfig { + + /** + * 拦截url + */ + private List interceptUrlList; + + /** + * 开放url + */ + private List openUrlList; + +} diff --git a/smallboot-api/common/auth/src/main/java/com/zhengqing/common/auth/config/SaTokenWebMvcConfig.java b/smallboot-api/common/auth/src/main/java/com/zhengqing/common/auth/config/SaTokenWebMvcConfig.java new file mode 100644 index 0000000..0c97497 --- /dev/null +++ b/smallboot-api/common/auth/src/main/java/com/zhengqing/common/auth/config/SaTokenWebMvcConfig.java @@ -0,0 +1,31 @@ +package com.zhengqing.common.auth.config; + +import cn.dev33.satoken.interceptor.SaRouteInterceptor; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.servlet.config.annotation.InterceptorRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +import javax.annotation.Resource; + +/** + *

注册 Sa-Token 路由拦截器

+ * + * @author zhengqingya + * @description + * @date 2021/11/3 12:06 + */ +@Configuration +public class SaTokenWebMvcConfig implements WebMvcConfigurer { + + @Resource + private SaTokenUrlConfig saTokenUrlConfig; + + @Override + public void addInterceptors(InterceptorRegistry registry) { + // 注册1个登录认证拦截器 + registry.addInterceptor(new SaRouteInterceptor()) + .addPathPatterns(this.saTokenUrlConfig.getInterceptUrlList()) + .excludePathPatterns(this.saTokenUrlConfig.getOpenUrlList()); + } + +} diff --git a/smallboot-api/common/auth/src/main/java/com/zhengqing/common/auth/model/bo/JwtUserBO.java b/smallboot-api/common/auth/src/main/java/com/zhengqing/common/auth/model/bo/JwtUserBO.java new file mode 100644 index 0000000..8956c16 --- /dev/null +++ b/smallboot-api/common/auth/src/main/java/com/zhengqing/common/auth/model/bo/JwtUserBO.java @@ -0,0 +1,31 @@ +package com.zhengqing.common.auth.model.bo; + +import com.zhengqing.common.base.model.bo.BaseBO; +import io.swagger.annotations.ApiModelProperty; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.SuperBuilder; + +/** + *

+ * 用户token信息 + *

+ * + * @author zhengqingya + * @description + * @date 2020/11/28 23:16 + */ +@Data +@SuperBuilder +@NoArgsConstructor +@AllArgsConstructor +public class JwtUserBO extends BaseBO { + + @ApiModelProperty(value = "用户ID") + private Integer userId; + + @ApiModelProperty(value = "用户名") + private String userName; + +} diff --git a/smallboot-api/common/auth/src/main/java/com/zhengqing/common/auth/model/dto/AuthLoginDTO.java b/smallboot-api/common/auth/src/main/java/com/zhengqing/common/auth/model/dto/AuthLoginDTO.java new file mode 100644 index 0000000..0b08ce0 --- /dev/null +++ b/smallboot-api/common/auth/src/main/java/com/zhengqing/common/auth/model/dto/AuthLoginDTO.java @@ -0,0 +1,34 @@ +package com.zhengqing.common.auth.model.dto; + +import io.swagger.annotations.ApiModelProperty; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import javax.validation.constraints.NotBlank; + +/** + *

+ * 登录参数 + *

+ * + * @author zhengqingya + * @description + * @date 2020/4/15 20:55 + */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class AuthLoginDTO { + + @NotBlank(message = "账号不能为空!") + @ApiModelProperty(value = "账号", required = true, example = "test") + private String username; + + @NotBlank(message = "密码不能为空!") + @ApiModelProperty(value = "密码", example = "123456") + private String password; + +} diff --git a/smallboot-api/common/auth/src/main/java/com/zhengqing/common/auth/model/vo/AuthLoginVO.java b/smallboot-api/common/auth/src/main/java/com/zhengqing/common/auth/model/vo/AuthLoginVO.java new file mode 100644 index 0000000..ac57b6a --- /dev/null +++ b/smallboot-api/common/auth/src/main/java/com/zhengqing/common/auth/model/vo/AuthLoginVO.java @@ -0,0 +1,31 @@ +package com.zhengqing.common.auth.model.vo; + +import io.swagger.annotations.ApiModelProperty; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + *

+ * 登录参数 + *

+ * + * @author zhengqingya + * @description + * @date 2020/4/15 20:55 + */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class AuthLoginVO { + + @ApiModelProperty("认证请求头名") + private String tokenName; + + @ApiModelProperty("认证值") + private String tokenValue; + + +} diff --git a/smallboot-api/common/auth/src/main/resources/application-auth.yml b/smallboot-api/common/auth/src/main/resources/application-auth.yml new file mode 100644 index 0000000..affe4ed --- /dev/null +++ b/smallboot-api/common/auth/src/main/resources/application-auth.yml @@ -0,0 +1,40 @@ +# Sa-Token配置 +sa-token: + # token名称 (同时也是cookie名称) 注意不能带冒号: + token-name: Authorization-smallboot + # token值前缀 + # token-prefix: Bearer + # token有效期,单位s 这里设置1天, -1代表永不过期 + timeout: 86400 + # token临时有效期 (指定时间内无操作就视为token过期) 单位: 秒 + activity-timeout: -1 + # 是否允许同一账号并发登录 (为true时允许一起登录, 为false时新登录挤掉旧登录) + is-concurrent: false + # 在多人登录同一账号时,是否共用一个token (为true时所有登录共用一个token, 为false时每次登录新建一个token) + is-share: false + # token风格 + token-style: random-128 + # 是否从cookie中读取token + is-read-cookie: false + # 是否从请求体里读取token + is-read-body: false + # 是否从head中读取token + is-read-head: true + # 是否输出操作日志 + is-log: true + # 是否在初始化配置时打印版本字符画 + is-print: false + # 拦截url + intercept-url-list: + - /** + # 开放url + open-url-list: + # Knife4j + - /webjars/** + - /doc.html + - /swagger-resources/** + - /v2/api-docs + - /v3/api-docs + - /favicon.ico + # 其它 + - /auth/** diff --git a/smallboot-api/common/base/pom.xml b/smallboot-api/common/base/pom.xml new file mode 100644 index 0000000..afb2d3f --- /dev/null +++ b/smallboot-api/common/base/pom.xml @@ -0,0 +1,30 @@ + + + 4.0.0 + + + common + com.zhengqing + ${revision} + + + base + + ${project.artifactId} + ${revision} + jar + + + + + + + com.zhengqing + swagger + provided + + + + diff --git a/smallboot-api/common/base/src/main/java/com/zhengqing/common/base/constant/AppConstant.java b/smallboot-api/common/base/src/main/java/com/zhengqing/common/base/constant/AppConstant.java new file mode 100644 index 0000000..10d5fe0 --- /dev/null +++ b/smallboot-api/common/base/src/main/java/com/zhengqing/common/base/constant/AppConstant.java @@ -0,0 +1,165 @@ +package com.zhengqing.common.base.constant; + +import java.nio.charset.StandardCharsets; +import java.util.HashMap; +import java.util.Map; + +/** + *

+ * 全局常用变量 + *

+ * + * @author zhengqingya + * @description + * @date 2019/10/12 14:47 + */ +public interface AppConstant extends BaseConstant { + + /** + * 解决返回json字符串中文乱码问题 + */ + String CONTENT_TYPE = "application/json;charset=utf-8"; + + /** + * 实体类名 + */ + String ENTITY_NAME = "${entity}"; + + /** + * 接口url + */ + Map URL_MAPPING_MAP = new HashMap<>(); + + /** + * 密码加密相关 + */ + String DEFAULT_PASSWORD = "123456"; + String SALT = "zhengqing"; + int HASH_ITERATIONS = 1; + + /** + * 用于登录密码加密解密 + */ + String DES_KEY = "deskeyzq"; + + /** + * 用于DB中的密码加密解密 + */ + byte[] AES_KEY = "123456789abcdefg".getBytes(StandardCharsets.UTF_8); + + /** + * 请求头类型: application/x-www-form-urlencoded : form表单格式 application/json : json格式 + */ + String REQUEST_HEADERS_CONTENT_TYPE = "application/json"; + + /** + * 系统超级管理员id + */ + Integer SYSTEM_SUPER_ADMIN_USER_ID = 1; + + /** + * 登录者角色 + */ + String ROLE_LOGIN = "role_login"; + + /** + * 所有第一级项目关联包父类id、父包名 + */ + Integer PROJECT_RE_PACKAGE_PARENT_ID = 0; + String PROJECT_RE_PACKAGE_PARENT_NAME = "com.zhengqing.demo"; + + /** + * 系统权限相关 + */ + String WRONG_PASSWORD = "密码错误!"; + String WRONG_OLD_PASSWORD = "原密码错误!"; + String NO_USERNAME = "用户名不存在!"; + String NO_PERMISSION = "请联系管理员为其分配角色!"; + String NO_TOKEN = "TOKEN已过期请重新登录!"; + String WRONG_USERNAME_PASSWORD = "用户名或密码错误!"; + + // =============================================================================== + // ============================ ↓↓↓↓↓↓ 文件系列 ↓↓↓↓↓↓ ============================ + // =============================================================================== + + /** + * liunx系统分隔符 + */ + String SEPARATOR_SPRIT = "/"; + /** + * win系统分隔符 + */ + String SEPARATOR_BACKSLASH = "\\\\"; + /** + * 分隔符 - 逗号 + */ + String SEPARATOR_COMMA = ","; + /** + * 分隔符 - 点 + */ + String SEPARATOR_SPOT = "."; + + /** + * 获取项目根目录 + */ + String PROJECT_ROOT_DIRECTORY = System.getProperty("user.dir") + .replaceAll("\\\\", SEPARATOR_SPRIT); + + String IMG_DOMAIN = ""; + + + /** + * csdn域名前缀 + */ + String CSDN_DOMAIN_PREFIX = "https://blog.csdn.net/"; + /** + * csdn博客临时存储路径 + */ + String FILE_PATH_CSDN_BLOG_IMPORT_SRC = PROJECT_ROOT_DIRECTORY + "/tmp/import/blog"; + String FILE_PATH_CSDN_BLOG_IMPORT_ZIP = PROJECT_ROOT_DIRECTORY + "/tmp/import/blog.zip"; + String FILE_PATH_CSDN_BLOG_EXPORT_SRC = PROJECT_ROOT_DIRECTORY + "/tmp/export/blog"; + String FILE_PATH_CSDN_BLOG_EXPORT_ZIP = PROJECT_ROOT_DIRECTORY + "/tmp/export/blog.zip"; + String FILE_PATH_CSDN_BLOG_EXPORT_EXCEL = PROJECT_ROOT_DIRECTORY + "/tmp/export/blog/excel.xls"; + String FILE_PATH_CSDN_BLOG_EXPORT_HTML = + PROJECT_ROOT_DIRECTORY + "/tmp/export/blog/html/" + System.currentTimeMillis() + "/"; + /** + * 代码生成临时存储路径 + */ + String FILE_PATH_CODE_GENERATOR_DATA_PATH = PROJECT_ROOT_DIRECTORY + "/tmp/upload"; + String FILE_PATH_CODE_GENERATOR_SRC_CODE = PROJECT_ROOT_DIRECTORY + "/tmp/upload/generate_code"; + String FILE_PATH_CODE_GENERATOR_TEMPLATE_CODE = + PROJECT_ROOT_DIRECTORY + "/tmp/upload/template_code"; + + String FILE_PATH_CODE_GENERATOR_FILE_NAME_DATA = + PROJECT_ROOT_DIRECTORY + "/tmp/upload/handle_generate_file_name_data"; + String FILE_PATH_CODE_GENERATOR_ZIP = PROJECT_ROOT_DIRECTORY + "/tmp/upload/code.zip"; + + /** + * 数据库导出word文档路径 + */ + String FILE_PATH_DB_WORD = PROJECT_ROOT_DIRECTORY + "/tmp/db/数据库信息.doc"; + + // =============================================================================== + // ============================ ↓↓↓↓↓↓ redis缓存系列 ↓↓↓↓↓↓ ============================ + // =============================================================================== + + + // =============================================================================== + // ============================ ↓↓↓↓↓↓ 业务系列 ↓↓↓↓↓↓ ============================ + // =============================================================================== + + /** + * 缓存默认过期时间 - 24小时 + */ + Long DEFAULT_EXPIRES_TIME = 24 * 60 * 60 * 1000L; + + // =============================================================================== + // ============================ ↓↓↓↓↓↓ 其它 ↓↓↓↓↓↓ ============================ + // =============================================================================== + + /** + * 限流测试 + */ + String API_LIMIT_KEY = "API_LIMIT"; + +} diff --git a/smallboot-api/common/base/src/main/java/com/zhengqing/common/base/constant/BaseConstant.java b/smallboot-api/common/base/src/main/java/com/zhengqing/common/base/constant/BaseConstant.java new file mode 100644 index 0000000..9b2864f --- /dev/null +++ b/smallboot-api/common/base/src/main/java/com/zhengqing/common/base/constant/BaseConstant.java @@ -0,0 +1,50 @@ +package com.zhengqing.common.base.constant; + + +import com.google.common.collect.Lists; + +import java.util.List; + +/** + *

+ * 全局常用变量 - base + *

+ * + * @author zhengqingya + * @description + * @date 2019/10/12 14:47 + */ +public interface BaseConstant { + + /** + * 第一级父类id + */ + Integer PARENT_ID = 0; + + /** + * 用户ID、用户名、消息上下文Key 【注:key名称一定不要和前端请求参数中的属性名一样,否则会拿不到真正的值!!!】 + */ + String CONTEXT_KEY_SYS_USER_ID = "small_tools_sys_user_id"; + String CONTEXT_KEY_UMS_USER_ID = "small_tools_ums_user_id"; + String CONTEXT_KEY_USERNAME = "small_tools_username"; + String DEFAULT_CONTEXT_KEY_USER_ID = "0"; + String DEFAULT_CONTEXT_KEY_USERNAME = "未知"; + + /** + * 请求头中的用户信息标识 + */ + String REQUEST_HEADER_TOKEN = "Authorization-smallboot"; + + /** + * rpc服务调用不需要封装返回值的api + *    ?  =>  匹配一个字符 + *    *  => 匹配0个及以上字符 + *    ** => 匹配0个及以上目录 + */ + List RETURN_VALUE_HANDLER_EXCLUDE_API_LIST = Lists.newArrayList( + "*:/rpc/client/**/*", + "POST:/oauth/token", + "POST:/auth/oauth/token" + ); + +} diff --git a/smallboot-api/common/base/src/main/java/com/zhengqing/common/base/constant/ProjectConstant.java b/smallboot-api/common/base/src/main/java/com/zhengqing/common/base/constant/ProjectConstant.java new file mode 100644 index 0000000..9e4ffcf --- /dev/null +++ b/smallboot-api/common/base/src/main/java/com/zhengqing/common/base/constant/ProjectConstant.java @@ -0,0 +1,45 @@ +package com.zhengqing.common.base.constant; + +/** + *

全局常用变量 - 工程使用

+ * + * @author zhengqingya + * @description + * @date 2021/7/20 18:16 + */ +public interface ProjectConstant { + + // =============================================================================== + // ============================ ↓↓↓↓↓↓ 文件系列 ↓↓↓↓↓↓ ============================ + // =============================================================================== + + /** + * 系统分隔符 + */ + String SYSTEM_SEPARATOR = "/"; + + /** + * 获取项目根目录 + */ + String PROJECT_ROOT_DIRECTORY = System.getProperty("user.dir").replaceAll("\\\\", SYSTEM_SEPARATOR); + + /** + * 临时文件相关 + */ + String DEFAULT_FOLDER_TMP = PROJECT_ROOT_DIRECTORY + "/tmp"; + String DEFAULT_FOLDER_TMP_GENERATE = PROJECT_ROOT_DIRECTORY + "/tmp-generate"; + + // =============================================================================== + // ============================ ↓↓↓↓↓↓ other ↓↓↓↓↓↓ ============================== + // =============================================================================== + + /** + * 实体类 + */ + String ENTITY_PACKAGE = "com.zhengqing.*.entity"; + /** + * mapper + */ + String MAPPER_PACKAGE = "com.zhengqing.*.mapper"; + +} diff --git a/smallboot-api/common/base/src/main/java/com/zhengqing/common/base/constant/RpcConstant.java b/smallboot-api/common/base/src/main/java/com/zhengqing/common/base/constant/RpcConstant.java new file mode 100644 index 0000000..a880d1a --- /dev/null +++ b/smallboot-api/common/base/src/main/java/com/zhengqing/common/base/constant/RpcConstant.java @@ -0,0 +1,23 @@ +package com.zhengqing.common.base.constant; + + +/** + *

RPC常用变量

+ * + * @author zhengqingya + * @description + * @date 2021/7/20 18:16 + */ +public interface RpcConstant extends BaseConstant { + + // =============================================================================== + // ============================ ↓↓↓↓↓↓ rpc ↓↓↓↓↓↓ ================================ + // =============================================================================== + + + // =============================================================================== + // ============================ ↓↓↓↓↓↓ other ↓↓↓↓↓↓ ============================== + // =============================================================================== + + +} diff --git a/smallboot-api/common/base/src/main/java/com/zhengqing/common/base/constant/SecurityConstant.java b/smallboot-api/common/base/src/main/java/com/zhengqing/common/base/constant/SecurityConstant.java new file mode 100644 index 0000000..20a5a6b --- /dev/null +++ b/smallboot-api/common/base/src/main/java/com/zhengqing/common/base/constant/SecurityConstant.java @@ -0,0 +1,77 @@ +package com.zhengqing.common.base.constant; + +/** + *

全局常用变量 - 安全认证

+ * + * @author zhengqingya + * @description + * @date 2022/6/13 7:14 PM + */ +public interface SecurityConstant { + + /** + * 认证请求头key + */ + String AUTHORIZATION_KEY = "Authorization-smallboot"; + + /** + * Basic认证前缀 + */ + String BASIC_PREFIX = "Basic "; + + /** + * JWT令牌前缀 + */ + String JWT_PREFIX = "Bearer "; + + /** + * JWT存储权限前缀 + */ + String AUTHORITY_PREFIX = "ROLE_"; + + /** + * JWT存储权限属性 + */ + String JWT_AUTHORITIES_KEY = "authorities"; + + /** + * 客户端ID + */ + String CLIENT_ID_KEY = "client_id"; + + /** + * 刷新token + */ + String REFRESH_TOKEN_KEY = "refresh_token"; + + /** + * 认证身份标识 + */ + String GRANT_TYPE = "authGrantType"; + + /** + * 超级管理员角色标识 + */ + String SUPER_ADMIN_ROLE_CODE = "super_admin"; + + // =============================================================================== + // ============================ ↓↓↓↓↓↓ redis缓存 ↓↓↓↓↓↓ ============================ + // =============================================================================== + + /** + * jwt自定义用户信息 + */ + String JWT_CUSTOM_USER = "smallboot:system:jwt_custom_user:"; + + /** + * url权限关联角色 + * [ {接口路径:[角色编码]},...] + */ + String URL_PERM_RE_ROLES = "smallboot:system:perm_rule:url"; + + /** + * 验证码 + */ + String CAPTCHA_CODE = "smallboot:auth:captcha:"; + +} diff --git a/smallboot-api/common/base/src/main/java/com/zhengqing/common/base/constant/ServiceConstant.java b/smallboot-api/common/base/src/main/java/com/zhengqing/common/base/constant/ServiceConstant.java new file mode 100644 index 0000000..ae6d25b --- /dev/null +++ b/smallboot-api/common/base/src/main/java/com/zhengqing/common/base/constant/ServiceConstant.java @@ -0,0 +1,30 @@ +package com.zhengqing.common.base.constant; + +/** + *

全局常用变量 - 工程使用

+ * + * @author zhengqingya + * @description + * @date 2021/7/20 18:16 + */ +public interface ServiceConstant { + + + // =============================================================================== + // ============================ ↓↓↓↓↓↓ service ↓↓↓↓↓↓ ============================ + // =============================================================================== + + String SERVICE_BASE_PACKAGE = "com.zhengqing"; + /** + * api基础前缀 + */ + String SERVICE_API_PREFIX_WEB = "/web/api"; + String SERVICE_API_PREFIX_MINI = "/mini/api"; + /** + * 各服务api前缀 + */ + String SERVICE_API_PREFIX_WEB_SYSTEM = SERVICE_API_PREFIX_WEB + "/system"; + String SERVICE_API_PREFIX_WEB_BUS = SERVICE_API_PREFIX_WEB + "/bus"; + + +} diff --git a/smallboot-api/common/base/src/main/java/com/zhengqing/common/base/constant/ThreadPoolConstant.java b/smallboot-api/common/base/src/main/java/com/zhengqing/common/base/constant/ThreadPoolConstant.java new file mode 100644 index 0000000..4f3ecaa --- /dev/null +++ b/smallboot-api/common/base/src/main/java/com/zhengqing/common/base/constant/ThreadPoolConstant.java @@ -0,0 +1,34 @@ +package com.zhengqing.common.base.constant; + +/** + *

全局常用变量 - 线程池

+ * + * @author zhengqingya + * @description + * @date 2021/5/27 10:52 + */ +public interface ThreadPoolConstant { + + // =============================================================================== + // ============================ ↓↓↓↓↓↓ 线程池 ↓↓↓↓↓↓ ============================== + // =============================================================================== + + /** + * SmallTools线程池 + */ + String SMALL_TOOLS_THREAD_POOL = "smallToolsThreadPoolTaskExecutor"; + + // =============================================================================== + // ============================ ↓↓↓↓↓↓ 线程池 - 线程名前缀 ↓↓↓↓↓↓ =================== + // =============================================================================== + + /** + * 替换原生Spring默认线程池-线程名前缀 + */ + String SPRING_DEFAULT_THREAD_NAME_PREFIX = "MyTaskExecutorInit-"; + /** + * SmallTools线程池-线程名前缀 + */ + String SMALL_TOOLS_THREAD_NAME_PREFIX = "SmallToolsTaskExecutor-"; + +} diff --git a/smallboot-api/common/base/src/main/java/com/zhengqing/common/base/context/AuthSourceContext.java b/smallboot-api/common/base/src/main/java/com/zhengqing/common/base/context/AuthSourceContext.java new file mode 100644 index 0000000..2620384 --- /dev/null +++ b/smallboot-api/common/base/src/main/java/com/zhengqing/common/base/context/AuthSourceContext.java @@ -0,0 +1,29 @@ +package com.zhengqing.common.base.context; + +import lombok.extern.slf4j.Slf4j; + +/** + *

认证来源上下文

+ * + * @author zhengqingya + * @description {@link com.zhengqing.common.base.enums.AuthSourceEnum} + * @date 2021/6/30 9:24 下午 + */ +@Slf4j +public class AuthSourceContext { + + public static final ThreadLocal THREAD_LOCAL = new ThreadLocal<>(); + + public static void set(String authSource) { + THREAD_LOCAL.set(authSource); + } + + public static String get() { + return THREAD_LOCAL.get(); + } + + public static void remove() { + THREAD_LOCAL.remove(); + } + +} diff --git a/smallboot-api/common/base/src/main/java/com/zhengqing/common/base/context/JwtCustomUserContext.java b/smallboot-api/common/base/src/main/java/com/zhengqing/common/base/context/JwtCustomUserContext.java new file mode 100644 index 0000000..db782e0 --- /dev/null +++ b/smallboot-api/common/base/src/main/java/com/zhengqing/common/base/context/JwtCustomUserContext.java @@ -0,0 +1,29 @@ +package com.zhengqing.common.base.context; + + +import com.zhengqing.common.base.model.bo.JwtCustomUserBO; + +/** + *

jwt自定义用户信息上下文

+ * + * @author zhengqingya + * @description 请务必在请求结束时, 调用 @Method remove() + * @date 2020/8/1 19:07 + */ +public class JwtCustomUserContext { + + public static final ThreadLocal THREAD_LOCAL = new ThreadLocal<>(); + + public static JwtCustomUserBO get() { + return THREAD_LOCAL.get(); + } + + public static void set(JwtCustomUserBO jwtCustomUserBO) { + THREAD_LOCAL.set(jwtCustomUserBO); + } + + public static void remove() { + THREAD_LOCAL.remove(); + } + +} diff --git a/smallboot-api/common/base/src/main/java/com/zhengqing/common/base/context/SysUserContext.java b/smallboot-api/common/base/src/main/java/com/zhengqing/common/base/context/SysUserContext.java new file mode 100644 index 0000000..92995c8 --- /dev/null +++ b/smallboot-api/common/base/src/main/java/com/zhengqing/common/base/context/SysUserContext.java @@ -0,0 +1,58 @@ +package com.zhengqing.common.base.context; + +import com.zhengqing.common.base.constant.BaseConstant; + +import java.util.HashMap; +import java.util.Map; + +/** + *

B端系统用户上下文

+ * + * @author zhengqingya + * @description 请务必在请求结束时, 调用 @Method remove() + * @date 2020/8/1 19:07 + */ +public class SysUserContext { + + public static final ThreadLocal> THREAD_LOCAL = new ThreadLocal<>(); + + public static Object get(String key) { + Map map = THREAD_LOCAL.get(); + if (map == null) { + return null; + } + return map.get(key); + } + + public static Integer getUserId() { + Object value = get(BaseConstant.CONTEXT_KEY_SYS_USER_ID); + return value == null ? Integer.valueOf(BaseConstant.DEFAULT_CONTEXT_KEY_USER_ID) : (Integer) value; + } + + public static String getUsername() { + Object value = get(BaseConstant.CONTEXT_KEY_USERNAME); + return value == null ? BaseConstant.DEFAULT_CONTEXT_KEY_USERNAME : (String) value; + } + + public static void setUserId(Integer userId) { + set(BaseConstant.CONTEXT_KEY_SYS_USER_ID, userId); + } + + public static void setUsername(String username) { + set(BaseConstant.CONTEXT_KEY_USERNAME, username); + } + + private static void set(String key, Object value) { + Map map = THREAD_LOCAL.get(); + if (map == null) { + map = new HashMap<>(1); + THREAD_LOCAL.set(map); + } + map.put(key, value); + } + + public static void remove() { + THREAD_LOCAL.remove(); + } + +} diff --git a/smallboot-api/common/base/src/main/java/com/zhengqing/common/base/context/TenantIdContext.java b/smallboot-api/common/base/src/main/java/com/zhengqing/common/base/context/TenantIdContext.java new file mode 100644 index 0000000..0fbb5e7 --- /dev/null +++ b/smallboot-api/common/base/src/main/java/com/zhengqing/common/base/context/TenantIdContext.java @@ -0,0 +1,49 @@ +package com.zhengqing.common.base.context; + +import lombok.extern.slf4j.Slf4j; + +/** + *

租户ID上下文

+ * + * @author zhengqingya + * @description + * @date 2021/6/30 9:24 下午 + */ +@Slf4j +public class TenantIdContext { + + /** + * 租户ID + */ + public static final ThreadLocal TENANT_ID_THREAD_LOCAL = new ThreadLocal<>(); + /** + * 租户ID是否启用标识 + * true : 是 -> 执行sql时,自动拼接租户ID + * false: 否 -> 执行sql时,不自动拼接租户ID + */ + public static final ThreadLocal TENANT_ID_FLAG_THREAD_LOCAL = new ThreadLocal<>(); + + public static void setTenantId(Integer tenantId) { + TENANT_ID_THREAD_LOCAL.set(tenantId); + TENANT_ID_FLAG_THREAD_LOCAL.set(true); + } + + public static Integer getTenantId() { + return TENANT_ID_THREAD_LOCAL.get(); + } + + public static void removeFlag() { + TENANT_ID_FLAG_THREAD_LOCAL.set(false); + } + + + public static Boolean getFlag() { + return TENANT_ID_FLAG_THREAD_LOCAL.get(); + } + + public static void remove() { + TENANT_ID_THREAD_LOCAL.remove(); + TENANT_ID_FLAG_THREAD_LOCAL.remove(); + } + +} diff --git a/smallboot-api/common/base/src/main/java/com/zhengqing/common/base/context/UmsUserContext.java b/smallboot-api/common/base/src/main/java/com/zhengqing/common/base/context/UmsUserContext.java new file mode 100644 index 0000000..5548d3c --- /dev/null +++ b/smallboot-api/common/base/src/main/java/com/zhengqing/common/base/context/UmsUserContext.java @@ -0,0 +1,58 @@ +package com.zhengqing.common.base.context; + +import com.zhengqing.common.base.constant.BaseConstant; + +import java.util.HashMap; +import java.util.Map; + +/** + *

C端用户上下文

+ * + * @author zhengqingya + * @description 请务必在请求结束时, 调用 @Method remove() + * @date 2020/8/1 19:07 + */ +public class UmsUserContext { + + public static final ThreadLocal> THREAD_LOCAL = new ThreadLocal<>(); + + public static Object get(String key) { + Map map = THREAD_LOCAL.get(); + if (map == null) { + return null; + } + return map.get(key); + } + + public static Long getUserId() { + Object value = get(BaseConstant.CONTEXT_KEY_UMS_USER_ID); + return value == null ? Long.valueOf(BaseConstant.DEFAULT_CONTEXT_KEY_USER_ID) : (Long) value; + } + + public static String getUsername() { + Object value = get(BaseConstant.CONTEXT_KEY_USERNAME); + return value == null ? BaseConstant.DEFAULT_CONTEXT_KEY_USERNAME : (String) value; + } + + public static void setUserId(Long userId) { + set(BaseConstant.CONTEXT_KEY_UMS_USER_ID, userId); + } + + public static void setUsername(String username) { + set(BaseConstant.CONTEXT_KEY_USERNAME, username); + } + + private static void set(String key, Object value) { + Map map = THREAD_LOCAL.get(); + if (map == null) { + map = new HashMap<>(1); + THREAD_LOCAL.set(map); + } + map.put(key, value); + } + + public static void remove() { + THREAD_LOCAL.remove(); + } + +} diff --git a/smallboot-api/common/base/src/main/java/com/zhengqing/common/base/enums/ApiResultCodeEnum.java b/smallboot-api/common/base/src/main/java/com/zhengqing/common/base/enums/ApiResultCodeEnum.java new file mode 100644 index 0000000..0834b58 --- /dev/null +++ b/smallboot-api/common/base/src/main/java/com/zhengqing/common/base/enums/ApiResultCodeEnum.java @@ -0,0 +1,50 @@ +package com.zhengqing.common.base.enums; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + *

+ * 响应码枚举 - 可参考HTTP状态码的语义 + *

+ * + * @author zhengqingya + * @description + * @date 2019/8/22 11:09 + */ +@Getter +@AllArgsConstructor +public enum ApiResultCodeEnum { + + // 成功 + SUCCESS(200, "SUCCESS"), + // 失败 + FAILURE(400, "FAILURE"), + // 参数校验失败 + PARAM_VALID_ERROR(400, "参数校验失败"), + // 未登录 + UN_LOGIN(401, "请求未授权"), + // 未通过认证 + USER_UNAUTHORIZED(402, "用户名或密码不正确"), + // 用户不存在 + USER_NOT_EXIST(402, "用户不存在"), + // 未认证(签名错误、token错误) + UNAUTHORIZED(403, "未认证"), + // 客户端认证失败 + CLIENT_AUTHENTICATION_FAILED(405, "客户端认证失败"), + // 接口不存在 + NOT_FOUND(404, "接口不存在"), + // token过期 + TOKEN_EXPIRED(-1, "token过期"), + // token丢失 + TOKEN_NOT_EXIST(-1, "token丢失"), + // token已被禁止访问 + TOKEN_ACCESS_FORBIDDEN(-1, "token已被禁止访问"), + // 服务器内部错误 + INTERNAL_SERVER_ERROR(500, "服务器内部错误"), + ; + + private final int code; + private final String desc; + +} diff --git a/smallboot-api/common/base/src/main/java/com/zhengqing/common/base/enums/AuthSourceEnum.java b/smallboot-api/common/base/src/main/java/com/zhengqing/common/base/enums/AuthSourceEnum.java new file mode 100644 index 0000000..bfce59c --- /dev/null +++ b/smallboot-api/common/base/src/main/java/com/zhengqing/common/base/enums/AuthSourceEnum.java @@ -0,0 +1,23 @@ +package com.zhengqing.common.base.enums; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + *

认证来源枚举

+ * + * @author zhengqingya + * @description + * @date 2022/6/10 15:18 + */ +@Getter +@AllArgsConstructor +public enum AuthSourceEnum implements IBaseEnum { + + B("B", "B端系统用户"), + C("C", "C端用户"); + + private String value; + private String desc; + +} diff --git a/smallboot-api/common/base/src/main/java/com/zhengqing/common/base/enums/IBaseEnum.java b/smallboot-api/common/base/src/main/java/com/zhengqing/common/base/enums/IBaseEnum.java new file mode 100644 index 0000000..6f271a9 --- /dev/null +++ b/smallboot-api/common/base/src/main/java/com/zhengqing/common/base/enums/IBaseEnum.java @@ -0,0 +1,90 @@ +package com.zhengqing.common.base.enums; + + +import cn.hutool.core.util.ObjectUtil; + +import java.util.EnumSet; +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; + +/** + *

通用枚举接口

+ * + * @author zhengqingya + * @description 符号 T、E、?代表的意思: + * T 表示一种特定的类型 + * E 也是一种类型的意思,只不过通常代表集合中的元素 + * ? 这是一种无限的符号,代表任何类型都可以 + * @date 2019/8/22 11:00 + */ +public interface IBaseEnum { + + /** + * 值 + */ + T getValue(); + + /** + * 描述 + */ + String getDesc(); + + /** + * 获取所有枚举值 + */ + static > List list(Class clazz) { + EnumSet allEnums = EnumSet.allOf(clazz); + return allEnums.stream().collect(Collectors.toList()); + } + + /** + * 根据值获取枚举 + */ + static & IBaseEnum> E getEnumByValue(Object value, Class clazz) { + Objects.requireNonNull(value); + EnumSet allEnums = EnumSet.allOf(clazz); + E matchEnum = allEnums.stream() + .filter(e -> ObjectUtil.equal(e.getValue(), value)) + .findFirst() + .orElse(null); + return matchEnum; + } + + /** + * 根据值获取描述 + */ + static & IBaseEnum> Object getValueByDesc(String desc, Class clazz) { + Objects.requireNonNull(desc); + EnumSet allEnums = EnumSet.allOf(clazz); + String finalDesc = desc; + E matchEnum = allEnums.stream() + .filter(e -> ObjectUtil.equal(e.getDesc(), finalDesc)) + .findFirst() + .orElse(null); + + Object value = null; + if (matchEnum != null) { + value = matchEnum.getValue(); + } + return value; + } + + /** + * 根据描述获取值 + */ + static & IBaseEnum> String getDescByValue(Object value, Class clazz) { + Objects.requireNonNull(value); + EnumSet allEnums = EnumSet.allOf(clazz); + E matchEnum = allEnums.stream() + .filter(e -> ObjectUtil.equal(e.getValue(), value)) + .findFirst() + .orElse(null); + String desc = null; + if (matchEnum != null) { + desc = matchEnum.getDesc(); + } + return desc; + } + +} diff --git a/smallboot-api/common/base/src/main/java/com/zhengqing/common/base/enums/YesNoEnum.java b/smallboot-api/common/base/src/main/java/com/zhengqing/common/base/enums/YesNoEnum.java new file mode 100644 index 0000000..05928d8 --- /dev/null +++ b/smallboot-api/common/base/src/main/java/com/zhengqing/common/base/enums/YesNoEnum.java @@ -0,0 +1,60 @@ +package com.zhengqing.common.base.enums; + +import com.google.common.collect.Lists; +import com.zhengqing.common.base.exception.MyException; +import lombok.AllArgsConstructor; +import lombok.Getter; + +import java.util.Arrays; +import java.util.List; + +/** + *

+ * 是/否 + *

+ * + * @author zhengqingya + * @description + * @date 2020/4/12 0:01 + */ +@Getter +@AllArgsConstructor +public enum YesNoEnum implements IBaseEnum { + + /** + * 是 + */ + YES(1, "是"), + /** + * 否 + */ + NO(0, "否"); + + /** + * 类型 + */ + private final Integer value; + /** + * 描述 + */ + private final String desc; + + private static final List LIST = Lists.newArrayList(); + + static { + LIST.addAll(Arrays.asList(YesNoEnum.values())); + } + + /** + * 根据指定类型查找相应枚举类 + */ + public static YesNoEnum getEnum(Integer value) { + for (YesNoEnum itemEnum : LIST) { + if (itemEnum.getValue().equals(value)) { + return itemEnum; + } + } + throw new MyException("未找到指定类型枚举数据!"); + } + +} diff --git a/smallboot-api/common/base/src/main/java/com/zhengqing/common/base/exception/MyAssert.java b/smallboot-api/common/base/src/main/java/com/zhengqing/common/base/exception/MyAssert.java new file mode 100644 index 0000000..ebdd7b7 --- /dev/null +++ b/smallboot-api/common/base/src/main/java/com/zhengqing/common/base/exception/MyAssert.java @@ -0,0 +1,79 @@ +package com.zhengqing.common.base.exception; + +import org.apache.commons.lang3.StringUtils; +import org.springframework.util.Assert; +import org.springframework.util.CollectionUtils; + +import java.util.Collection; +import java.util.function.Supplier; + +/** + *

+ * 业务断言 + *

+ * + * @author zhengqingya + * @description + * @date 2020/5/21 20:27 + */ +public final class MyAssert extends Assert { + + /** + * 断言对象不为空 + * + * @param object 对象 + * @param msg 不满足断言的异常信息 + */ + public static void notNull(Object object, String msg) { + state(object != null, msg); + } + + public static void notNull(Object object, Supplier supplier) { + state(object != null, supplier); + } + + /** + * 断言字符串不为空 + * + * @param str 字符串 + * @param msg 不满足断言的异常信息 + */ + public static void notEmpty(String str, String msg) { + state(!StringUtils.isEmpty(str), msg); + } + + /** + * 断言集合不为空 + * + * @param collection 集合 + * @param msg 不满足断言的异常信息 + */ + public static void notEmpty(Collection collection, String msg) { + state(!CollectionUtils.isEmpty(collection), msg); + } + + /** + * 断言一个boolean表达式 + * + * @param expression boolean表达式 + * @param message 不满足断言的异常信息 + */ + public static void state(boolean expression, String message) { + if (!expression) { + throw new MyException(message); + } + } + + /** + * 断言一个boolean表达式,用于需要大量拼接字符串以及一些其他操作等 + * + * @param expression boolean表达式 + * @param supplier msg生产者 + */ + public static void state(boolean expression, Supplier supplier) { + if (!expression) { + throw new MyException(supplier.get()); + } + } + +} diff --git a/smallboot-api/common/base/src/main/java/com/zhengqing/common/base/exception/MyException.java b/smallboot-api/common/base/src/main/java/com/zhengqing/common/base/exception/MyException.java new file mode 100644 index 0000000..8f094d5 --- /dev/null +++ b/smallboot-api/common/base/src/main/java/com/zhengqing/common/base/exception/MyException.java @@ -0,0 +1,52 @@ +package com.zhengqing.common.base.exception; + +import com.zhengqing.common.base.enums.ApiResultCodeEnum; + +/** + *

+ * 自定义异常类 + *

+ * + * @author zhengqingya + * @description + * @date 2019/8/26 15:11 + */ +public class MyException extends RuntimeException { + + /** + * 异常状态码 + */ + private Integer code; + + public MyException(Throwable cause) { + super(cause); + } + + public MyException(String message) { + super(message); + } + + public MyException(Integer code, String message) { + super(message); + this.code = code; + } + + public MyException(String message, Integer code) { + super(message); + this.code = code; + } + + public MyException(ApiResultCodeEnum response) { + super(response.getDesc()); + this.code = response.getCode(); + } + + public MyException(String message, Throwable cause) { + super(message, cause); + } + + public Integer getCode() { + return code; + } + +} diff --git a/smallboot-api/common/base/src/main/java/com/zhengqing/common/base/exception/ParameterException.java b/smallboot-api/common/base/src/main/java/com/zhengqing/common/base/exception/ParameterException.java new file mode 100644 index 0000000..f432b2b --- /dev/null +++ b/smallboot-api/common/base/src/main/java/com/zhengqing/common/base/exception/ParameterException.java @@ -0,0 +1,22 @@ +package com.zhengqing.common.base.exception; + +/** + *

+ * 参数异常 + *

+ * + * @author zhengqingya + * @description + * @date 2020/8/1 18:07 + */ +public class ParameterException extends MyException { + + public ParameterException(String message) { + super(message); + } + + public ParameterException(String message, Throwable e) { + super(message, e); + } + +} diff --git a/smallboot-api/common/base/src/main/java/com/zhengqing/common/base/model/bo/BaseBO.java b/smallboot-api/common/base/src/main/java/com/zhengqing/common/base/model/bo/BaseBO.java new file mode 100644 index 0000000..577b012 --- /dev/null +++ b/smallboot-api/common/base/src/main/java/com/zhengqing/common/base/model/bo/BaseBO.java @@ -0,0 +1,34 @@ +package com.zhengqing.common.base.model.bo; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.SuperBuilder; + +import java.io.Serializable; + +/** + *

基类参数

+ * + * @author zhengqingya + * @description + * @date 2021/8/17 15:38 + */ +@Data +@SuperBuilder +@NoArgsConstructor +@AllArgsConstructor +@ApiModel("基类参数") +public class BaseBO implements Serializable { + + private static final long serialVersionUID = 1L; + + @JsonIgnore // jackson +// @JSONField(serialize = false, deserialize = false) // fastjson + @ApiModelProperty(value = "隐藏字段-解决子类lombok部分注解(ex:构造器@NoArgsConstructor、@AllArgsConstructor)无法使用问题", hidden = true) + private String xxx; + +} diff --git a/smallboot-api/common/base/src/main/java/com/zhengqing/common/base/model/bo/JwtCustomUserBO.java b/smallboot-api/common/base/src/main/java/com/zhengqing/common/base/model/bo/JwtCustomUserBO.java new file mode 100644 index 0000000..20078f0 --- /dev/null +++ b/smallboot-api/common/base/src/main/java/com/zhengqing/common/base/model/bo/JwtCustomUserBO.java @@ -0,0 +1,48 @@ +package com.zhengqing.common.base.model.bo; + +import com.zhengqing.common.base.enums.AuthSourceEnum; +import io.swagger.annotations.ApiModelProperty; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.SuperBuilder; + +/** + *

jwt中自定义的用户信息

+ * + * @author zhengqingya + * @description {@link com.zhengqing.auth.security.config.CustomAdditionalInformation} + * @date 2022/6/15 10:34 + */ +@Data +@SuperBuilder +@NoArgsConstructor +@AllArgsConstructor +public class JwtCustomUserBO { + /** + * {@link AuthSourceEnum} + */ + @ApiModelProperty(value = "认证来源") + private String authSource; + + @ApiModelProperty(value = "jwt") + private String token; + + @ApiModelProperty(value = "jwt的唯一身份标识") + private String jti; + + @ApiModelProperty(value = "过期时间(2022-06-01 23:06:53)") + private String expireTime; + + @ApiModelProperty(value = "租户ID") + private String tenantId; + + @ApiModelProperty(value = "B端系统用户ID") + private String sysUserId; + + @ApiModelProperty(value = "C端用户ID") + private String umsUserId; + + @ApiModelProperty(value = "用户名") + private String username; +} diff --git a/smallboot-api/common/base/src/main/java/com/zhengqing/common/base/model/dto/BaseDTO.java b/smallboot-api/common/base/src/main/java/com/zhengqing/common/base/model/dto/BaseDTO.java new file mode 100644 index 0000000..77b0855 --- /dev/null +++ b/smallboot-api/common/base/src/main/java/com/zhengqing/common/base/model/dto/BaseDTO.java @@ -0,0 +1,44 @@ +package com.zhengqing.common.base.model.dto; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.SuperBuilder; + +import java.io.Serializable; + +/** + *

+ * 基类查询参数 + *

+ * + * @author zhengqingya + * @description + * @date 2019/9/13 0013 1:57 + */ +@Data +@SuperBuilder +@NoArgsConstructor +@AllArgsConstructor +@ApiModel("基类查询参数") +public class BaseDTO implements Serializable { + + private static final long serialVersionUID = 1L; + + @JsonIgnore // 字段忽略 +// @JsonProperty("userId") // 字段别名 + @ApiModelProperty(value = "当前用户ID", hidden = true) + private Integer currentUserId; + + @JsonIgnore + @ApiModelProperty(value = "当前用户名称", hidden = true) + private String currentUsername; + +// @JsonIgnore +// @ApiModelProperty(value = "令牌", hidden = true) +// private String token; + +} diff --git a/smallboot-api/common/base/src/main/java/com/zhengqing/common/base/model/dto/BasePageDTO.java b/smallboot-api/common/base/src/main/java/com/zhengqing/common/base/model/dto/BasePageDTO.java new file mode 100644 index 0000000..111b01f --- /dev/null +++ b/smallboot-api/common/base/src/main/java/com/zhengqing/common/base/model/dto/BasePageDTO.java @@ -0,0 +1,34 @@ +package com.zhengqing.common.base.model.dto; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import lombok.experimental.SuperBuilder; + +/** + *

+ * 基础分页检索参数 + *

+ * + * @author zhengqingya + * @description + * @date 2019/9/13 0013 1:57 + */ +@Data +@SuperBuilder +@NoArgsConstructor +@AllArgsConstructor +@EqualsAndHashCode(callSuper = true) +@ApiModel("基础分页检索参数") +public class BasePageDTO extends BaseDTO { + + @ApiModelProperty(value = "当前页", required = true, position = 0, example = "1") + private Integer pageNum = 1; + + @ApiModelProperty(value = "每页显示数量", required = true, position = 1, example = "10") + private Integer pageSize = 10; + +} diff --git a/smallboot-api/common/base/src/main/java/com/zhengqing/common/base/model/vo/ApiResult.java b/smallboot-api/common/base/src/main/java/com/zhengqing/common/base/model/vo/ApiResult.java new file mode 100644 index 0000000..dd9633a --- /dev/null +++ b/smallboot-api/common/base/src/main/java/com/zhengqing/common/base/model/vo/ApiResult.java @@ -0,0 +1,155 @@ +package com.zhengqing.common.base.model.vo; + +import com.zhengqing.common.base.enums.ApiResultCodeEnum; +import com.zhengqing.common.base.exception.MyException; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +/** + *

+ * API返回参数 + *

+ * + * @author zhengqingya + * @description + * @date 2019/7/20 11:09 + */ +@Setter +@Getter +@AllArgsConstructor +@NoArgsConstructor +@ApiModel(value = "API返回参数") +public class ApiResult { + + @ApiModelProperty(value = "响应码", required = true) + private Integer code; + + @ApiModelProperty(value = "消息内容", required = false) + private String msg; + + @ApiModelProperty(value = "响应数据", required = false) + private T data; + + public ApiResult(T data) { + this.code = ApiResultCodeEnum.SUCCESS.getCode(); + this.msg = "OK"; + this.data = data; + } + + public ApiResult(Integer code, String msg) { + this.code = code; + this.msg = msg; + } + + public static ApiResult ok() { + return new ApiResult(ApiResultCodeEnum.SUCCESS.getCode(), "OK", null); + } + + public static ApiResult ok(E o) { + // 支持Controller层直接返回ApiResult + ApiResult result = new ApiResult(ApiResultCodeEnum.SUCCESS); + if (o instanceof ApiResult) { + result = ((ApiResult) o); + } else { + // 其他obj封装进data,保持返回格式统一 + result.setData(o); + } + return result; + } + + public static ApiResult ok(String data) { + return new ApiResult(ApiResultCodeEnum.SUCCESS.getCode(), "OK", data); + } + + public static ApiResult ok(Object data, String msg) { + return new ApiResult(ApiResultCodeEnum.SUCCESS.getCode(), msg, data); + } + + /** + * 自定义返回码 + */ + public static ApiResult ok(Integer code, String msg) { + return new ApiResult(code, msg); + } + + /** + * 自定义 + * + * @param code 验证码 + * @param msg 返回消息内容 + * @param data 返回数据 + * @return 响应体 + */ + public static ApiResult ok(Integer code, String msg, Object data) { + return new ApiResult(code, msg, data); + } + + /** + * 过期 + * + * @param msg 消息内容 + * @return 响应体 + */ + public static ApiResult expired(String msg) { + return new ApiResult(ApiResultCodeEnum.TOKEN_EXPIRED.getCode(), msg, null); + } + + public static ApiResult fail(String msg) { + return new ApiResult(ApiResultCodeEnum.FAILURE.getCode(), msg, null); + } + + public static ApiResult busy() { + return new ApiResult(ApiResultCodeEnum.FAILURE.getCode(), "服务繁忙", null); + } + + /*** + * 自定义错误返回码 + * + * @param code 验证码 + * @param msg 消息内容 + * @return 响应体 + */ + public static ApiResult fail(Integer code, String msg) { + return new ApiResult(code, msg, null); + } + + /** + * 是否成功 + * + * @return true->成功,false->失败 + * @author zhengqingya + * @date 2022/6/1 12:45 + */ + public boolean checkIsSuccess() { + return ApiResultCodeEnum.SUCCESS.getCode() == this.code; + } + + /** + * 是否失败 + * + * @return true->失败,false->成功 + * @author zhengqingya + * @date 2022/6/1 12:45 + */ + public boolean checkIsFail() { + return ApiResultCodeEnum.SUCCESS.getCode() != this.code; + } + + /** + * rpc校验结果是否异常 + * + * @return void + * @author zhengqingya + * @date 2021/10/14 16:21 + */ + public void checkForRpc() { + if (this.checkIsFail()) { + throw new MyException(this.msg); + } + } + +} diff --git a/smallboot-api/common/base/src/main/java/com/zhengqing/common/base/model/vo/BaseVO.java b/smallboot-api/common/base/src/main/java/com/zhengqing/common/base/model/vo/BaseVO.java new file mode 100644 index 0000000..0171aab --- /dev/null +++ b/smallboot-api/common/base/src/main/java/com/zhengqing/common/base/model/vo/BaseVO.java @@ -0,0 +1,33 @@ +package com.zhengqing.common.base.model.vo; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.SuperBuilder; + +import java.io.Serializable; + +/** + *

基类响应参数

+ * + * @author zhengqingya + * @description + * @date 2021/8/18 16:14 + */ +@Data +@SuperBuilder +@NoArgsConstructor +@AllArgsConstructor +@ApiModel("基类响应参数") +public class BaseVO implements Serializable { + + private static final long serialVersionUID = 1L; + + @JsonIgnore + @ApiModelProperty(value = "隐藏字段-解决子类lombok部分注解(ex:构造器@NoArgsConstructor、@AllArgsConstructor)无法使用问题", hidden = true) + private String xxx; + +} diff --git a/smallboot-api/common/base/src/main/java/com/zhengqing/common/base/model/vo/PageVO.java b/smallboot-api/common/base/src/main/java/com/zhengqing/common/base/model/vo/PageVO.java new file mode 100644 index 0000000..f8f591f --- /dev/null +++ b/smallboot-api/common/base/src/main/java/com/zhengqing/common/base/model/vo/PageVO.java @@ -0,0 +1,77 @@ +package com.zhengqing.common.base.model.vo; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.JsonSerializer; +import com.fasterxml.jackson.databind.SerializerProvider; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import io.swagger.annotations.ApiModel; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.SuperBuilder; + +import java.io.IOException; +import java.io.Serializable; +import java.util.List; + +/** + *

分页响应参数

+ * + * @author zhengqingya + * @description {@link com.baomidou.mybatisplus.core.metadata.IPage } + * @date 2021/8/18 16:14 + */ +@Data +@SuperBuilder +@NoArgsConstructor +@AllArgsConstructor +@ApiModel("基类响应参数") +public class PageVO implements Serializable { + + private static final long serialVersionUID = 1L; + + /** + * 当前页 + */ + @JsonSerialize(using = ToLongSerializer.class) + Long current; + /** + * 每页显示条数 + */ + @JsonSerialize(using = ToLongSerializer.class) + Long size; + /** + * 当前分页总页数 + */ + @JsonSerialize(using = ToLongSerializer.class) + Long pages; + /** + * 当前满足条件总行数 + */ + @JsonSerialize(using = ToLongSerializer.class) + Long total; + /** + * 分页记录列表 + */ + List records; + +} + +/** + *

jackson 转Long类型

+ * + * @author zhengqingya + * @description 解决全局将Long类型转换为String类型后,部分类又需要Long还是原本Long类型问题 + * 使用方式:添加注解`@JsonSerialize(using = ToLongSerializer.class)` + * {@link com.zhengqing.common.web.config.jackson.ToLongSerializer } + * 由于此为base包,不额外引用其它包中的东西,这里单独处理一下 + * @date 2022/8/5 17:22 + */ +class ToLongSerializer extends JsonSerializer { + + @Override + public void serialize(Long value, JsonGenerator gen, SerializerProvider serializers) throws IOException { + gen.writeNumber(value); + } + +} \ No newline at end of file diff --git a/smallboot-api/common/base/src/main/java/com/zhengqing/common/base/util/ApplicationContextUtil.java b/smallboot-api/common/base/src/main/java/com/zhengqing/common/base/util/ApplicationContextUtil.java new file mode 100644 index 0000000..9a84547 --- /dev/null +++ b/smallboot-api/common/base/src/main/java/com/zhengqing/common/base/util/ApplicationContextUtil.java @@ -0,0 +1,35 @@ +package com.zhengqing.common.base.util; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.BeansException; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.stereotype.Component; + +/** + *

+ * 全局上下文工具类配置 + *

+ * + * @author zhengqingya + * @description 解决静态方法中mapper的调用 + * @date 2019/10/17 14:44 + */ +@Slf4j +@Component +public class ApplicationContextUtil implements ApplicationContextAware { + private static ApplicationContext applicationContext; + + @Override + public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { + ApplicationContextUtil.applicationContext = applicationContext; + } + + public static ApplicationContext getApplicationContext() { + return applicationContext; + } + + public static Object getBean(String beanName) { + return applicationContext.getBean(beanName); + } +} diff --git a/smallboot-api/common/base/src/main/java/com/zhengqing/common/base/util/AutoUpgradeUtil.java b/smallboot-api/common/base/src/main/java/com/zhengqing/common/base/util/AutoUpgradeUtil.java new file mode 100644 index 0000000..4c82e9e --- /dev/null +++ b/smallboot-api/common/base/src/main/java/com/zhengqing/common/base/util/AutoUpgradeUtil.java @@ -0,0 +1,40 @@ +package com.zhengqing.common.base.util; + +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; + +/** + *

+ * 自增编号工具类 + *

+ * + * @author zhengqingya + * @description + * @date 2020/10/13$ 14:08$ + */ +@Slf4j +public class AutoUpgradeUtil { + + /** + * 自增编号 ex:0001 -> 0002 + * + * @param code 编号 + * @return 自增后的编号 + * @author zhengqingya + * @date 2020/10/13 14:13 + */ + public static String autoUpgrade(String code) { + String codeNew = "0001"; + if (StringUtils.isNotBlank(code)) { + code = code.trim(); + codeNew = String.format("%0" + code.length() + "d", Long.valueOf(code) + 1); + } + log.debug("《自增编号》 旧数据:【{}】 新数据:【{}】", code, codeNew); + return codeNew; + } + + public static void main(String[] args) { + autoUpgrade("9999"); + } + +} diff --git a/smallboot-api/common/base/src/main/java/com/zhengqing/common/base/util/AutoUpgradeVersionUtil.java b/smallboot-api/common/base/src/main/java/com/zhengqing/common/base/util/AutoUpgradeVersionUtil.java new file mode 100644 index 0000000..772627d --- /dev/null +++ b/smallboot-api/common/base/src/main/java/com/zhengqing/common/base/util/AutoUpgradeVersionUtil.java @@ -0,0 +1,56 @@ +package com.zhengqing.common.base.util; + +import org.apache.commons.lang3.StringUtils; + +import java.util.Arrays; + +/** + *

+ * 自增版本号工具类 + *

+ * + * @author zhengqingya + * @description + * @date 2020/10/13 14:08 + */ +public class AutoUpgradeVersionUtil { + + /** + * 自动自增版本号 ex:0.0.1 -> 0.0.2 + * + * @param version 版本号 + * @return 自增后的版本号 + * @author zhengqingya + * @date 2020/10/13 14:13 + */ + public static String autoUpgradeVersion(String version) { + if (StringUtils.isBlank(version)) { + version = "0.0.1"; + } + // 将版本号拆解成整数数组 + String[] versionArray = version.split("\\."); + int[] versionList = Arrays.stream(versionArray).mapToInt(Integer::valueOf).toArray(); + + // 递归调用 + autoUpgradeVersion(versionList, versionList.length - 1); + + // 数组转字符串 + version = StringUtils.join(versionList, '.'); + return version; + } + + private static void autoUpgradeVersion(int[] ints, int index) { + if (index == 0) { + ints[0] = ints[0] + 1; + } else { + int value = ints[index] + 1; + if (value < 10) { + ints[index] = value; + } else { + ints[index] = 0; + autoUpgradeVersion(ints, index - 1); + } + } + } + +} diff --git a/smallboot-api/common/base/src/main/java/com/zhengqing/common/base/util/BigDecimalUtil.java b/smallboot-api/common/base/src/main/java/com/zhengqing/common/base/util/BigDecimalUtil.java new file mode 100644 index 0000000..df134d4 --- /dev/null +++ b/smallboot-api/common/base/src/main/java/com/zhengqing/common/base/util/BigDecimalUtil.java @@ -0,0 +1,282 @@ +package com.zhengqing.common.base.util; + +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; + +import java.math.BigDecimal; +import java.text.NumberFormat; + +/** + *

BigDecimal工具类

+ * + * @author zhengqingya + * @description + * @date 2021/8/24 10:45 + */ +@Slf4j +public class BigDecimalUtil { + + /** + * 默认除法运算精度 + */ + private static final int DEF_DIV_SCALE = 2; + + /** + * 建立货币格式化引用 + */ + private static final NumberFormat CURRENCY = NumberFormat.getCurrencyInstance(); + + /** + * 建立百分比格式化引用 + */ + private static final NumberFormat PERCENT = NumberFormat.getPercentInstance(); + + /** + * 加法 + * + * @param num1 数1 + * @param num2 数2 + * @return 计算结果 + * @author zhengqingya + * @date 2021/8/24 10:49 + */ + public static BigDecimal add(BigDecimal num1, BigDecimal num2) { + return num1.add(num2); + } + + + /** + * 加法(默认四舍五入,根据scale保留小数位数) + * + * @param num1 数1 + * @param num2 数2 + * @param scale 保留小数位数 + * @return 计算结果 + * @author zhengqingya + * @date 2021/8/24 10:52 + */ + public static BigDecimal add(BigDecimal num1, BigDecimal num2, int scale) { + return num1.add(num2).setScale(scale, BigDecimal.ROUND_HALF_UP); + } + + /** + * 减法 + * + * @param num1 数1 + * @param num2 数2 + * @return 计算结果 + * @author zhengqingya + * @date 2021/8/24 10:54 + */ + public static BigDecimal sub(BigDecimal num1, BigDecimal num2) { + return num1.subtract(num2); + } + + /** + * 减法(默认四舍五入,根据scale保留小数位数) + * + * @param num1 数1 + * @param num2 数2 + * @return 计算结果 + * @author zhengqingya + * @date 2021/8/24 10:54 + */ + public static BigDecimal sub(BigDecimal num1, BigDecimal num2, int scale) { + return num1.subtract(num2).setScale(scale, BigDecimal.ROUND_HALF_UP); + } + + /** + * 乘法 + * + * @param num1 数1 + * @param num2 数2 + * @return 计算结果 + * @author zhengqingya + * @date 2021/8/24 10:54 + */ + public static BigDecimal multiply(BigDecimal num1, BigDecimal num2) { + return num1.multiply(num2); + } + + /** + * 乘法(默认四舍五入,根据scale保留小数位数) + * + * @param num1 数1 + * @param num2 数2 + * @return 计算结果 + * @author zhengqingya + * @date 2021/8/24 10:54 + */ + public static BigDecimal multiply(BigDecimal num1, BigDecimal num2, int scale) { + return num1.multiply(num2).setScale(scale, BigDecimal.ROUND_HALF_UP); + } + + /** + * 除法(除不尽会抛异常) + * + * @param num1 数1 + * @param num2 数2 + * @return 计算结果 = 数1 / 数2 + * @author zhengqingya + * @date 2021/8/24 10:54 + */ + public static BigDecimal divide(BigDecimal num1, BigDecimal num2) { + return num1.divide(num2, DEF_DIV_SCALE); + } + + /** + * 除法(默认四舍五入保留两位小数) + * + * @param num1 数1 + * @param num2 数2 + * @return 计算结果 = 数1 / 数2 + * @author zhengqingya + * @date 2021/8/24 10:54 + */ + public static BigDecimal divide(BigDecimal num1, BigDecimal num2, int scale) { + return num1.divide(num2, scale, BigDecimal.ROUND_HALF_UP); + } + + /** + * 比较BigDecimal + * + * @param num1 数1 + * @param num2 数2 + * @return 相等返回0, num1>num2返回1, num1 emoji表情符号处理工具类

+ * 主要功能: + * 1. 将带有emoji的字符串,格式化成unicode字符串 + * 2. 将可见unicode字符反解成emoji字符 + * 3. 过滤emoji字符 + * + *

+ * 相关识知点: + * + * Unicode平面, + * BMP的字符可以使用charAt(index)来处理,计数可以使用length() + * 其它平面字符,需要用codePointAt(index),计数可以使用codePointCount(0,str.lenght()) + *

+ * Unicode可以逻辑分为17平面(Plane),每个平面拥有65536( = 216)个代码点,虽然目前只有少数平面被使 + * 用。 + * 平面0 (0000–FFFF): 基本多文种平面(Basic Multilingual Plane, BMP). + * 平面1 (10000–1FFFF): 多文种补充平面(Supplementary Multilingual Plane, SMP). + * 平面2 (20000–2FFFF): 表意文字补充平面(Supplementary Ideographic Plane, SIP). + * 平面3 (30000–3FFFF): 表意文字第三平面(Tertiary Ideographic Plane, TIP). + * 平面4 to 13 (40000–DFFFF)尚未使用 + * 平面14 (E0000–EFFFF): 特别用途补充平面(Supplementary Special-purpose Plane, SSP) + * 平面15 (F0000–FFFFF)保留作为私人使用区(Private Use Area, PUA) + * 平面16 (100000–10FFFF),保留作为私人使用区(Private Use Area, PUA) + *

+ * 参考: + * 维基百科: http://en.wikipedia.org/wiki/Emoji + * GITHUB: http://punchdrunker.github.io/iOSEmoji/ + * 杂项象形符号:1F300-1F5FF + * 表情符号:1F600-1F64F + * 交通和地图符号:1F680-1F6FF + * 杂项符号:2600-26FF + * 符号字体:2700-27BF + * 国旗:1F100-1F1FF + * 箭头:2B00-2BFF 2900-297F + * 各种技术符号:2300-23FF + * 字母符号: 2100–214F + * 中文符号: 303D 3200–32FF 2049 203C + * Private Use Area:E000-F8FF; + * High Surrogates D800..DB7F; + * High Private Use Surrogates DB80..DBFF + * Low Surrogates DC00..DFFF D800-DFFF E000-F8FF + * 标点符号:2000-200F 2028-202F 205F 2065-206F + * 变异选择器:IOS独有 FE00-FE0F + * + * @author zhengqingya + * @description + * @date 2021/12/26 4:08 下午 + */ +public class EmojiCharacterUtil { + + /** + * 转义时标识 + */ + private static final char unicode_separator = '&'; + private static final char unicode_prefix = 'u'; + private static final char separator = ':'; + + + /** + * 是否含有emoji表情符号 + * + * @param codePoint 字符 + * @return true->是 false->否 + */ + private static boolean isEmojiCharacter(int codePoint) { + return (codePoint >= 0x2600 && codePoint <= 0x27BF) // 杂项符号与符号字体 + || codePoint == 0x303D + || codePoint == 0x2049 + || codePoint == 0x203C + || (codePoint >= 0x2000 && codePoint <= 0x200F)// + || (codePoint >= 0x2028 && codePoint <= 0x202F)// + || codePoint == 0x205F // + || (codePoint >= 0x2065 && codePoint <= 0x206F)// + /* 标点符号占用区域 */ + || (codePoint >= 0x2100 && codePoint <= 0x214F)// 字母符号 + || (codePoint >= 0x2300 && codePoint <= 0x23FF)// 各种技术符号 + || (codePoint >= 0x2B00 && codePoint <= 0x2BFF)// 箭头A + || (codePoint >= 0x2900 && codePoint <= 0x297F)// 箭头B + || (codePoint >= 0x3200 && codePoint <= 0x32FF)// 中文符号 + || (codePoint >= 0xD800 && codePoint <= 0xDFFF)// 高低位替代符保留区域 + || (codePoint >= 0xE000 && codePoint <= 0xF8FF)// 私有保留区域 + || (codePoint >= 0xFE00 && codePoint <= 0xFE0F)// 变异选择器 + || codePoint >= 0x10000; // Plane在第二平面以上的,char都不可以存,全部都转 + } + + /** + * 将带有emoji字符的字符串转换成可见字符标识 + */ + public static String escape(String src) { + if (StringUtils.isBlank(src)) { + return src; + } + int cpCount = src.codePointCount(0, src.length()); + int firCodeIndex = src.offsetByCodePoints(0, 0); + int lstCodeIndex = src.offsetByCodePoints(0, cpCount - 1); + StringBuilder sb = new StringBuilder(src.length()); + for (int index = firCodeIndex; index <= lstCodeIndex; index++) { + int codepoint = src.codePointAt(index); + if (isEmojiCharacter(codepoint)) { + String hash = Integer.toHexString(codepoint); + sb.append(unicode_separator).append(hash.length()) + .append(unicode_prefix).append(separator).append(hash); + // hash 长度,4位1个字节 + index += (hash.length() - 1) / 4; + } else { + sb.append((char) codepoint); + } + } + return sb.toString(); + } + + /** + * 解析可见字符标识字符串 + */ + public static String reverse(String src) { + // 查找对应编码的标识位 + if (StringUtils.isBlank(src)) { + return src; + } + StringBuilder sb = new StringBuilder(src.length()); + char[] sourceChar = src.toCharArray(); + int index = 0; + while (index < sourceChar.length) { + if (sourceChar[index] == unicode_separator) { + if (index + 6 >= sourceChar.length) { + sb.append(sourceChar[index]); + index++; + continue; + } + // 自已的格式,与通用unicode格式不能互转 + if (sourceChar[index + 1] >= '4' && sourceChar[index + 1] <= '6' + && sourceChar[index + 2] == unicode_prefix + && sourceChar[index + 3] == separator) { + int length = Integer.parseInt(String.valueOf(sourceChar[index + 1])); + char[] hexchars = new char[length]; // 创建一个4至六位的数组,来存储uncode码的HEX值 + for (int j = 0; j < length; j++) { + char ch = sourceChar[index + 4 + j];// 4位识别码 + if ((ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f')) { + hexchars[j] = ch; + + } else { // 字符范围不对 + sb.append(sourceChar[index]); + index++; + break; + } + } + sb.append(Character.toChars(Integer.parseInt(new String(hexchars), 16))); + index += (4 + length);// 4位前缀+4-6位字符码 + } else if (sourceChar[index + 1] == unicode_prefix) { // 通用字符的反转 + // 因为第二平面之上的,已经采用了我们自己转码格式,所以这里是固定的长度4 + char[] hexchars = new char[4]; + for (int j = 0; j < 4; j++) { + char ch = sourceChar[index + 2 + j]; // 两位识别码要去掉 + if ((ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f')) { + hexchars[j] = ch; // 4位识别码 + } else { // 字符范围不对 + sb.append(sourceChar[index]); + index++; + break; + } + sb.append(Character.toChars(Integer.parseInt(String.valueOf(hexchars), 16))); + index += (2 + 4);// 2位前缀+4位字符码 + } + } else { + sb.append(sourceChar[index]); + index++; + continue; + } + } else { + sb.append(sourceChar[index]); + index++; + continue; + } + } + + return sb.toString(); + } + + /** + * 过滤emoji 或者 其他非文字类型的字符 + * + * @param srcStr 源文字 + * @return 过滤后的字符 + */ + public static String filter(String srcStr) { + if (StringUtils.isBlank(srcStr)) { + return Strings.EMPTY; + } + int cpCount = srcStr.codePointCount(0, srcStr.length()); + int firCodeIndex = srcStr.offsetByCodePoints(0, 0); + int lstCodeIndex = srcStr.offsetByCodePoints(0, cpCount - 1); + StringBuilder sb = new StringBuilder(srcStr.length()); + for (int index = firCodeIndex; index <= lstCodeIndex; ) { + int codepoint = srcStr.codePointAt(index); + if (!isEmojiCharacter(codepoint)) { +// System.err.println("codepoint:" + Integer.toHexString(codepoint)); + sb.append((char) codepoint); + } + index += ((Character.isSupplementaryCodePoint(codepoint)) ? 2 : 1); + + } + return sb.toString(); + } + + public static void main(String[] args) { + System.out.println("11 \uD83E\uDDF8 ☀️ ☀ 11"); + System.out.println(EmojiCharacterUtil.filter("11 \uD83E\uDDF8 🛫 🐻 🐸 \uD83C\uDFF3️ \uD83C\uDF8C ☀️ ☀ 11")); + System.out.println(EmojiCharacterUtil.filter("123 \ud83d\udfff")); + + System.out.println(EmojiCharacterUtil.filter(" ")); + System.out.println(EmojiCharacterUtil.filter("")); + + System.out.println(EmojiCharacterUtil.filter("👴🐏🐎❀ 汉字 123")); + + System.out.println(EmojiCharacterUtil.escape("👴🐏🐎❀ 汉字 123")); + System.out.println(EmojiCharacterUtil.reverse(EmojiCharacterUtil.escape("👴🐏🐎❀ 汉字 123"))); + } + +} diff --git a/smallboot-api/common/base/src/main/java/com/zhengqing/common/base/util/MyBeanUtil.java b/smallboot-api/common/base/src/main/java/com/zhengqing/common/base/util/MyBeanUtil.java new file mode 100644 index 0000000..7831318 --- /dev/null +++ b/smallboot-api/common/base/src/main/java/com/zhengqing/common/base/util/MyBeanUtil.java @@ -0,0 +1,195 @@ +package com.zhengqing.common.base.util; + +import cn.hutool.json.JSONUtil; +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; +import lombok.SneakyThrows; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.BeanUtils; +import org.springframework.beans.BeansException; +import org.springframework.util.CollectionUtils; + +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.List; +import java.util.Map; + +/** + *

+ * bean 工具类 + *

+ * + * @author zhengqingya
+ * @date 2019/11/6$ 18:34$
+ */ +@Slf4j +public class MyBeanUtil { + + /** + * 对象属性拷贝 + * + * @param source 源对象 + * @param target 目标对象 + * @return void + * @author zhengqingya + * @date 2020/9/5 23:22 + */ + public static void copyProperties(Object source, Object target) { + BeanUtils.copyProperties(source, target); + } + + /** + * 对象属性拷贝 : 将源对象的属性拷贝到目标对象 + * + * @param source 源对象 + * @param clz 目标对象class + * @return 对象数据 + */ + public static T copyProperties(Object source, Class clz) { + if (source == null) { + return null; + } + T target = BeanUtils.instantiate(clz); + try { + BeanUtils.copyProperties(source, target); + } catch (BeansException e) { + log.error("BeanUtil property copy failed :BeansException", e); + } catch (Exception e) { + log.error("BeanUtil property copy failed:Exception", e); + } + return target; + } + + /** + * 拷贝list + * + * @param inList 输入list + * @param outClz 输出目标对象class + * @return 返回集合 + */ + public static List copyList(List inList, Class outClz) { + List output = Lists.newArrayList(); + if (!CollectionUtils.isEmpty(inList)) { + for (E source : inList) { + T target = BeanUtils.instantiate(outClz); + BeanUtils.copyProperties(source, target); + output.add(target); + } + } + return output; + } + + /** + * 获取任意泛型类的指定属性值(暂只支持同时访问父类和子类属性,如果有多继承的话,还需要再修改) + * + * @param tList list对象数据 + * @param field 要获取指定字段属性值对应的字段名 + * @param isSuperfield true->父类 false->子类 + * @return 字段属性值 + */ + public static List getFieldList(List tList, String field, Boolean isSuperfield) { + if (StringUtils.isBlank(field)) { + return null; + } + + // 拼接方法 + field = + new StringBuffer("get").append(field.substring(0, 1).toUpperCase()).append(field.substring(1)).toString(); + List idList = Lists.newArrayList(); + Method method = null; + try { + if (isSuperfield) { + method = tList.get(0).getClass().getSuperclass().getMethod(field); + } else { + method = tList.get(0).getClass().getMethod(field); + } + } catch (NoSuchMethodException e) { + e.printStackTrace(); + } + try { + for (T t : tList) { + Object s = method.invoke(t); + idList.add(s); + } + } catch (NullPointerException | InvocationTargetException | IllegalAccessException e) { + e.printStackTrace(); + } + return idList; + } + + /** + * 获取任意泛型类的指定属性值(暂只支持同时访问父类和子类属性,如果有多继承的话,还需要再修改) + * + * @param tList list对象数据 + * @param field 要获取指定字段属性值对应的字段名 + * @return 字段属性值 + */ + public static List getFieldList(List> tList, String field) { + if (StringUtils.isBlank(field)) { + return null; + } + List idList = Lists.newArrayList(); + for (Map map : tList) { + idList.add(map.get(field)); + } + return idList; + } + + /** + * 将Map转换为对象 (注:暂无法转换含嵌套对象的map数据源) + * + * @param map 需转换map + * @param clz 目标对象class + * @return T + * @author zhengqingya + * @date 2020/11/27 18:39 + */ + public static T mapToObj(Map map, Class clz) { + return JSONUtil.toBean(JSONUtil.toJsonStr(map), clz); + } + + /** + * 对象 转 map (通过反射获取类里面的值和名称) + * + * @param obj 对象 + * @return map + * @author zhengqingya + * @date 2021/1/26 16:09 + */ + @SneakyThrows(Exception.class) + public static Map objToMapStr(Object obj) { + Map map = Maps.newHashMap(); + Class clazz = obj.getClass(); + for (Field field : clazz.getDeclaredFields()) { + field.setAccessible(true); + String fieldName = field.getName(); + Object value = field.get(obj); + map.put(fieldName, value == null ? "" : value.toString()); + } + return map; + } + + /** + * 对象 转 map (通过反射获取类里面的值和名称) + * + * @param obj 对象 + * @return map + * @author zhengqingya + * @date 2021/1/26 16:09 + */ + @SneakyThrows(Exception.class) + public static Map objToMap(Object obj) { + Map map = Maps.newHashMap(); + Class clazz = obj.getClass(); + for (Field field : clazz.getDeclaredFields()) { + field.setAccessible(true); + String fieldName = field.getName(); + Object value = field.get(obj); + map.put(fieldName, value == null ? "" : value.toString()); + } + return map; + } + +} diff --git a/smallboot-api/common/base/src/main/java/com/zhengqing/common/base/util/MyDateUtil.java b/smallboot-api/common/base/src/main/java/com/zhengqing/common/base/util/MyDateUtil.java new file mode 100644 index 0000000..a530470 --- /dev/null +++ b/smallboot-api/common/base/src/main/java/com/zhengqing/common/base/util/MyDateUtil.java @@ -0,0 +1,421 @@ +package com.zhengqing.common.base.util; + +import lombok.SneakyThrows; +import lombok.extern.slf4j.Slf4j; + +import java.lang.management.ManagementFactory; +import java.text.SimpleDateFormat; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.ZoneId; +import java.time.format.DateTimeFormatter; +import java.util.Calendar; +import java.util.Date; +import java.util.concurrent.TimeUnit; + +/** + *

+ * 时间工具类 + *

+ * + * @author zhengqingya + * @description + * @date 2019/8/18 12:57 + */ +@Slf4j +public class MyDateUtil { + + public static final String GMT = "GMT+8"; + public static final String DATE_TIME_FORMAT = "yyyy-MM-dd HH:mm:ss"; + public static final String DATE_TIME_MS_FORMAT = "yyyy-MM-dd HH:mm:ss.SSS"; + public static final String DATE_TIME_START_FORMAT = "yyyy-MM-dd 00:00:00"; + public static final String DATE_TIME_END_FORMAT = "yyyy-MM-dd 23:59:59"; + public static final String MINUTE_FORMAT = "yyyy-MM-dd HH:mm"; + public static final String HOUR_FORMAT = "yyyy-MM-dd HH"; + public static final String DATE_FORMAT = "yyyy-MM-dd"; + public static final String MONTH_FORMAT = "yyyy-MM"; + public static final String YEAR_FORMAT = "yyyy"; + public static final String MINUTE_ONLY_FORMAT = "mm"; + public static final String HOUR_ONLY_FORMAT = "HH"; + public static final String MINUTE_JAVA_AUTHOR_FORMAT = "yyyy/MM/dd HH:mm"; + public static final String DAY_FORMAT = "yyyy/MM/dd"; + + /** + * 获取当前时间的字符串格式时间 -- 秒 + * + * @return 字符串格式时间 + * @author zhengqingya + * @date 2020/8/22 13:07 + */ + public static String nowStr() { + return new SimpleDateFormat(DATE_TIME_FORMAT).format(new Date()); + } + + /** + * 获取当前时间的字符串格式时间 -- 毫秒 + * + * @return 字符串格式时间 + * @author zhengqingya + * @date 2020/8/22 13:07 + */ + public static String nowMsStr() { + return new SimpleDateFormat(DATE_TIME_MS_FORMAT).format(new Date()); + } + + /** + * 获取当前时间字符串的指定格式时间 + * + * @param format 时间格式 + * @return 字符串格式时间 + * @author zhengqingya + * @date 2020/8/22 13:07 + */ + public static String nowStr(String format) { + return new SimpleDateFormat(format).format(new Date()); + } + + /** + * Date转Str + * + * @param date 时间 + * @param format 时间格式 + * @return 字符串时间类型 + * @author zhengqingya + * @date 2020/8/22 13:07 + */ + public static String dateToStr(Date date, String format) { + return new SimpleDateFormat(format).format(date); + } + + /** + * Date转Str {@link MyDateUtil.DATE_TIME_FORMAT} + * + * @param date 时间 + * @return 字符串时间类型 + * @author zhengqingya + * @date 2020/8/22 13:07 + */ + public static String dateToStr(Date date) { + return new SimpleDateFormat(DATE_TIME_FORMAT).format(date); + } + + /** + * Date 时间开始处理 (ex: Sat Sep 04 10:11:25 CST 2021 -> 2021-09-04 00:00:00) + * + * @param date 时间 + * @return 时间 + * @author zhengqingya + * @date 2021/9/4 10:12 + */ + @SneakyThrows(Exception.class) + public static Date dateToStartTime(Date date) { + String dateTime = dateToStr(date, DATE_TIME_START_FORMAT); + return strToDate(dateTime, DATE_TIME_FORMAT); + } + + /** + * Date 时间结束处理 (ex: Sat Sep 04 10:11:25 CST 2021 -> 2021-09-04 23:59:59) + * + * @param date 时间 + * @return 时间 + * @author zhengqingya + * @date 2021/9/4 10:12 + */ + @SneakyThrows(Exception.class) + public static Date dateToEndTime(Date date) { + String dateTime = dateToStr(date, DATE_TIME_END_FORMAT); + return strToDate(dateTime, DATE_TIME_FORMAT); + } + + /** + * Str(yyyy-MM-dd HH:mm:ss)转DateTime + * + * @param dateStr 字符串时间 + * @return Date时间类型 + * @author zhengqingya + * @date 2021/8/20 9:41 + */ + @SneakyThrows(Exception.class) + public static Date strToDateTime(String dateStr) { + return new SimpleDateFormat(DATE_TIME_FORMAT).parse(dateStr); + } + + /** + * Str转Date + * + * @param dateStr 字符串时间 + * @param format 时间格式 + * @return Date时间类型 + * @author zhengqingya + * @date 2020/8/22 13:07 + */ + @SneakyThrows(Exception.class) + public static Date strToDate(String dateStr, String format) { + return new SimpleDateFormat(format).parse(dateStr); + } + + /** + * Str转long(10位-秒级别) + * + * @param dateStr 字符串时间 + * @param format 时间格式 + * @return 秒 + * @author zhengqingya + * @date 2020/8/22 13:07 + */ + @SneakyThrows(Exception.class) + public static long strToLong(String dateStr, String format) { + return new SimpleDateFormat(format).parse(dateStr).getTime() / 1000; + } + + /** + * 获取服务器启动时间 + */ + public static Date getServerStartDate() { + long time = ManagementFactory.getRuntimeMXBean().getStartTime(); + return new Date(time); + } + + /** + * 判断结束时间是否大于等于开始时间 + * + * @param startTime 开始时间 + * @param endTime 结束时间 + * @param format 时间格式 + * @return 判断结果 + * @author zhengqingya + * @date 2020/8/22 13:21 + */ + public static boolean verifyTime(String startTime, String endTime, String format) { + Date startDate = strToDate(startTime, format); + Date endDate = strToDate(endTime, format); + return endDate.getTime() >= startDate.getTime(); + } + + /** + * 获取今日开始时间 + * + * @return 今日开始时间 + * @author zhengqingya + * @date 2021/8/18 18:58 + */ + public static Date todayStartTime() { + LocalDateTime todayStartTime = LocalDateTime.of(LocalDate.now(), LocalTime.MIN); + return Date.from(todayStartTime.atZone(ZoneId.systemDefault()).toInstant()); + } + + /** + * 获取今日开始时间 + * + * @return 今日开始时间 + * @author zhengqingya + * @date 2021/8/18 18:58 + */ + public static String todayStartTimeStr() { + DateTimeFormatter df = DateTimeFormatter.ofPattern(DATE_TIME_START_FORMAT); + LocalDateTime time = LocalDateTime.now(); + return df.format(time); + } + + /** + * 获取今日结束时间 + * + * @return 今日结束时间 + * @author zhengqingya + * @date 2021/8/18 18:58 + */ + public static Date todayEndTime() { + LocalDateTime todayStartTime = LocalDateTime.of(LocalDate.now(), LocalTime.MAX); + return Date.from(todayStartTime.atZone(ZoneId.systemDefault()).toInstant()); + } + + /** + * 获取今日结束时间 + * + * @return 今日结束时间 + * @author zhengqingya + * @date 2021/8/18 18:58 + */ + public static String todayEndTimeStr() { + DateTimeFormatter df = DateTimeFormatter.ofPattern(DATE_TIME_END_FORMAT); + LocalDateTime time = LocalDateTime.now(); + return df.format(time); + } + + /** + * 计算两时间分钟差 + * + * @param startTimeStr 开始时间 ex: 2020-09-09 10:00:10 + * @param endTimeStr 结束时间 ex: 2020-09-09 10:30:10 + * @return 分钟差 ex:30 + * @author zhengqingya + * @date 2021/9/9 10:21 + */ + @SneakyThrows(Exception.class) + public static int diffMinute(String startTimeStr, String endTimeStr) { + SimpleDateFormat simpleFormat = new SimpleDateFormat(MINUTE_FORMAT); + long startTime = simpleFormat.parse(startTimeStr).getTime(); + long endTime = simpleFormat.parse(endTimeStr).getTime(); + return (int) ((endTime - startTime) / (1000 * 60)); + } + + /** + * 计算两时间毫秒差 + * + * @param startTime 开始时间 + * @param endTime 结束时间 + * @return 毫秒差 ex:30 + * @author zhengqingya + * @date 2021/9/9 10:21 + */ + @SneakyThrows(Exception.class) + public static long diffMillisecond(Date startTime, Date endTime) { + return endTime.getTime() - startTime.getTime(); + } + + /** + * 计算两个时间差 + * + * @param startDate 开始时间 + * @param endDate 结束时间 + * @return 时间差 + * @author zhengqingya + * @date 2021/7/23 10:17 + */ + public static String getDateDiffStr(Date startDate, Date endDate) { + long nd = 1000 * 24 * 60 * 60; + long nh = 1000 * 60 * 60; + long nm = 1000 * 60; + // long ns = 1000; + // 获得两个时间的毫秒时间差异 + long diff = endDate.getTime() - startDate.getTime(); + // 计算差多少天 + long day = diff / nd; + // 计算差多少小时 + long hour = diff % nd / nh; + // 计算差多少分钟 + long min = diff % nd % nh / nm; + // 计算差多少秒//输出结果 + // long sec = diff % nd % nh % nm / ns; + return day + "天" + hour + "小时" + min + "分钟"; + } + + /** + * 在当前时间上加指定时间 + * + * @param timeUnit 时间单位 + * @param time 时间 + * @return 结果 + * @author zhengqingya + * @date 2021/10/18 16:57 + */ + @SneakyThrows(Exception.class) + public static Date addTime(TimeUnit timeUnit, int time) { + return addAndSubTime(timeUnit, +time); + } + + /** + * 在当前时间上减指定时间 + * + * @param timeUnit 时间单位 + * @param time 时间 + * @return 结果 + * @author zhengqingya + * @date 2021/10/18 16:57 + */ + @SneakyThrows(Exception.class) + public static Date subTime(TimeUnit timeUnit, int time) { + return addAndSubTime(timeUnit, -time); + } + + /** + * 在当前时间上 加或减 指定时间 + * + * @param timeUnit 时间单位 + * @param time 时间 + * @return 结果 + * @author zhengqingya + * @date 2021/10/18 16:57 + */ + @SneakyThrows(Exception.class) + public static Date addAndSubTime(TimeUnit timeUnit, int time) { + return timeAddAndSubTime(new Date(), timeUnit, time); + } + + /** + * 在指定时间上 加或减 指定时间 + * + * @param timeUnit 时间单位 + * @param time 时间 + * @return 结果 + * @author zhengqingya + * @date 2021/10/18 16:57 + */ + @SneakyThrows(Exception.class) + public static Date timeAddAndSubTime(Date sourceDate, TimeUnit timeUnit, int time) { + Calendar calendar = Calendar.getInstance(); + calendar.setTime(sourceDate); + switch (timeUnit) { + case SECONDS: + calendar.add(Calendar.SECOND, time); + break; + case MINUTES: + calendar.add(Calendar.MINUTE, time); + break; + case HOURS: + calendar.add(Calendar.HOUR, time); + break; + case DAYS: + calendar.add(Calendar.DATE, time); + break; + default: + throw new Exception("暂不支持该时间类型!"); + } + return calendar.getTime(); + } + + + public static void main(String[] args) { + Date nowDateTime = new Date(); + String dateStr = dateToStr(nowDateTime, DATE_TIME_FORMAT); + System.out.println(nowStr(YEAR_FORMAT)); + boolean result = verifyTime("2020-08-18 00:00:00", "2020-08-17 23:59:59", DATE_TIME_FORMAT); + System.out.println(result); + + Date todayStartTime = todayStartTime(); + Date todayEndTime = todayEndTime(); + + String todayStartTimeStr = todayStartTimeStr(); + String todayEndTimeStr = todayEndTimeStr(); + + Date date = strToDateTime("2020-08-18 00:00:10"); + + Long aLong = strToLong("2020-08-18 00:00:10", DATE_TIME_FORMAT); + + Date dateTimeStartFormat = dateToStartTime(nowDateTime); + Date dateTimeEndFormat = dateToEndTime(nowDateTime); + + int diffMinute = diffMinute("2020-09-09 10:00:10", "2020-09-09 10:30:10"); + long diffMillisecond = diffMillisecond(todayStartTime, todayEndTime); + + log.info("nowTime:{} addTime: {}", nowStr(), dateToStr(addTime(TimeUnit.SECONDS, 20), DATE_TIME_FORMAT)); + log.info("nowTime:{} addTime: {}", nowStr(), dateToStr(addTime(TimeUnit.MINUTES, 10), DATE_TIME_FORMAT)); + log.info("nowTime:{} addTime: {}", nowStr(), dateToStr(addTime(TimeUnit.HOURS, 10), DATE_TIME_FORMAT)); + log.info("nowTime:{} addTime: {}", nowStr(), dateToStr(addTime(TimeUnit.DAYS, 10), DATE_TIME_FORMAT)); + + log.info("--------------------------------"); + + log.info("nowTime:{} subTime: {}", nowStr(), dateToStr(subTime(TimeUnit.SECONDS, 20), DATE_TIME_FORMAT)); + log.info("nowTime:{} subTime: {}", nowStr(), dateToStr(subTime(TimeUnit.MINUTES, 10), DATE_TIME_FORMAT)); + log.info("nowTime:{} subTime: {}", nowStr(), dateToStr(subTime(TimeUnit.HOURS, 10), DATE_TIME_FORMAT)); + log.info("nowTime:{} subTime: {}", nowStr(), dateToStr(subTime(TimeUnit.DAYS, 10), DATE_TIME_FORMAT)); + + log.info("--------------------------------"); + + log.info("nowTime:{} timeAddAndsubTime: {}", nowStr(), dateToStr(timeAddAndSubTime(todayStartTime, TimeUnit.MINUTES, 10), DATE_TIME_FORMAT)); + + log.info("当前时间到今天开始时间差:{}", MyDateUtil.getDateDiffStr(MyDateUtil.todayStartTime(), new Date())); + } + +} diff --git a/smallboot-api/common/base/src/main/java/com/zhengqing/common/base/util/MyFileUtil.java b/smallboot-api/common/base/src/main/java/com/zhengqing/common/base/util/MyFileUtil.java new file mode 100644 index 0000000..a849096 --- /dev/null +++ b/smallboot-api/common/base/src/main/java/com/zhengqing/common/base/util/MyFileUtil.java @@ -0,0 +1,177 @@ +package com.zhengqing.common.base.util; + +import cn.hutool.core.io.FileUtil; +import cn.hutool.core.util.ZipUtil; +import lombok.SneakyThrows; +import lombok.extern.slf4j.Slf4j; + +import java.io.File; +import java.io.InputStream; +import java.nio.charset.Charset; + +/** + *

+ * 文件工具类 + *

+ * + * @author zhengqingya + * @description + * @date 2020/8/27 19:21 + */ +@Slf4j +public class MyFileUtil { + + /** + * 多文件或目录压缩:将`srcPath`目录以及其目录下的所有文件目录打包到`zipPath`+`suffixFileName`文件中 【采用hutool工具类进行打包文件】 + * + * @param srcPath 需打包的源目录 + * @param zipPath 打包后的路径+文件后缀名 + * @param isWithSrcDir 是否带目录显示 (true:表示带目录显示) + * @param isDeleteSrcZip 是否删除源目录 + * @return java.lang.String + * @author zhengqingya + * @date 2020/8/27 19:25 + */ + public static File zip(String srcPath, String zipPath, boolean isWithSrcDir, boolean isDeleteSrcZip) { + log.debug("【压缩文件】 源目录路径: 【{}】 打包后的路径+文件后缀名: 【{}】", srcPath, zipPath); + File zipFile = ZipUtil.zip(srcPath, zipPath, isWithSrcDir); + // 删除目录 -> 保证下次生成文件的时候不会累计上次留下的文件 + if (isDeleteSrcZip) { + MyFileUtil.deleteFileOrFolder(srcPath); + } + return zipFile; + } + + /** + * 根据路径删除指定的目录或文件,无论存在与否 + * + * @param fullFileOrDirPath 要删除的目录或文件 + * @return 删除成功返回 true,否则返回 false + * @author zhengqingya + * @date 2020/9/5 20:56 + */ + public static boolean deleteFileOrFolder(String fullFileOrDirPath) { + return FileUtil.del(fullFileOrDirPath); + } + + /** + * 根据路径创建文件 + * + * @param fullFilePath 文件生成路径 + * @return 文件信息 + * @author zhengqingya + * @date 2020/9/8 21:41 + */ + public static File touch(String fullFilePath) { + return FileUtil.touch(fullFilePath); + } + + /** + * 解压 + * + * @param inputStream 流 + * @param zipFilePath zip文件路径 + * @param outFileDir 解压后的目录路径 + * @param isDeleteZip 是否删除源zip文件 + * @return 解压后的文件File信息 + * @author zhengqingya + * @date 2020/9/5 20:50 + */ + @SneakyThrows(Exception.class) + public static File unzip(InputStream inputStream, String zipFilePath, String outFileDir, boolean isDeleteZip) { + log.debug("【解压文件】 zip文件路径: 【{}】 解压后的目录路径: 【{}】", zipFilePath, outFileDir); + // zip压缩文件 +// File zipFile = FileUtil.newFile(zipFilePath); + // 写入文件 +// FileUtils.copyInputStreamToFile(inputStream, zipFile); + // 编码方式 "UTF-8" 、"GBK" 【注: gbk编码才能解决报错: java.lang.IllegalArgumentException: MALFORMED】 + File outFile = ZipUtil.unzip(zipFilePath, outFileDir, Charset.forName("GBK")); + // 删除zip -> 保证下次解压后的文件数据不会累计上次解压留下的文件 + if (isDeleteZip) { + MyFileUtil.deleteFileOrFolder(zipFilePath); + } + return outFile; + } + + /** + * 读取文件内容 + * + * @param file 文件数据 + * @return 文件内容 + * @author zhengqingya + * @date 2020/9/5 23:00 + */ + public static String readFileContent(File file) { + return FileUtil.readUtf8String(file); + } + + /** + * 读取文件内容 + * + * @param filePath 文件路径 + * @return 文件内容 + * @author zhengqingya + * @date 2020/9/5 23:00 + */ + public static String readFileContent(String filePath) { + return FileUtil.readUtf8String(filePath); + } + + /** + * 读取文件数据 + * + * @param filePath 文件路径 + * @return 文件字节码 + * @author zhengqingya + * @date 2020/9/5 23:00 + */ + public static byte[] readBytes(String filePath) { + return FileUtil.readBytes(filePath); + } + + /** + * 写入文件内容 + * + * @param fileContent 文件内容 + * @param filePath 文件路径 + * @return 文件信息 + * @author zhengqingya + * @date 2020/11/17 21:38 + */ + @SneakyThrows(Exception.class) + public static File writeFileContent(String fileContent, String filePath) { + return FileUtil.writeUtf8String(fileContent, filePath); + } + + /** + * 字节码写入文件 + * + * @param data 字节码 + * @param filePath 文件路径 + * @return 文件信息 + * @author zhengqingya + * @date 2020/11/24 14:36 + */ + @SneakyThrows(Exception.class) + public static File writeFileContent(byte[] data, String filePath) { + return FileUtil.writeBytes(data, filePath); + } + + public static void main(String[] args) { + try { + String filePath = "E:\\IT_zhengqing\\code\\me-workspace\\最新代码生成器\\code-api\\document\\import\\blog.zip"; + String filePathX = "E:\\IT_zhengqing\\code\\me-workspace\\最新代码生成器\\code-api\\document\\import"; + // File file = + // FileUtil.newFile(filePath); + // InputStream fileInputStream = new FileInputStream(file); + File unzip = ZipUtil.unzip(filePath, filePathX); + System.out.println(unzip); + + String fileContent = FileUtil.readUtf8String(filePath); + } catch (Exception e) { + e.printStackTrace(); + } + + } + +} diff --git a/smallboot-api/common/base/src/main/java/com/zhengqing/common/base/util/MyStringUtil.java b/smallboot-api/common/base/src/main/java/com/zhengqing/common/base/util/MyStringUtil.java new file mode 100644 index 0000000..7d39c6c --- /dev/null +++ b/smallboot-api/common/base/src/main/java/com/zhengqing/common/base/util/MyStringUtil.java @@ -0,0 +1,118 @@ +package com.zhengqing.common.base.util; + +import com.google.common.base.CaseFormat; +import org.apache.commons.lang3.StringUtils; + +import java.util.StringJoiner; + +/** + *

+ * 字符串工具类 + *

+ * + * @author zhengqingya
+ * @date 2019/9/14 0014$ 19:49$
+ */ +public class MyStringUtil { + + /** + * 将驼峰式命名的字符串转换为下划线大写方式。如果转换前的驼峰式命名的字符串为空,则返回空字符串。
+ * 例如:HelloWorld->HELLO_WORLD + * + * @param name 转换前的驼峰式命名的字符串 + * @return 转换后下划线大写方式命名的字符串 + */ + public static String humpToMark(String name) { + StringBuilder result = new StringBuilder(); + if (name != null && name.length() > 0) { + // 将第一个字符处理成大写 + result.append(name.substring(0, 1).toUpperCase()); + // 循环处理其余字符 + for (int i = 1; i < name.length(); i++) { + String s = name.substring(i, i + 1); + // 在大写字母前添加下划线 + if (s.equals(s.toUpperCase()) && !Character.isDigit(s.charAt(0))) { + result.append("_"); + } + // 其他字符直接转成大写 + result.append(s.toUpperCase()); + } + } + return result.toString(); + } + + /** + * 将字符串转换为驼峰式
+ * 例如:HELLO.WORLD -> HelloWorld + *

+ * System.out.println(CaseFormat.LOWER_HYPHEN.to(CaseFormat.LOWER_CAMEL, "test-data"));//testData + * System.out.println(CaseFormat.LOWER_UNDERSCORE.to(CaseFormat.LOWER_CAMEL, "test_data"));//testData + * System.out.println(CaseFormat.UPPER_UNDERSCORE.to(CaseFormat.UPPER_CAMEL, "test_data"));//TestData + *

+ * System.out.println(CaseFormat.LOWER_CAMEL.to(CaseFormat.LOWER_UNDERSCORE, "testdata"));//testdata + * System.out.println(CaseFormat.LOWER_CAMEL.to(CaseFormat.LOWER_UNDERSCORE, "TestData"));//test_data + * System.out.println(CaseFormat.LOWER_CAMEL.to(CaseFormat.LOWER_HYPHEN, "testData"));//test-data + * + * @param str 转换前的字符串 + * @param mark 以xx符号分割 ex: `,` `_` `-` `.` + * @return 转换后的驼峰式命名的字符串 + */ + public static String strToHumpByMark(String str, String mark) { + // 替换指定的符号mark为`_` + String replaceStr = StringUtils.replace(str, mark, "_"); + // 再使用谷歌开发工具包转驼峰命名 ex:test_data -> TestData + String result = CaseFormat.UPPER_UNDERSCORE.to(CaseFormat.UPPER_CAMEL, replaceStr); + return result; + } + + /** + * 数据库表名/表字段 转 首字母小写驼峰命名 ex:test_data -> testData + * + * @param dbStr 数据库表名/表字段 + * @return 驼峰命名字符串 + * @author zhengqingya + * @date 2020/11/15 17:38 + */ + public static String dbStrToHumpLower(String dbStr) { + // 使用谷歌开发工具包转驼峰命名 + return CaseFormat.LOWER_UNDERSCORE.to(CaseFormat.LOWER_CAMEL, dbStr); + } + + /** + * 数据库表名/表字段 转 首字母大写驼峰命名 ex:test_data -> TestData + * + * @param dbStr 数据库表名/表字段 + * @return 驼峰命名字符串 + * @author zhengqingya + * @date 2020/11/15 17:38 + */ + public static String dbStrToHumpUpper(String dbStr) { + // 使用谷歌开发工具包转驼峰命名 + return CaseFormat.LOWER_UNDERSCORE.to(CaseFormat.UPPER_CAMEL, dbStr); + } + + /** + * 获取表名缩写 + * + * @param tableName 表名 + * @return 缩写 + * @author zhengqingya + * @date 2020/12/12 15:29 + */ + public static String tableNameToAbbr(String tableName) { + StringJoiner sj = new StringJoiner(""); + String[] strArray = tableName.split("_"); + for (String str : strArray) { + sj.add(str.substring(0, 1)); + } + return sj.toString(); + } + + public static void main(String[] args) { + String str = dbStrToHumpLower("t_sys_user"); + String str2 = dbStrToHumpUpper("t_sys_user"); + String str3 = tableNameToAbbr("t_sys_user"); + System.out.println(str3); + } + +} diff --git a/smallboot-api/common/base/src/main/java/com/zhengqing/common/base/util/SpringElUtil.java b/smallboot-api/common/base/src/main/java/com/zhengqing/common/base/util/SpringElUtil.java new file mode 100644 index 0000000..a3f3732 --- /dev/null +++ b/smallboot-api/common/base/src/main/java/com/zhengqing/common/base/util/SpringElUtil.java @@ -0,0 +1,55 @@ +package com.zhengqing.common.base.util; + +import org.springframework.core.LocalVariableTableParameterNameDiscoverer; +import org.springframework.expression.EvaluationContext; +import org.springframework.expression.ExpressionParser; +import org.springframework.expression.spel.standard.SpelExpressionParser; +import org.springframework.expression.spel.support.StandardEvaluationContext; + +import java.lang.reflect.Method; + +/** + * spring El表达式 工具类 + * + * @author zhengqingya + * @description + * @date 2020/4/12 0:40 + */ +public class SpringElUtil { + + /** + * 表达式解析器 + */ + private static final ExpressionParser PARSER = new SpelExpressionParser(); + + /** + * 获取方法的参数名 + */ + private static final LocalVariableTableParameterNameDiscoverer DISCOVERER = new LocalVariableTableParameterNameDiscoverer(); + + /** + * 解析spring EL表达式 + * + * @param key 表达式 + * @param method 方法 + * @param args 方法参数 + * @return 结果 + * @author zhengqingya + * @date 2022/1/18 14:35 + */ + public static String parse(String key, Method method, Object[] args) { + // 获取方法的参数名 + String[] paramNameArray = DISCOVERER.getParameterNames(method); + if (paramNameArray == null) { + return ""; + } + // 表达式的上下文 + EvaluationContext context = new StandardEvaluationContext(); + for (int i = 0; i < paramNameArray.length; i++) { + // 为了让表达式可以访问该对象, 先把对象放到上下文中 + context.setVariable(paramNameArray[i], args[i]); + } + return PARSER.parseExpression(key).getValue(context, String.class); + } + +} diff --git a/smallboot-api/common/base/src/main/resources/application-base.yml b/smallboot-api/common/base/src/main/resources/application-base.yml new file mode 100644 index 0000000..e69de29 diff --git a/smallboot-api/common/base/src/main/resources/config.properties b/smallboot-api/common/base/src/main/resources/config.properties new file mode 100644 index 0000000..e69de29 diff --git a/smallboot-api/common/core/pom.xml b/smallboot-api/common/core/pom.xml new file mode 100644 index 0000000..10d4926 --- /dev/null +++ b/smallboot-api/common/core/pom.xml @@ -0,0 +1,74 @@ + + + 4.0.0 + + + common + com.zhengqing + ${revision} + + + core + + ${project.artifactId} + ${revision} + jar + + + + + + + com.zhengqing + base + + + + com.zhengqing + auth + + + + com.zhengqing + log + + + + com.zhengqing + swagger + + + + com.zhengqing + web + + + + com.zhengqing + db + + + + com.zhengqing + redis + + + + + com.zhengqing + file + + + + + + + + + + + + + diff --git a/smallboot-api/common/core/src/main/java/com/zhengqing/common/core/api/BaseController.java b/smallboot-api/common/core/src/main/java/com/zhengqing/common/core/api/BaseController.java new file mode 100644 index 0000000..c29d94d --- /dev/null +++ b/smallboot-api/common/core/src/main/java/com/zhengqing/common/core/api/BaseController.java @@ -0,0 +1,32 @@ +package com.zhengqing.common.core.api; + +import com.zhengqing.common.base.context.SysUserContext; +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.bind.annotation.RestController; + +/** + *

+ * Controller基类 + *

+ * + * @author zhengqingya + * @description + * @date 2019/8/17 0017 19:53 + */ +@Slf4j +@RestController +//@Api(tags = "base接口") +public class BaseController { + + /** + * 获取当前登录人ID + * + * @return 当前登录人ID + * @author zhengqingya + * @date 2020/8/30 15:41 + */ + protected Integer appGetCurrentUserId() { + return SysUserContext.getUserId(); + } + +} diff --git a/smallboot-api/common/core/src/main/java/com/zhengqing/common/core/aspect/ApiLogAspect.java b/smallboot-api/common/core/src/main/java/com/zhengqing/common/core/aspect/ApiLogAspect.java new file mode 100644 index 0000000..708f6bd --- /dev/null +++ b/smallboot-api/common/core/src/main/java/com/zhengqing/common/core/aspect/ApiLogAspect.java @@ -0,0 +1,89 @@ +package com.zhengqing.common.core.aspect; + +import cn.hutool.json.JSONUtil; +import com.zhengqing.common.base.context.SysUserContext; +import com.zhengqing.common.web.util.ServletUtil; +import io.swagger.annotations.ApiOperation; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; +import org.aspectj.lang.JoinPoint; +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.annotation.Before; +import org.aspectj.lang.annotation.Pointcut; +import org.aspectj.lang.reflect.MethodSignature; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.stereotype.Component; + +import javax.servlet.http.HttpServletRequest; +import java.util.Map; + +/** + *

+ * 日志切面 + *

+ * + * @author zhengqingya + * @description + * @date 2020/8/1 19:09 + */ +@Slf4j +@Aspect +@Component +@ConditionalOnProperty( + value = {"smallboot.api-log"}, + havingValue = "true", + // true表示缺少此配置属性时也会加载该bean + matchIfMissing = true +) +public class ApiLogAspect { + + /** + * 配置织入点 + */ + @Pointcut("execution(* com.zhengqing.*..*.*Controller.*(..))") + public void logPointCut() { + } + + @Before("logPointCut()") + public void doAround(JoinPoint joinPoint) throws Throwable { + HttpServletRequest request = ServletUtil.getRequest(); + if (request != null) { + Integer userId = SysUserContext.getUserId(); + String username = SysUserContext.getUsername(); + + // 从切面织入点处通过反射机制获取织入点处的方法 + MethodSignature signature = (MethodSignature) joinPoint.getSignature(); + // 获取切入点所在的方法 + ApiOperation apiOperation = signature.getMethod().getAnnotation(ApiOperation.class); + +// log.debug("========================== ↓↓↓↓↓↓ 《ApiLogAspect》 Start... ↓↓↓↓↓↓ =========================="); +// log.debug("《ApiLogAspect》 controller method: {}", +// signature.getDeclaringTypeName() + "." + signature.getName()); +// log.debug("《ApiLogAspect》 controller method description: {}", apiOperation.value()); +// log.debug("《ApiLogAspect》 operatorId: {}", userId); +// log.debug("《ApiLogAspect》 operatorName: {}", username); +// log.debug("《ApiLogAspect》 request header: {}", request.getHeader(SwaggerConstant.REQUEST_HEADER_AUTHORIZATION)); +// log.debug("《ApiLogAspect》 request ip: {}", request.getRemoteAddr()); +// log.debug("《ApiLogAspect》 request url: {}", request.getRequestURL().toString()); +// log.debug("《ApiLogAspect》 request http method: {}", request.getMethod()); +// log.debug("《ApiLogAspect》 request params: {}", this.getRequestValue(request)); +// log.debug("========================== ↑↑↑↑↑↑ 《ApiLogAspect》 End... ↑↑↑↑↑↑ =========================="); + + log.debug("[{}] [{}] 请求参数:{}", + request.getMethod(), + signature.getDeclaringTypeName() + "." + signature.getName(), + this.getRequestValue(request) + ); + } + } + + /** + * 获取请求的参数 + */ + private String getRequestValue(HttpServletRequest request) { + Map map = request.getParameterMap(); + String params = JSONUtil.toJsonStr(map); + return StringUtils.substring(params, 0, 2000); + } + +} diff --git a/smallboot-api/common/core/src/main/java/com/zhengqing/common/core/aspect/ControllerAspect.java b/smallboot-api/common/core/src/main/java/com/zhengqing/common/core/aspect/ControllerAspect.java new file mode 100644 index 0000000..7dbf661 --- /dev/null +++ b/smallboot-api/common/core/src/main/java/com/zhengqing/common/core/aspect/ControllerAspect.java @@ -0,0 +1,60 @@ +package com.zhengqing.common.core.aspect; + +import com.zhengqing.common.base.context.SysUserContext; +import com.zhengqing.common.base.model.dto.BaseDTO; +import com.zhengqing.common.core.custom.parameter.ParamCheck; +import lombok.extern.slf4j.Slf4j; +import org.aspectj.lang.JoinPoint; +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.annotation.Before; +import org.aspectj.lang.annotation.Pointcut; +import org.springframework.stereotype.Component; + +/** + *

+ * Controller 切面 + *

+ * + * @author zhengqingya + * @description + * @date 2021/1/9 17:47 + */ +@Slf4j +@Aspect +@Component +public class ControllerAspect { + + /** + * 配置织入点 + */ + @Pointcut("execution(* com.zhengqing.*..*.*Controller.*(..))") + public void controllerPointCut() { + } + + /** + * Before增强:在目标方法被执行的时候织入增强 + *

+ * Controller所有方法为切入点 + */ + @Before("controllerPointCut()") + public void controllerPointCut(JoinPoint joinPoint) { + // 参数处理 + Object[] paramObjArray = joinPoint.getArgs(); + // 遍历所有传入参数,赋值 + for (Object paramObj : paramObjArray) { + // dto参数处理 + if (paramObj instanceof BaseDTO) { + BaseDTO baseDTO = (BaseDTO) paramObj; + baseDTO.setCurrentUserId(SysUserContext.getUserId()); + baseDTO.setCurrentUsername(SysUserContext.getUsername()); + } + + // 参数校验处理 + if (paramObj instanceof ParamCheck) { + ParamCheck paramCheck = (ParamCheck) paramObj; + paramCheck.checkParam(); + } + } + } + +} diff --git a/smallboot-api/common/core/src/main/java/com/zhengqing/common/core/aspect/MapperAspect.java b/smallboot-api/common/core/src/main/java/com/zhengqing/common/core/aspect/MapperAspect.java new file mode 100644 index 0000000..2c2804b --- /dev/null +++ b/smallboot-api/common/core/src/main/java/com/zhengqing/common/core/aspect/MapperAspect.java @@ -0,0 +1,61 @@ +package com.zhengqing.common.core.aspect; + +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.zhengqing.common.base.exception.ParameterException; +import com.zhengqing.common.swagger.constant.SwaggerConstant; +import com.zhengqing.common.web.util.ServletUtil; +import org.aspectj.lang.JoinPoint; +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.annotation.Before; +import org.aspectj.lang.annotation.Pointcut; +import org.springframework.core.annotation.Order; +import org.springframework.stereotype.Component; + +/** + *

+ * Mapper分页参数注入切面 + *

+ * + * @author zhengqingya + * @description + * @date 2020/8/1 18:18 + */ +@Aspect +@Order(2) +@Component +public class MapperAspect { + + private static final int DEFAULT_PAGE_NUM = 1; + private static final int DEFAULT_PAGE_SIZE = 10; + + /** + * 配置织入点 + */ + @Pointcut("execution(* com.zhengqing.*..*.*Mapper.*(..))") + public void mapperPointCut() { + } + + /** + * Before增强:在目标方法被执行的时候织入增强 + *

+ * Mapper所有方法作为切入点 + */ + @Before("mapperPointCut()") + public void mapperPointCut(JoinPoint joinPoint) { + Object[] paramObjArray = joinPoint.getArgs(); + int pageNum = ServletUtil.getParameterToInt(SwaggerConstant.PAGE_NUM, DEFAULT_PAGE_NUM); + int pageSize = ServletUtil.getParameterToInt(SwaggerConstant.PAGE_SIZE, DEFAULT_PAGE_SIZE); + // 遍历所有传入参数,赋值 + for (Object paramObj : paramObjArray) { + if (paramObj instanceof IPage) { + if (pageNum < 1 || pageSize < 1) { + throw new ParameterException("传递的分页参数有误!"); + } + IPage page = ((IPage) paramObj); + page.setCurrent(pageNum); + page.setSize(pageSize); + } + } + } + +} diff --git a/smallboot-api/common/core/src/main/java/com/zhengqing/common/core/aspect/config/BeanSelfAware.java b/smallboot-api/common/core/src/main/java/com/zhengqing/common/core/aspect/config/BeanSelfAware.java new file mode 100644 index 0000000..741bbbf --- /dev/null +++ b/smallboot-api/common/core/src/main/java/com/zhengqing/common/core/aspect/config/BeanSelfAware.java @@ -0,0 +1,14 @@ +package com.zhengqing.common.core.aspect.config; + +/** + *

+ * 定义BeanPostProcessor 需要使用的标识接口 + *

+ * + * @author zhengqingya + * @description 即我们自定义的BeanPostProcessor (InjectBeanSelfProcessor)如果发现我们的Bean是实现了该标识接口就调用setSelf注入代理对象 + * @date 2021/1/9 1:10 + */ +public interface BeanSelfAware { + void setSelf(Object proxyBean); +} diff --git a/smallboot-api/common/core/src/main/java/com/zhengqing/common/core/aspect/config/InjectBeanSelfProcessor.java b/smallboot-api/common/core/src/main/java/com/zhengqing/common/core/aspect/config/InjectBeanSelfProcessor.java new file mode 100644 index 0000000..d033b92 --- /dev/null +++ b/smallboot-api/common/core/src/main/java/com/zhengqing/common/core/aspect/config/InjectBeanSelfProcessor.java @@ -0,0 +1,59 @@ +package com.zhengqing.common.core.aspect.config; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.aop.support.AopUtils; +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.config.BeanPostProcessor; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.stereotype.Component; + +/** + *

+ * 通过BeanPostProcessor 在目标对象中注入代理对象 -> 解决Spring AOP不拦截对象内部调用方法问题 + *

+ * + * @author zhengqingya + * @description + * @date 2021/1/9 0:55 + */ +@Slf4j +@Component +public class InjectBeanSelfProcessor implements BeanPostProcessor, ApplicationContextAware { + + private ApplicationContext applicationContext; + + /** + * 1、注入ApplicationContext + */ + @Override + public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { + this.applicationContext = applicationContext; + } + + @Override + public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { + // 2、如果Bean没有实现BeanSelfAware标识接口 跳过 + if (!(bean instanceof BeanSelfAware)) { + return bean; + } + + log.debug("inject proxy:【{}】", bean.getClass()); + + if (AopUtils.isAopProxy(bean)) { + // 3、如果当前对象是AOP代理对象,直接注入 + ((BeanSelfAware) bean).setSelf(bean); + } else { + // 4、如果当前对象不是AOP代理,则通过context.getBean(beanName)获取代理对象并注入 + // 此种方式不适合解决prototype Bean的代理对象注入 + ((BeanSelfAware) bean).setSelf(this.applicationContext.getBean(beanName)); + } + return bean; + } + + @Override + public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { + return bean; + } + +} diff --git a/smallboot-api/common/core/src/main/java/com/zhengqing/common/core/config/AppCommonRunner.java b/smallboot-api/common/core/src/main/java/com/zhengqing/common/core/config/AppCommonRunner.java new file mode 100644 index 0000000..66356be --- /dev/null +++ b/smallboot-api/common/core/src/main/java/com/zhengqing/common/core/config/AppCommonRunner.java @@ -0,0 +1,26 @@ +package com.zhengqing.common.core.config; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.boot.CommandLineRunner; +import org.springframework.stereotype.Component; + +/** + *

+ * 服务初始化之后,执行方法 + *

+ * + * @author zhengqingya + * @description + * @date 2020/5/22 19:29 + */ +@Slf4j +@Component +public abstract class AppCommonRunner implements CommandLineRunner { + + public void appRun() { + log.info("《AppCommonRunner》: 服务初始化之后,执行方法 start..."); + + log.info("《AppCommonRunner》: 服务初始化之后,执行方法 end..."); + } + +} diff --git a/smallboot-api/common/core/src/main/java/com/zhengqing/common/core/config/CommonProperty.java b/smallboot-api/common/core/src/main/java/com/zhengqing/common/core/config/CommonProperty.java new file mode 100644 index 0000000..a9473f4 --- /dev/null +++ b/smallboot-api/common/core/src/main/java/com/zhengqing/common/core/config/CommonProperty.java @@ -0,0 +1,41 @@ +package com.zhengqing.common.core.config; + +import lombok.Data; + +/** + *

+ * 公共基础配置参数 + *

+ * + * @author zhengqingya + * @description + * @date 2019/8/19 9:07 + */ +@Data +public class CommonProperty { + + /** + * ip + */ + private String ip; + + /** + * MySQL参数 + */ + private Mysql mysql; + + @Data + public static class Mysql { + private MysqlConn master; + private MysqlConn dbTest; + } + + @Data + public static class MysqlConn { + private String ip; + private String port; + private String username; + private String password; + } + +} diff --git a/smallboot-api/common/core/src/main/java/com/zhengqing/common/core/config/WebAppConfig.java b/smallboot-api/common/core/src/main/java/com/zhengqing/common/core/config/WebAppConfig.java new file mode 100644 index 0000000..2712996 --- /dev/null +++ b/smallboot-api/common/core/src/main/java/com/zhengqing/common/core/config/WebAppConfig.java @@ -0,0 +1,26 @@ +package com.zhengqing.common.core.config; + +import com.zhengqing.common.core.config.interceptor.HandlerInterceptorForTokenUser; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.servlet.config.annotation.InterceptorRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +/** + *

+ * 注册拦截器 + *

+ * + * @author zhengqingya + * @description ex: 租户ID + * @date 2021/1/13 14:41 + */ +@Configuration +public class WebAppConfig implements WebMvcConfigurer { + + @Override + public void addInterceptors(InterceptorRegistry registry) { + // 可添加多个 + registry.addInterceptor(new HandlerInterceptorForTokenUser()).addPathPatterns("/**"); + } + +} diff --git a/smallboot-api/common/core/src/main/java/com/zhengqing/common/core/config/interceptor/HandlerInterceptorForTokenUser.java b/smallboot-api/common/core/src/main/java/com/zhengqing/common/core/config/interceptor/HandlerInterceptorForTokenUser.java new file mode 100644 index 0000000..74db3a3 --- /dev/null +++ b/smallboot-api/common/core/src/main/java/com/zhengqing/common/core/config/interceptor/HandlerInterceptorForTokenUser.java @@ -0,0 +1,63 @@ +package com.zhengqing.common.core.config.interceptor; + +import cn.dev33.satoken.stp.StpUtil; +import cn.hutool.json.JSONUtil; +import com.zhengqing.common.auth.model.bo.JwtUserBO; +import com.zhengqing.common.base.constant.AppConstant; +import com.zhengqing.common.base.context.JwtCustomUserContext; +import com.zhengqing.common.base.context.SysUserContext; +import com.zhengqing.common.base.context.UmsUserContext; +import com.zhengqing.common.core.config.WebAppConfig; +import org.apache.commons.lang3.StringUtils; +import org.springframework.web.servlet.HandlerInterceptor; +import org.springframework.web.servlet.ModelAndView; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +/** + *

拦截器 -- token用户信息

+ * + * @author zhengqingya + * @description 注册使用参考 {@link WebAppConfig} + * @date 2022/1/10 16:28 + */ +public class HandlerInterceptorForTokenUser implements HandlerInterceptor { + + /** + * 在业务处理器处理请求之前被调用。预处理,可以进行编码、安全控制、权限校验等处理 + * {@link com.zhengqing.gateway.filter.AuthFilter#filter } + */ + @Override + public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { + String token = request.getHeader(AppConstant.REQUEST_HEADER_TOKEN); + if (StringUtils.isBlank(token)) { + return true; + } + JwtUserBO jwtUserBO = JSONUtil.toBean(StpUtil.getLoginId().toString(), JwtUserBO.class); + SysUserContext.setUserId(jwtUserBO.getUserId()); + SysUserContext.setUsername(jwtUserBO.getUserName()); + return true; + } + + /** + * 在业务处理器处理请求执行完成后,生成视图之前执行。 + * 后处理(调用了Service并返回ModelAndView,但未进行页面渲染),有机会修改ModelAndView + */ + @Override + public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { + HandlerInterceptor.super.postHandle(request, response, handler, modelAndView); + } + + /** + * 在DispatcherServlet完全处理完请求后被调用,可用于清理资源等。返回处理(已经渲染了页面) + */ + @Override + public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { + HandlerInterceptor.super.afterCompletion(request, response, handler, ex); + SysUserContext.remove(); + UmsUserContext.remove(); + JwtCustomUserContext.remove(); + } + +} diff --git a/smallboot-api/common/core/src/main/java/com/zhengqing/common/core/config/threadpool/NativeAsyncTaskExecutePool.java b/smallboot-api/common/core/src/main/java/com/zhengqing/common/core/config/threadpool/NativeAsyncTaskExecutePool.java new file mode 100644 index 0000000..0098491 --- /dev/null +++ b/smallboot-api/common/core/src/main/java/com/zhengqing/common/core/config/threadpool/NativeAsyncTaskExecutePool.java @@ -0,0 +1,71 @@ +package com.zhengqing.common.core.config.threadpool; + +import com.zhengqing.common.base.constant.ThreadPoolConstant; +import lombok.extern.slf4j.Slf4j; +import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler; +import org.springframework.context.annotation.Configuration; +import org.springframework.scheduling.annotation.AsyncConfigurer; +import org.springframework.scheduling.annotation.EnableAsync; +import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; + +import java.lang.reflect.Method; +import java.util.concurrent.Executor; +import java.util.concurrent.ThreadPoolExecutor; + +/** + *

原生(Spring)异步任务线程池装配类

+ * + * @author zhengqingya + * @description 配置默认的线程池,重写spring默认线程池的方式使用的时候,只需要加@Async注解就可以,不用去声明线程池类。 + * @date 2021/5/27 10:31 + */ +@Slf4j +@EnableAsync +@Configuration +public class NativeAsyncTaskExecutePool implements AsyncConfigurer { + + @Override + public Executor getAsyncExecutor() { + ThreadPoolTaskExecutor threadPoolTaskExecutor = new ThreadPoolTaskExecutor(); + // 核心线程池大小 + threadPoolTaskExecutor.setCorePoolSize(5); + // 最大线程数 + threadPoolTaskExecutor.setMaxPoolSize(10); + // 队列容量 + threadPoolTaskExecutor.setQueueCapacity(200); + // 活跃时间 60s + threadPoolTaskExecutor.setKeepAliveSeconds(60); + // 线程名字前缀 + threadPoolTaskExecutor + .setThreadNamePrefix(ThreadPoolConstant.SPRING_DEFAULT_THREAD_NAME_PREFIX); + // 设置在关闭线程池时是否等待任务完成 + threadPoolTaskExecutor.setWaitForTasksToCompleteOnShutdown(true); + // 允许核心线程超时 + threadPoolTaskExecutor.setAllowCoreThreadTimeOut(true); + // 等待时间 (默认为0,此时立即停止),并没等待xx秒后强制停止 + threadPoolTaskExecutor.setAwaitTerminationSeconds(60); + // 修改拒绝策略为使用当前线程执行 + // setRejectedExecutionHandler:当pool已经达到max size的时候,如何处理新任务 + // CallerRunsPolicy:不在新线程中执行任务,而是由调用者所在的线程来执行 + threadPoolTaskExecutor + .setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy()); + //初始化线程池 + threadPoolTaskExecutor.initialize(); + return threadPoolTaskExecutor; + } + + /** + * 异步任务中异常处理 (注:只能捕获到@Async下无返回值的方法) + */ + @Override + public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() { + return new AsyncUncaughtExceptionHandler() { + @Override + public void handleUncaughtException(Throwable throwable, Method method, + Object... objects) { + log.error("exception method: 【{}】", method); + log.error("exception msg: ", throwable); + } + }; + } +} diff --git a/smallboot-api/common/core/src/main/java/com/zhengqing/common/core/config/threadpool/SmallToolsThreadPoolConfig.java b/smallboot-api/common/core/src/main/java/com/zhengqing/common/core/config/threadpool/SmallToolsThreadPoolConfig.java new file mode 100644 index 0000000..9963476 --- /dev/null +++ b/smallboot-api/common/core/src/main/java/com/zhengqing/common/core/config/threadpool/SmallToolsThreadPoolConfig.java @@ -0,0 +1,51 @@ +package com.zhengqing.common.core.config.threadpool; + +import com.zhengqing.common.base.constant.ThreadPoolConstant; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; + +import java.util.concurrent.Executor; +import java.util.concurrent.ThreadPoolExecutor; + +/** + *

自定义线程池配置

+ * + * @author zhengqingya + * @description + * @date 2021/5/27 10:03 + */ +@Configuration +public class SmallToolsThreadPoolConfig { + + @Bean(ThreadPoolConstant.SMALL_TOOLS_THREAD_POOL) + public Executor threadPoolExecutor() { + ThreadPoolTaskExecutor threadPoolTaskExecutor = new ThreadPoolTaskExecutor(); + // 核心线程池大小 + threadPoolTaskExecutor.setCorePoolSize(5); + // 最大线程数 + threadPoolTaskExecutor.setMaxPoolSize(10); + // 队列容量 + threadPoolTaskExecutor.setQueueCapacity(200); + // 活跃时间 60s + threadPoolTaskExecutor.setKeepAliveSeconds(60); + // 线程名字前缀 + threadPoolTaskExecutor + .setThreadNamePrefix(ThreadPoolConstant.SMALL_TOOLS_THREAD_NAME_PREFIX); + // 设置在关闭线程池时是否等待任务完成 + threadPoolTaskExecutor.setWaitForTasksToCompleteOnShutdown(true); + // 允许核心线程超时 + threadPoolTaskExecutor.setAllowCoreThreadTimeOut(true); + // 等待时间 (默认为0,此时立即停止),并没等待xx秒后强制停止 + threadPoolTaskExecutor.setAwaitTerminationSeconds(60); + // 修改拒绝策略为使用当前线程执行 + // setRejectedExecutionHandler:当pool已经达到max size的时候,如何处理新任务 + // CallerRunsPolicy:不在新线程中执行任务,而是由调用者所在的线程来执行 + threadPoolTaskExecutor + .setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy()); + //初始化线程池 + threadPoolTaskExecutor.initialize(); + return threadPoolTaskExecutor; + } + +} diff --git a/smallboot-api/common/core/src/main/java/com/zhengqing/common/core/custom/fieldrepeat/FieldRepeatValidator.java b/smallboot-api/common/core/src/main/java/com/zhengqing/common/core/custom/fieldrepeat/FieldRepeatValidator.java new file mode 100644 index 0000000..9190b90 --- /dev/null +++ b/smallboot-api/common/core/src/main/java/com/zhengqing/common/core/custom/fieldrepeat/FieldRepeatValidator.java @@ -0,0 +1,67 @@ +package com.zhengqing.common.core.custom.fieldrepeat; + +import javax.validation.Constraint; +import javax.validation.Payload; +import java.lang.annotation.*; + +/** + *

+ * 自定义字段对应数据库内容重复校验 注解 + *

+ * + * @author zhengqingya + * @description + * @date 2019/9/10 9:32 + */ +// 元注解: 给其他普通的标签进行解释说明 【@Retention、@Documented、@Target、@Inherited、@Repeatable】 +@Documented +/** + * 指明生命周期: RetentionPolicy.SOURCE 注解只在源码阶段保留,在编译器进行编译时它将被丢弃忽视。 RetentionPolicy.CLASS 注解只被保留到编译进行的时候,它并不会被加载到 JVM 中。 + * RetentionPolicy.RUNTIME 注解可以保留到程序运行的时候,它会被加载进入到 JVM 中,所以在程序运行时可以获取到它们。 + */ +@Retention(RetentionPolicy.RUNTIME) +/** + * 指定注解运用的地方: ElementType.ANNOTATION_TYPE 可以给一个注解进行注解 ElementType.CONSTRUCTOR 可以给构造方法进行注解 ElementType.FIELD 可以给属性进行注解 + * ElementType.LOCAL_VARIABLE 可以给局部变量进行注解 ElementType.METHOD 可以给方法进行注解 ElementType.PACKAGE 可以给一个包进行注解 + * ElementType.PARAMETER 可以给一个方法内的参数进行注解 ElementType.TYPE 可以给一个类型进行注解,比如类、接口、枚举 + */ +@Target({ElementType.PARAMETER, ElementType.FIELD, ElementType.TYPE}) +@Constraint(validatedBy = FieldRepeatValidatorClass.class) +// @Repeatable(LinkVals.class)(可重复注解同一字段,或者类,java1.8后支持) +public @interface FieldRepeatValidator { + + /** + * 表名 + */ + String tableName(); + + /** + * 数据库主键id字段属性名 - 默认为id (该值可无) + */ + String idDbName() default "id"; + + /** + * 注解属性 - 对应校验字段名组 ex: { "name", "code" } 【 注:第一个字段名为所校验的字段,其后的字段为辅助校验字段!!! 】 + */ + String[] fieldNames() default {}; + + /** + * 数据库校验字段属性名 - 对应数据库校验字段名组 ex: { "name", "code" } + */ + String[] dbFieldNames() default {}; + + /** + * 校验字段组固定值 ex: { "", "1" } + */ + String[] fieldFixedValues() default {}; + + /** + * 默认错误提示信息 + */ + String message() default "字段内容重复!"; + + Class[] groups() default {}; + + Class[] payload() default {}; + +} diff --git a/smallboot-api/common/core/src/main/java/com/zhengqing/common/core/custom/fieldrepeat/FieldRepeatValidatorClass.java b/smallboot-api/common/core/src/main/java/com/zhengqing/common/core/custom/fieldrepeat/FieldRepeatValidatorClass.java new file mode 100644 index 0000000..cb16cf5 --- /dev/null +++ b/smallboot-api/common/core/src/main/java/com/zhengqing/common/core/custom/fieldrepeat/FieldRepeatValidatorClass.java @@ -0,0 +1,46 @@ +package com.zhengqing.common.core.custom.fieldrepeat; + +import javax.validation.ConstraintValidator; +import javax.validation.ConstraintValidatorContext; + +/** + *

+ * `@FieldRepeatValidator`注解接口实现类 + *

+ * + * @author zhengqingya + * @description 技巧01:必须实现ConstraintValidator接口 + * 技巧02:实现了ConstraintValidator接口后即使不进行Bean配置,spring也会将这个类进行Bean管理 + * 技巧03:可以在实现了ConstraintValidator接口的类中依赖注入其它Bean + * 技巧04:实现了ConstraintValidator接口后必须重写 initialize 和 isValid 这两个方法 + * initialize 方法主要来进行初始化,通常用来获取自定义注解的属性值; + * isValid 方法主要进行校验逻辑,返回true表示校验通过,返回false表示校验失败,通常根据注解属性值和实体类属性值进行校验判断 [Object:校验字段的属性值] + * @date 2019/9/10 9:22 + */ +public class FieldRepeatValidatorClass implements ConstraintValidator { + + private String tableName; + private String idDbName; + private String[] fieldNames; + private String[] dbFieldNames; + private String[] fieldFixedValues; + private String message; + + @Override + public void initialize(FieldRepeatValidator fieldRepeatValidator) { + this.tableName = fieldRepeatValidator.tableName(); + this.idDbName = fieldRepeatValidator.idDbName(); + this.fieldNames = fieldRepeatValidator.fieldNames(); + this.dbFieldNames = fieldRepeatValidator.dbFieldNames(); + this.fieldFixedValues = fieldRepeatValidator.fieldFixedValues(); + this.message = fieldRepeatValidator.message(); + } + + @Override + public boolean isValid(Object o, ConstraintValidatorContext constraintValidatorContext) { + FieldRepeatValidatorUtil fieldRepeatValidator = + new FieldRepeatValidatorUtil(this.tableName, this.idDbName, this.fieldNames, this.dbFieldNames, this.fieldFixedValues, o, this.message); + return fieldRepeatValidator.fieldRepeat(); + } + +} diff --git a/smallboot-api/common/core/src/main/java/com/zhengqing/common/core/custom/fieldrepeat/FieldRepeatValidatorUtil.java b/smallboot-api/common/core/src/main/java/com/zhengqing/common/core/custom/fieldrepeat/FieldRepeatValidatorUtil.java new file mode 100644 index 0000000..780e046 --- /dev/null +++ b/smallboot-api/common/core/src/main/java/com/zhengqing/common/core/custom/fieldrepeat/FieldRepeatValidatorUtil.java @@ -0,0 +1,210 @@ +package com.zhengqing.common.core.custom.fieldrepeat; + +import com.zhengqing.common.base.exception.MyException; +import com.zhengqing.common.db.mapper.MyBaseMapper; +import com.zhengqing.common.base.util.ApplicationContextUtil; +import com.zhengqing.common.base.util.MyBeanUtil; +import com.zhengqing.common.base.util.MyStringUtil; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; +import org.springframework.util.CollectionUtils; + +import java.lang.reflect.Field; +import java.util.*; +import java.util.stream.Collectors; + +/** + *

+ * 数据库字段内容重复判断处理工具类 + *

+ * + * @author zhengqingya + * @date 2019/9/10 9:28 + */ +@Slf4j +public class FieldRepeatValidatorUtil { + + /** + * 表名 + */ + private String TABLE_NAME; + + /** + * 数据库对应实体类主键id字段属性名 + */ + private String idName; + /** + * 数据库主键id字段属性名 + */ + private String idDbName; + + /** + * 数据库主键id字段属性值 + */ + private Integer idValue; + + /** + * 校验字段名组 + */ + private String[] fieldNames; + + /** + * 校验辅助字段组值 - 字符串、数字、对象... + */ + private List fieldValues = new LinkedList<>(); + + /** + * 校验辅助字段组固定值 - 字符串、数字、对象... + */ + private String[] fieldFixedValues; + + /** + * 储存校验辅助字段对应数据库字段值 + */ + private List DB_FIELDS; + + /** + * 顶级父包名 + */ + private final String PACKAGE_NAME = "java.lang.Object"; + + /** + * 实体类对象值 + */ + private Object object; + + /** + * 错误提示信息 + */ + private String message; + + /** + * @param tableName: 数据库表名 + * @param idDbName: 数据库主键id字段属性名 + * @param fieldNames:校验字段组 + * @param dbFieldNames:数据库校验字段组 + * @param fieldFixedValues:校验字段组固定值 + * @param object:对象数据 + * @param message:回调到前端提示消息 + */ + public FieldRepeatValidatorUtil(String tableName, String idDbName, String[] fieldNames, String[] dbFieldNames, + String[] fieldFixedValues, Object object, String message) { + this.TABLE_NAME = tableName; + + this.idDbName = idDbName; + // id 转 驼峰命名 + this.idName = MyStringUtil.dbStrToHumpLower(idDbName); + + this.fieldNames = fieldNames; + this.DB_FIELDS = new ArrayList<>(Arrays.asList(dbFieldNames)); + this.fieldFixedValues = fieldFixedValues; + this.object = object; + this.message = message; + this.getFieldValue(); + } + + /** + * 校验数据 + * + * @return boolean + */ + public boolean fieldRepeat() { + // 4、 校验字段内容是否重复 + // 工厂模式 + ar动态语法 -> 【这里已修改为`MyBaseMapper`方式查询数据】 + // Model entity = (Model)object; + // if (object.getClass().getAnnotation(TableName.class) == null) { + // try { + // entity = (Model)object.getClass().getSuperclass().newInstance(); + // } catch (Exception e) { + // e.printStackTrace(); + // } + // } + // list = entity.selectList((Wrapper)new QueryWrapper().allEq(queryMap)); + + // 5、拼接sql + Map queryMap = new HashMap<>(5); + if (this.fieldFixedValues.length == 0) { + for (int i = 0; i < this.fieldNames.length; i++) { + queryMap.put(this.DB_FIELDS.get(i), this.fieldValues.get(i)); + } + } else { + for (int i = 0; i < this.fieldNames.length; i++) { + if (StringUtils.isBlank(this.fieldFixedValues[i])) { + queryMap.put(this.DB_FIELDS.get(i), this.fieldValues.get(i)); + } else { + queryMap.put(this.DB_FIELDS.get(i), this.fieldFixedValues[i]); + } + + } + } + + List> list = + ApplicationContextUtil.getApplicationContext().getBean(MyBaseMapper.class).selectList(this.TABLE_NAME, queryMap); + + // 6、如果数据重复返回false -> 再返回自定义错误消息到前端 + if (!CollectionUtils.isEmpty(list)) { + if (this.idValue == null) { + throw new MyException(this.message); + } else { + if (list.size() > 1) { + throw new MyException(this.message); + } + // 获取list中指定字段属性值 - 这里只获取主键id + List idList = (List) MyBeanUtil.getFieldList(list, this.idDbName); + boolean isContainsIdValue = false; + for (Object itemId : idList) { + if (itemId.toString().equals(this.idValue.toString())) { + isContainsIdValue = true; + break; + } + } + if (!isContainsIdValue) { + throw new MyException(this.message); + } + } + } + return true; + } + + /** + * 获取主键id、校验字段、以及对应数据库字段值 + */ + private void getFieldValue() { + try { + // 1、获取所有的字段 + Class clz = this.object.getClass(); + // 当父类为null的时候说明到达了最上层的父类(Object类) -> 作递归取父类属性值使用 + Map fieldMap = new HashMap<>(); + while (clz != null && !this.PACKAGE_NAME.equals(clz.getName().toLowerCase())) { + fieldMap.putAll( + Arrays.stream(clz.getDeclaredFields()).collect(Collectors.toMap(Field::getName, field -> field))); + // 得到父类,然后赋给自己 + clz = clz.getSuperclass(); + } + + // 2、取校验字段值 + for (int i = 0; i < this.fieldNames.length; i++) { + Field field = fieldMap.get(this.fieldNames[i]); + if (field == null) { + this.fieldValues.add(null); + } else { + // 设置对象中成员 属性private为可读 + field.setAccessible(true); + // 校验字段名的值 【 fieldNames中第一个字段为校验字段,其后为辅助校验字段 】 + Object fieldValue = field.get(this.object); + this.fieldValues.add(fieldValue); + } + } + + // 3、取主键id字段值 -> 作用:判断是插入还是更新操作 + Field fieldId = fieldMap.get(this.idName); + if (fieldId != null) { + fieldId.setAccessible(true); + this.idValue = (Integer) fieldId.get(this.object); + } + } catch (Exception e) { + throw new MyException("数据库字段内容验重校验取值失败:" + e.toString()); + } + } + +} diff --git a/smallboot-api/common/core/src/main/java/com/zhengqing/common/core/custom/limit/ApiLimit.java b/smallboot-api/common/core/src/main/java/com/zhengqing/common/core/custom/limit/ApiLimit.java new file mode 100644 index 0000000..d798737 --- /dev/null +++ b/smallboot-api/common/core/src/main/java/com/zhengqing/common/core/custom/limit/ApiLimit.java @@ -0,0 +1,54 @@ +package com.zhengqing.common.core.custom.limit; + +import org.redisson.api.RateIntervalUnit; +import org.redisson.api.RateType; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + *

自定义注解-基于redis的限流

+ * + * @author zhengqingya + * @description + * @date 2021/10/8 9:31 + */ +// 作用到方法上 +@Target(ElementType.METHOD) +// 运行时有效 +@Retention(RetentionPolicy.RUNTIME) +public @interface ApiLimit { + + /** + * 限流器key + */ + String key(); + + /** + * 速率类型,默认所有客户端加总限流 + */ + RateType rateType() default RateType.OVERALL; + + /** + * 速率数,默认1 + */ + long rate() default 1; + + /** + * 速率间隔时间,默认3秒 + */ + long rateInterval() default 3; + + /** + * 速率间隔时间单位,默认秒 + */ + RateIntervalUnit rateIntervalUnit() default RateIntervalUnit.SECONDS; + + /** + * 默认限流提示信息 + */ + String msg() default "操作频繁,请稍后再试!"; + +} diff --git a/smallboot-api/common/core/src/main/java/com/zhengqing/common/core/custom/limit/ApiLimitAspect.java b/smallboot-api/common/core/src/main/java/com/zhengqing/common/core/custom/limit/ApiLimitAspect.java new file mode 100644 index 0000000..8a03595 --- /dev/null +++ b/smallboot-api/common/core/src/main/java/com/zhengqing/common/core/custom/limit/ApiLimitAspect.java @@ -0,0 +1,79 @@ +package com.zhengqing.common.core.custom.limit; + +import cn.hutool.core.lang.Assert; +import com.zhengqing.common.base.exception.MyException; +import com.zhengqing.common.base.util.SpringElUtil; +import lombok.SneakyThrows; +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.reflect.MethodSignature; +import org.redisson.api.RRateLimiter; +import org.redisson.api.RateIntervalUnit; +import org.redisson.api.RateType; +import org.redisson.api.RedissonClient; +import org.springframework.stereotype.Component; + +import javax.annotation.Resource; +import java.lang.reflect.Method; +import java.util.concurrent.TimeUnit; + +/** + *

aop切面-redis限流处理

+ * + * @author zhengqingya + * @description + * @date 2021/10/8 9:36 + */ +@Slf4j +@Aspect +@Component +public class ApiLimitAspect { + + @Resource + private RedissonClient redissonClient; + + /** + * 【环绕通知】 用于拦截指定方法,判断用户表单保存操作是否属于重复提交 + * 定义切入点表达式: execution(public * (…)) 表达式解释: execution:主体 public:可省略 *:标识方法的任意返回值 任意包+类+方法(…) 任意参数 + * com.zhengqing.mall.controller.*Controller.*(..)) : 标识AOP所切服务的包名,即需要进行横切的业务类 .*Controller : 标识类名,*即所有类 .*(..) : + * 标识任何方法名,括号表示参数,两个点表示任何参数类型 + * + * @param pjp 切入点对象 + * @param apiLimit 自定义的注解对象 + * @return java.lang.Object + */ + @SneakyThrows + @Around("( execution(* com.zhengqing.*..*.*Controller.*(..))" + + " || execution(* com.zhengqing.*.feign.*(..)) ) && @annotation(apiLimit)") + public Object doAround(ProceedingJoinPoint pjp, ApiLimit apiLimit) { + // 数据获取 + String key = apiLimit.key(); + Assert.notBlank(key, "限流器key不能为空!"); + + // 支持spring El表达式 + Method method = ((MethodSignature) pjp.getSignature()).getMethod(); + Object[] args = pjp.getArgs(); + key = SpringElUtil.parse(key, method, args); + + RateType rateType = apiLimit.rateType(); + long rate = apiLimit.rate(); + long rateInterval = apiLimit.rateInterval(); + RateIntervalUnit rateIntervalUnit = apiLimit.rateIntervalUnit(); + String msg = apiLimit.msg(); + + // 1、声明一个限流器 + RRateLimiter rRateLimiter = this.redissonClient.getRateLimiter(key); + // 2、设置速率,[rateInterval]秒中产生[rate]个令牌 + rRateLimiter.trySetRate(rateType, rate, rateInterval, rateIntervalUnit); + rRateLimiter.expire(1, TimeUnit.HOURS); + // 3、试图获取一个令牌,获取到返回true + if (rRateLimiter.tryAcquire()) { + return pjp.proceed(); + } else { + throw new MyException(msg); + } + } + +} diff --git a/smallboot-api/common/core/src/main/java/com/zhengqing/common/core/custom/lock/RedisLock.java b/smallboot-api/common/core/src/main/java/com/zhengqing/common/core/custom/lock/RedisLock.java new file mode 100644 index 0000000..d72e80d --- /dev/null +++ b/smallboot-api/common/core/src/main/java/com/zhengqing/common/core/custom/lock/RedisLock.java @@ -0,0 +1,44 @@ +package com.zhengqing.common.core.custom.lock; + +import java.lang.annotation.*; +import java.util.concurrent.TimeUnit; + +/** + *

自定义注解-基于redisson的分布式锁

+ * + * @author zhengqingya + * @description + * @date 2022/1/18 14:22 + */ +@Target({ElementType.METHOD}) +@Retention(RetentionPolicy.RUNTIME) +@Inherited +public @interface RedisLock { + + /** + * 锁的资源,key。支持spring El表达式 + */ +// @AliasFor("key") + String key() default "'smallboot:redis_lock'"; + + /** + * 锁类型 + */ + RedisLockType lockType() default RedisLockType.REENTRANT_LOCK; + + /** + * 获取锁等待时间,默认3秒 + */ + long waitTime() default 3000L; + + /** + * 锁自动释放时间,默认30秒 + */ + long leaseTime() default 30000L; + + /** + * 时间单位,默认毫秒(获取锁等待时间和持锁时间都用此单位) + */ + TimeUnit unit() default TimeUnit.MILLISECONDS; + +} diff --git a/smallboot-api/common/core/src/main/java/com/zhengqing/common/core/custom/lock/RedisLockAspect.java b/smallboot-api/common/core/src/main/java/com/zhengqing/common/core/custom/lock/RedisLockAspect.java new file mode 100644 index 0000000..50f0a7e --- /dev/null +++ b/smallboot-api/common/core/src/main/java/com/zhengqing/common/core/custom/lock/RedisLockAspect.java @@ -0,0 +1,82 @@ +package com.zhengqing.common.core.custom.lock; + +import com.zhengqing.common.base.util.SpringElUtil; +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.reflect.MethodSignature; +import org.redisson.api.RLock; +import org.redisson.api.RedissonClient; +import org.redisson.spring.starter.RedissonAutoConfiguration; +import org.springframework.boot.autoconfigure.AutoConfigureAfter; +import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; +import org.springframework.stereotype.Component; + +import javax.annotation.Resource; +import java.lang.reflect.Method; + +/** + *

aop切面-redisson分布式锁

+ * + * @author zhengqingya + * @description + * @date 2022/1/18 14:24 + */ +@Slf4j +@Aspect +@Component +@ConditionalOnBean(RedissonClient.class) +@AutoConfigureAfter(RedissonAutoConfiguration.class) +public class RedisLockAspect { + + @Resource + private RedissonClient redissonClient; + + @Around("@annotation(com.zhengqing.common.core.custom.lock.RedisLock)") + public Object around(ProceedingJoinPoint pjp) throws Throwable { + Method method = ((MethodSignature) pjp.getSignature()).getMethod(); + RedisLock redisLock = method.getAnnotation(RedisLock.class); + String key = redisLock.key(); + Object[] args = pjp.getArgs(); + // 支持spring El表达式 + key = SpringElUtil.parse(key, method, args); + // 获取锁 + RLock lock = this.getLock(key, redisLock); + lock.lock(redisLock.leaseTime(), redisLock.unit()); + try { + return pjp.proceed(); + } catch (Exception e) { + throw e; + } finally { + // 释放锁 + lock.unlock(); + } + } + + + /** + * 获取锁 + * + * @param key key + * @param redisLock 分布式锁注解 + * @return 锁 + * @author zhengqingya + * @date 2022/1/18 14:28 + */ + private RLock getLock(String key, RedisLock redisLock) { + switch (redisLock.lockType()) { + case REENTRANT_LOCK: + return this.redissonClient.getLock(key); + case FAIR_LOCK: + return this.redissonClient.getFairLock(key); + case READ_LOCK: + return this.redissonClient.getReadWriteLock(key).readLock(); + case WRITE_LOCK: + return this.redissonClient.getReadWriteLock(key).writeLock(); + default: + throw new RuntimeException("do not support lock type:" + redisLock.lockType().name()); + } + } + +} diff --git a/smallboot-api/common/core/src/main/java/com/zhengqing/common/core/custom/lock/RedisLockType.java b/smallboot-api/common/core/src/main/java/com/zhengqing/common/core/custom/lock/RedisLockType.java new file mode 100644 index 0000000..50c8e12 --- /dev/null +++ b/smallboot-api/common/core/src/main/java/com/zhengqing/common/core/custom/lock/RedisLockType.java @@ -0,0 +1,32 @@ +package com.zhengqing.common.core.custom.lock; + +/** + *

基于redisson的分布式锁类型

+ * + * @author zhengqingya + * @description https://www.bookstack.cn/read/redisson-wiki-zh/8.-%E5%88%86%E5%B8%83%E5%BC%8F%E9%94%81%E5%92%8C%E5%90%8C%E6%AD%A5%E5%99%A8.md + * @date 2022/1/18 14:23 + */ +public enum RedisLockType { + + /** + * 可重入锁 + */ + REENTRANT_LOCK, + + /** + * 公平锁 + */ + FAIR_LOCK, + + /** + * 读锁 + */ + READ_LOCK, + + /** + * 写锁 + */ + WRITE_LOCK; + +} diff --git a/smallboot-api/common/core/src/main/java/com/zhengqing/common/core/custom/parameter/ParamCheck.java b/smallboot-api/common/core/src/main/java/com/zhengqing/common/core/custom/parameter/ParamCheck.java new file mode 100644 index 0000000..e14a386 --- /dev/null +++ b/smallboot-api/common/core/src/main/java/com/zhengqing/common/core/custom/parameter/ParamCheck.java @@ -0,0 +1,23 @@ +package com.zhengqing.common.core.custom.parameter; + +import com.zhengqing.common.base.exception.ParameterException; + +/** + *

+ * 参数校验 + *

+ * + * @author zhengqingya + * @description + * @date 2020/8/1 18:08 + */ +public interface ParamCheck { + + /** + * 传入参数验证 + * + * @throws ParameterException 参数异常 + */ + void checkParam() throws ParameterException; + +} diff --git a/smallboot-api/common/core/src/main/java/com/zhengqing/common/core/custom/post/RequestPostSingleParam.java b/smallboot-api/common/core/src/main/java/com/zhengqing/common/core/custom/post/RequestPostSingleParam.java new file mode 100644 index 0000000..370b4b5 --- /dev/null +++ b/smallboot-api/common/core/src/main/java/com/zhengqing/common/core/custom/post/RequestPostSingleParam.java @@ -0,0 +1,53 @@ +package com.zhengqing.common.core.custom.post; + +import org.springframework.core.annotation.AliasFor; +import org.springframework.web.bind.annotation.ValueConstants; + +import java.lang.annotation.*; + +/** + *

+ * 自定义注解`RequestPostSingleParam` - 处理接收单个参数的`post`请求 + *

+ * + * @author zhengqingya + * @description 使用时注意,用了`RequestPostSingleParam`接收参数就只能作用于一个参数,不能在controller层方法中写多个参数了!!! + * @date 2021/1/13 14:41 + */ +@Target(ElementType.PARAMETER) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface RequestPostSingleParam { + + /** + * Alias for {@link #name}. + */ + @AliasFor("name") + String value() default ""; + + /** + * The name of the request parameter to bind to. + * + * @since 4.2 + */ + @AliasFor("value") + String name() default ""; + + /** + * Whether the parameter is required. + *

+ * Defaults to {@code true}, leading to an exception being thrown if the parameter is missing in the request. Switch + * this to {@code false} if you prefer a {@code null} value if the parameter is not present in the request. + *

+ * Alternatively, provide a {@link #defaultValue}, which implicitly sets this flag to {@code false}. + */ + boolean required() default true; + + /** + * The default value to use as a fallback when the request parameter is not provided or has an empty value. + *

+ * Supplying a default value implicitly sets {@link #required} to {@code false}. + */ + String defaultValue() default ValueConstants.DEFAULT_NONE; + +} diff --git a/smallboot-api/common/core/src/main/java/com/zhengqing/common/core/custom/post/RequestPostSingleParamMethodArgumentResolver.java b/smallboot-api/common/core/src/main/java/com/zhengqing/common/core/custom/post/RequestPostSingleParamMethodArgumentResolver.java new file mode 100644 index 0000000..7bdb406 --- /dev/null +++ b/smallboot-api/common/core/src/main/java/com/zhengqing/common/core/custom/post/RequestPostSingleParamMethodArgumentResolver.java @@ -0,0 +1,113 @@ +package com.zhengqing.common.core.custom.post; + +import cn.hutool.json.JSONUtil; +import com.google.common.collect.Maps; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.beanutils.ConvertUtils; +import org.apache.commons.lang3.StringUtils; +import org.springframework.core.MethodParameter; +import org.springframework.web.bind.support.WebDataBinderFactory; +import org.springframework.web.context.request.NativeWebRequest; +import org.springframework.web.method.support.HandlerMethodArgumentResolver; +import org.springframework.web.method.support.ModelAndViewContainer; + +import javax.servlet.http.HttpServletRequest; +import java.io.BufferedReader; +import java.io.IOException; +import java.util.Map; +import java.util.Objects; + +/** + *

+ * 编写参数解析器 + *

+ * + * @author zhengqingya + * @description + * @date 2021/1/13 14:41 + */ +@Slf4j +public class RequestPostSingleParamMethodArgumentResolver implements HandlerMethodArgumentResolver { + + private static final String POST = "post"; + private static final String APPLICATION_JSON = "application/json"; + + /** + * 判断是否需要处理该参数 + * + * @param parameter the method parameter to check + * @return {@code true} if this resolver supports the supplied parameter; {@code false} otherwise + */ + @Override + public boolean supportsParameter(MethodParameter parameter) { + // 只处理带有@RequestPostSingleParam注解的参数 + return parameter.hasParameterAnnotation(RequestPostSingleParam.class); + } + + @Override + public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, + NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception { + HttpServletRequest servletRequest = webRequest.getNativeRequest(HttpServletRequest.class); + String contentType = Objects.requireNonNull(servletRequest).getContentType(); + + if (contentType == null || !contentType.contains(APPLICATION_JSON)) { + log.error("《RequestPostSingleParam》 contentType需为【{}】", APPLICATION_JSON); + throw new RuntimeException("《RequestPostSingleParam》 contentType需为application/json"); + } + + if (!POST.equalsIgnoreCase(servletRequest.getMethod())) { + log.error("《RequestPostSingleParam》 请求类型必须为post"); + throw new RuntimeException("《RequestPostSingleParam》 请求类型必须为post"); + } + return this.bindRequestParams(parameter, servletRequest); + } + + private Object bindRequestParams(MethodParameter parameter, HttpServletRequest servletRequest) { + RequestPostSingleParam requestPostSingleParam = parameter.getParameterAnnotation(RequestPostSingleParam.class); + Class parameterType = parameter.getParameterType(); + String requestBody = this.getRequestBody(servletRequest); + Map paramObj = JSONUtil.toBean(requestBody, Map.class); + if (paramObj == null) { + paramObj = Maps.newHashMap(); + } + // if (paramObj.size() > 1) { + // throw new RuntimeException("《RequestPostSingleParam》 post请求只支持接收单个参数!"); + // } + + String parameterName = StringUtils.isBlank(requestPostSingleParam.value()) ? parameter.getParameterName() + : requestPostSingleParam.value(); + Object value = paramObj.get(parameterName); + + if (requestPostSingleParam.required()) { + if (value == null) { + log.error("《RequestPostSingleParam》 require=true,参数【{}】不能为空!", parameterName); + throw new RuntimeException("《RequestPostSingleParam》 " + parameterName + "不能为空!"); + } + } + + return ConvertUtils.convert(value, parameterType); + } + + /** + * 获取请求body + * + * @param servletRequest: request + * @return 请求body + */ + private String getRequestBody(HttpServletRequest servletRequest) { + StringBuilder stringBuilder = new StringBuilder(); + try { + BufferedReader reader = servletRequest.getReader(); + char[] buf = new char[1024]; + int length; + while ((length = reader.read(buf)) != -1) { + stringBuilder.append(buf, 0, length); + } + } catch (IOException e) { + log.error("《RequestPostSingleParam》 读取流异常", e); + throw new RuntimeException("《RequestPostSingleParam》 读取流异常"); + } + return stringBuilder.toString(); + } + +} diff --git a/smallboot-api/common/core/src/main/java/com/zhengqing/common/core/custom/post/RequestPostSingleParamResolverConfig.java b/smallboot-api/common/core/src/main/java/com/zhengqing/common/core/custom/post/RequestPostSingleParamResolverConfig.java new file mode 100644 index 0000000..b047bce --- /dev/null +++ b/smallboot-api/common/core/src/main/java/com/zhengqing/common/core/custom/post/RequestPostSingleParamResolverConfig.java @@ -0,0 +1,27 @@ +package com.zhengqing.common.core.custom.post; + +import org.springframework.context.annotation.Configuration; +import org.springframework.web.method.support.HandlerMethodArgumentResolver; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +import java.util.List; + +/** + *

+ * 注册参数解析器 + *

+ * + * @author zhengqingya + * @description + * @date 2021/1/13 14:41 + */ +@Configuration +public class RequestPostSingleParamResolverConfig implements WebMvcConfigurer { + + @Override + public void addArgumentResolvers(List resolvers) { + resolvers.add(new RequestPostSingleParamMethodArgumentResolver()); + WebMvcConfigurer.super.addArgumentResolvers(resolvers); + } + +} diff --git a/smallboot-api/common/core/src/main/java/com/zhengqing/common/core/custom/repeatsubmit/NoRepeatSubmit.java b/smallboot-api/common/core/src/main/java/com/zhengqing/common/core/custom/repeatsubmit/NoRepeatSubmit.java new file mode 100644 index 0000000..feda29b --- /dev/null +++ b/smallboot-api/common/core/src/main/java/com/zhengqing/common/core/custom/repeatsubmit/NoRepeatSubmit.java @@ -0,0 +1,26 @@ +package com.zhengqing.common.core.custom.repeatsubmit; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + *

+ * 注解:校验表单重复提交 + *

+ * + * @author zhengqingya + * @description + * @date 2019/11/27 9:59 + */ +// 作用到方法上 +@Target(ElementType.METHOD) +// 运行时有效 +@Retention(RetentionPolicy.RUNTIME) +public @interface NoRepeatSubmit { + /** + * 默认时间3秒 + */ + int time() default 3 * 1000; +} diff --git a/smallboot-api/common/core/src/main/java/com/zhengqing/common/core/custom/repeatsubmit/NoRepeatSubmitAop.java b/smallboot-api/common/core/src/main/java/com/zhengqing/common/core/custom/repeatsubmit/NoRepeatSubmitAop.java new file mode 100644 index 0000000..c30eba6 --- /dev/null +++ b/smallboot-api/common/core/src/main/java/com/zhengqing/common/core/custom/repeatsubmit/NoRepeatSubmitAop.java @@ -0,0 +1,84 @@ +package com.zhengqing.common.core.custom.repeatsubmit; + +import com.zhengqing.common.base.exception.MyException; +import com.zhengqing.common.redis.util.RedisUtil; +import com.zhengqing.common.web.util.IpUtil; +import lombok.SneakyThrows; +import lombok.extern.slf4j.Slf4j; +import org.aspectj.lang.ProceedingJoinPoint; +import org.aspectj.lang.annotation.Around; +import org.aspectj.lang.annotation.Aspect; +import org.springframework.stereotype.Component; +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.context.request.ServletRequestAttributes; + +import javax.servlet.http.HttpServletRequest; +import java.util.Objects; + +/** + *

+ * aop切面 校验表单重复提交 + *

+ * + * @author zhengqingya + * @description + * @date 2019/11/27 11:26 + */ +@Slf4j +@Aspect +@Component +public class NoRepeatSubmitAop { + + /** + * 【环绕通知】 用于拦截指定方法,判断用户表单保存操作是否属于重复提交 + * 定义切入点表达式: execution(public * (…)) 表达式解释: + * - execution => 主体 + * - public => 可省略 + * - * => 标识方法的任意返回值 任意包+类+方法(…) 任意参数 + * - com.zhengqing.*.*.api => 标识AOP所切服务的包名,即需要进行横切的业务类 + * - .*Controller => 标识类名,*即所有类 + * - .*(..) => 标识任何方法名,括号表示参数,两个点表示任何参数类型 + * + * @param pjp 切入点对象 + * @param noRepeatSubmit 自定义的注解对象 + * @return java.lang.Object + */ + @SneakyThrows + @Around("( execution(* com.zhengqing.*.api.*Controller.*(..)) || execution(* com.zhengqing..*.api.*Controller.*(..)) ) && @annotation(noRepeatSubmit)") + public Object doAround(ProceedingJoinPoint pjp, NoRepeatSubmit noRepeatSubmit) { + HttpServletRequest request = + ((ServletRequestAttributes) Objects.requireNonNull(RequestContextHolder.getRequestAttributes())) + .getRequest(); + + // 拿到ip地址、请求路径 + String ip = IpUtil.getIpAdrress(request); + String url = request.getRequestURL().toString(); + + // 现在时间 + long now = System.currentTimeMillis(); + + // 自定义key值方式 + String key = "REQUEST_FORM_" + ip; + if (RedisUtil.hasKey(key)) { + // 上次表单提交时间 + long lastTime = Long.parseLong(RedisUtil.get(key)); + // 如果现在距离上次提交时间小于设置的默认时间 则 判断为重复提交 否则 正常提交 -> 进入业务处理 + if ((now - lastTime) > noRepeatSubmit.time()) { + // 非重复提交操作 - 重新记录操作时间 + RedisUtil.set(key, String.valueOf(now)); + // 进入处理业务 + // ApiResult result = (ApiResult)pjp.proceed(); + return pjp.proceed(); + } else { + throw new MyException("请勿重复提交!"); + } + } else { + // 这里是第一次操作 + RedisUtil.set(key, String.valueOf(now)); + // ApiResult result = (ApiResult)pjp.proceed(); + return pjp.proceed(); + } + + } + +} diff --git a/smallboot-api/common/core/src/main/java/com/zhengqing/common/core/custom/requestparamalias/RequestParamAlias.java b/smallboot-api/common/core/src/main/java/com/zhengqing/common/core/custom/requestparamalias/RequestParamAlias.java new file mode 100644 index 0000000..de9b713 --- /dev/null +++ b/smallboot-api/common/core/src/main/java/com/zhengqing/common/core/custom/requestparamalias/RequestParamAlias.java @@ -0,0 +1,18 @@ +package com.zhengqing.common.core.custom.requestparamalias; + +import java.lang.annotation.*; + +/** + *

自定义注解`@RequestParamAlias` - get请求`@ModelAttribute`接收对象属性中的字段别名设置

+ * + * @author zhengqingya + * @description 通过自定义spring属性编辑器解决 + * tips: 字段别名注解`@JsonProperty`和`@JsonAlias`应用于post请求,不支持get请求,所以这里才单独自定义注解 + * @date 2022/10/20 10:58 + */ +@Target(ElementType.FIELD) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface RequestParamAlias { + String value(); +} diff --git a/smallboot-api/common/core/src/main/java/com/zhengqing/common/core/custom/requestparamalias/RequestParamAliasBean.java b/smallboot-api/common/core/src/main/java/com/zhengqing/common/core/custom/requestparamalias/RequestParamAliasBean.java new file mode 100644 index 0000000..b599621 --- /dev/null +++ b/smallboot-api/common/core/src/main/java/com/zhengqing/common/core/custom/requestparamalias/RequestParamAliasBean.java @@ -0,0 +1,11 @@ +package com.zhengqing.common.core.custom.requestparamalias; + +/** + *

如果是实体类,并且用到了注解@RequestParamAlias,请实现本类

+ * + * @author zhengqingya + * @description + * @date 2022/10/20 11:18 + */ +public interface RequestParamAliasBean { +} diff --git a/smallboot-api/common/core/src/main/java/com/zhengqing/common/core/custom/requestparamalias/RequestParamAliasConfig.java b/smallboot-api/common/core/src/main/java/com/zhengqing/common/core/custom/requestparamalias/RequestParamAliasConfig.java new file mode 100644 index 0000000..86082a6 --- /dev/null +++ b/smallboot-api/common/core/src/main/java/com/zhengqing/common/core/custom/requestparamalias/RequestParamAliasConfig.java @@ -0,0 +1,60 @@ +package com.zhengqing.common.core.custom.requestparamalias; + +import org.jetbrains.annotations.NotNull; +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.config.BeanPostProcessor; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.method.support.HandlerMethodArgumentResolver; +import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter; + +import java.util.ArrayList; +import java.util.List; + +/** + *

请求的字段别名-参数解析器

+ * + * @author zhengqingya + * @description 添加到第一个参数解析器中的bean配置,通过自定义spring属性编辑器解决 + * @date 2022/10/20 10:58 + */ +@Configuration +public class RequestParamAliasConfig { + private ApplicationContext applicationContext; + + @Autowired + public void setApplicationContext(ApplicationContext applicationContext) { + this.applicationContext = applicationContext; + } + + @Bean + public RequestParamAliasProcessor requestParamAliasProcessor() { + return new RequestParamAliasProcessor(this.applicationContext); + } + + @Bean + public BeanPostProcessor beanPostProcessor() { + return new BeanPostProcessor() { + @Override + public Object postProcessBeforeInitialization(@NotNull Object bean, @NotNull String beanName) throws BeansException { + return bean; + } + + @Override + public Object postProcessAfterInitialization(@NotNull Object bean, @NotNull String beanName) throws BeansException { + if (bean instanceof RequestMappingHandlerAdapter) { + RequestMappingHandlerAdapter adapter = (RequestMappingHandlerAdapter) bean; + List resolvers = new ArrayList<>(); + resolvers.add(RequestParamAliasConfig.this.requestParamAliasProcessor()); + if (adapter.getArgumentResolvers() != null) { + resolvers.addAll(adapter.getArgumentResolvers()); + } + adapter.setArgumentResolvers(resolvers); + } + return bean; + } + }; + } +} diff --git a/smallboot-api/common/core/src/main/java/com/zhengqing/common/core/custom/requestparamalias/RequestParamAliasDataBinder.java b/smallboot-api/common/core/src/main/java/com/zhengqing/common/core/custom/requestparamalias/RequestParamAliasDataBinder.java new file mode 100644 index 0000000..ad7c67d --- /dev/null +++ b/smallboot-api/common/core/src/main/java/com/zhengqing/common/core/custom/requestparamalias/RequestParamAliasDataBinder.java @@ -0,0 +1,54 @@ +package com.zhengqing.common.core.custom.requestparamalias; + +import lombok.extern.slf4j.Slf4j; +import org.jetbrains.annotations.NotNull; +import org.springframework.beans.MutablePropertyValues; +import org.springframework.beans.PropertyValue; +import org.springframework.web.servlet.mvc.method.annotation.ExtendedServletRequestDataBinder; + +import javax.servlet.ServletRequest; +import java.util.Map; + +/** + *

请求的字段别名-数据绑定处理

+ * + * @author zhengqingya + * @description 通过自定义spring属性编辑器解决 + * @date 2022/10/20 10:59 + */ +@Slf4j +public class RequestParamAliasDataBinder extends ExtendedServletRequestDataBinder { + private final Map cacheMap; + + public RequestParamAliasDataBinder(Object target, String objectName, Map asMap) { + super(target, objectName); + this.cacheMap = asMap; + } + + /** + * 复写addBindValues方法 + * + * @param mpv 这里面存的就是请求参数的key-value + * @param req 请求本身, 这里没有用到 + */ + @Override + protected void addBindValues(@NotNull MutablePropertyValues mpv, @NotNull ServletRequest req) { + super.addBindValues(mpv, req); + Object obj; + PropertyValue pv; + log.info("字段别名-缓存,{}", this.cacheMap); + for (Map.Entry entry : this.cacheMap.entrySet()) { + String alias = entry.getKey(), fieldName = entry.getValue(); + if (mpv.contains(alias)) { + pv = mpv.getPropertyValue(alias); + if (null == pv) { + continue; + } + obj = pv.getValue(); + log.debug("字段别名-数据绑定处理,{}<>{},{}", alias, fieldName, obj); + // 给原始字段赋值 + mpv.add(fieldName, obj); + } + } + } +} diff --git a/smallboot-api/common/core/src/main/java/com/zhengqing/common/core/custom/requestparamalias/RequestParamAliasProcessor.java b/smallboot-api/common/core/src/main/java/com/zhengqing/common/core/custom/requestparamalias/RequestParamAliasProcessor.java new file mode 100644 index 0000000..5a1dced --- /dev/null +++ b/smallboot-api/common/core/src/main/java/com/zhengqing/common/core/custom/requestparamalias/RequestParamAliasProcessor.java @@ -0,0 +1,96 @@ +package com.zhengqing.common.core.custom.requestparamalias; + +import org.jetbrains.annotations.NotNull; +import org.springframework.beans.BeanUtils; +import org.springframework.context.ApplicationContext; +import org.springframework.core.MethodParameter; +import org.springframework.web.bind.WebDataBinder; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.context.request.NativeWebRequest; +import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter; +import org.springframework.web.servlet.mvc.method.annotation.ServletModelAttributeMethodProcessor; + +import javax.servlet.ServletRequest; +import java.lang.reflect.Field; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.concurrent.ConcurrentHashMap; + +/** + *

请求的字段别名-参数解析器

+ * + * @author zhengqingya + * @description 通过自定义spring属性编辑器解决 + * @date 2022/10/20 11:00 + */ +public class RequestParamAliasProcessor extends ServletModelAttributeMethodProcessor { + private static final Map, Map> PARAM_CACHE_MAP = new ConcurrentHashMap<>(); + private final ApplicationContext context; + + public RequestParamAliasProcessor(ApplicationContext applicationContext) { + super(true); + this.context = applicationContext; + } + + @Override + public boolean supportsParameter(MethodParameter parameter) { + // 接口方法不含 注解 RequestParam、入参不是 简单参数、并且参数对象内的字段包含自定义注解 RequestParamAlias + return !parameter.hasParameterAnnotation(RequestParam.class) + && !parameter.hasParameterAnnotation(RequestBody.class) + && !BeanUtils.isSimpleProperty(parameter.getParameterType()) + && Arrays.stream(parameter.getParameterType().getDeclaredFields()) + .anyMatch(field -> field.getAnnotation(RequestParamAlias.class) != null); + } + + @Override + protected void bindRequestParameters(@NotNull WebDataBinder binder, @NotNull NativeWebRequest request) { + Map asMap = this.cacheMap(Objects.requireNonNull(binder.getTarget()).getClass()); + RequestParamAliasDataBinder dataBinder = new RequestParamAliasDataBinder(binder.getTarget(), binder.getObjectName(), asMap); + RequestMappingHandlerAdapter adapter = this.context.getBean(RequestMappingHandlerAdapter.class); + Objects.requireNonNull(adapter.getWebBindingInitializer()).initBinder(dataBinder); + dataBinder.bind(Objects.requireNonNull(request.getNativeRequest(ServletRequest.class))); + super.bindRequestParameters(binder, request); + } + + private Map cacheMap(Class target) { + if (PARAM_CACHE_MAP.containsKey(target)) { + return PARAM_CACHE_MAP.get(target); + } + Map map = this.analyzeClass(target, "", ""); + PARAM_CACHE_MAP.put(target, map); + return map; + } + + private Map analyzeClass(Class target, String prtAs, String prtField) { + Field[] fields = target.getDeclaredFields(); + Map map = new HashMap<>(fields.length); + RequestParamAlias rpa; + boolean boo; + String as; + for (Field field : fields) { + boo = (null == (rpa = field.getAnnotation(RequestParamAlias.class)) || rpa.value().isEmpty()); + as = boo ? field.getName() : rpa.value(); + if (this.isExtendBean(field.getType().getInterfaces())) { + // 如果字段类实现了自定义接口,就认为是自定义是对象类,继续解析字段 + // 通过自定义注解在类上面使用,在这里判断也可以 + map.putAll(this.analyzeClass(field.getType(), prtAs + as + '.', prtField + field.getName() + '.')); + } else if (!boo || !"".equals(prtAs)) { + // 只绑定需要的映射 + map.put(prtAs + as, prtField + field.getName()); + } + } + return map; + } + + private boolean isExtendBean(Class[] interfaces) { + for (Class face : interfaces) { + if (face == RequestParamAliasBean.class) { + return true; + } + } + return false; + } +} diff --git a/smallboot-api/common/core/src/main/java/com/zhengqing/common/core/custom/validator/common/CreateGroup.java b/smallboot-api/common/core/src/main/java/com/zhengqing/common/core/custom/validator/common/CreateGroup.java new file mode 100644 index 0000000..5b8ea17 --- /dev/null +++ b/smallboot-api/common/core/src/main/java/com/zhengqing/common/core/custom/validator/common/CreateGroup.java @@ -0,0 +1,20 @@ +package com.zhengqing.common.core.custom.validator.common; + +import javax.validation.groups.Default; + +/** + *

+ * 使用groups的校验 + *

+ * + * @author zhengqingya + * @description 同一个对象要复用, 比如UserDTO在更新时候要校验userId, 在保存的时候不需要校验userId, 在两种情况下都要校验username, 那就用上groups了 + * 在需要校验的地方@Validated声明校验组 ` update(@RequestBody @Validated(Update.class) UserDTO userDTO) ` + * 在DTO中的字段上定义好groups = {}的分组类型 ` @NotNull(message = "用户id不能为空", groups = Update.class) + * 或 groups = {Create.class, Update.class} private Long userId; ` + * 【注】注意:在声明分组的时候尽量加上 extend javax.validation.groups.Default + * 否则,在你声明@Validated(Update.class)的时候,就会出现你在默认没添加groups = {}的时候的校验组@Email(message = "邮箱格式不对"),会不去校验,因为默认的校验组是groups = {Default.class}. + * @date 2019/9/9 16:51 + */ +public interface CreateGroup extends Default { +} diff --git a/smallboot-api/common/core/src/main/java/com/zhengqing/common/core/custom/validator/common/UpdateGroup.java b/smallboot-api/common/core/src/main/java/com/zhengqing/common/core/custom/validator/common/UpdateGroup.java new file mode 100644 index 0000000..07d1e2a --- /dev/null +++ b/smallboot-api/common/core/src/main/java/com/zhengqing/common/core/custom/validator/common/UpdateGroup.java @@ -0,0 +1,20 @@ +package com.zhengqing.common.core.custom.validator.common; + +import javax.validation.groups.Default; + +/** + *

+ * 使用groups的校验 + *

+ * + * @author zhengqingya + * @description 同一个对象要复用, 比如UserDTO在更新时候要校验userId, 在保存的时候不需要校验userId, 在两种情况下都要校验username, 那就用上groups了 + * 在需要校验的地方@Validated声明校验组 ` update(@RequestBody @Validated(Update.class) UserDTO userDTO) ` + * 在DTO中的字段上定义好groups = {}的分组类型 ` @NotNull(message = "用户id不能为空", groups = Update.class) + * 或 groups = {Create.class, Update.class} private Long userId; ` + * 【注】注意:在声明分组的时候尽量加上 extend javax.validation.groups.Default + * 否则,在你声明@Validated(Update.class)的时候,就会出现你在默认没添加groups = {}的时候的校验组@Email(message = "邮箱格式不对"),会不去校验,因为默认的校验组是groups = {Default.class}. + * @date 2019/9/9 16:51 + */ +public interface UpdateGroup extends Default { +} diff --git a/smallboot-api/common/core/src/main/java/com/zhengqing/common/core/custom/validator/common/ValidList.java b/smallboot-api/common/core/src/main/java/com/zhengqing/common/core/custom/validator/common/ValidList.java new file mode 100644 index 0000000..ec35643 --- /dev/null +++ b/smallboot-api/common/core/src/main/java/com/zhengqing/common/core/custom/validator/common/ValidList.java @@ -0,0 +1,143 @@ +package com.zhengqing.common.core.custom.validator.common; + +import javax.validation.Valid; +import java.util.*; + +/** + *

+ * 自定义list + *

+ * + * @author zhengqingya + * @description 解决`@Validated`只能验证单个实体类,在验证List集合时不生效问题 + * @date 2021/1/20 15:16 + */ +public class ValidList implements List { + + @Valid + private List list = new LinkedList<>(); + + @Override + public int size() { + return this.list.size(); + } + + @Override + public boolean isEmpty() { + return this.list.isEmpty(); + } + + @Override + public boolean contains(Object o) { + return this.list.contains(o); + } + + @Override + public Iterator iterator() { + return this.list.iterator(); + } + + @Override + public Object[] toArray() { + return this.list.toArray(); + } + + @Override + public T[] toArray(T[] a) { + return this.list.toArray(a); + } + + @Override + public boolean add(E e) { + return this.list.add(e); + } + + @Override + public boolean remove(Object o) { + return this.list.remove(o); + } + + @Override + public boolean containsAll(Collection c) { + return this.list.containsAll(c); + } + + @Override + public boolean addAll(Collection c) { + return this.list.addAll(c); + } + + @Override + public boolean addAll(int index, Collection c) { + return this.list.addAll(index, c); + } + + @Override + public boolean removeAll(Collection c) { + return this.list.removeAll(c); + } + + @Override + public boolean retainAll(Collection c) { + return this.list.retainAll(c); + } + + @Override + public void clear() { + this.list.clear(); + } + + @Override + public E get(int index) { + return this.list.get(index); + } + + @Override + public E set(int index, E element) { + return this.list.set(index, element); + } + + @Override + public void add(int index, E element) { + this.list.add(index, element); + } + + @Override + public E remove(int index) { + return this.list.remove(index); + } + + @Override + public int indexOf(Object o) { + return this.list.indexOf(o); + } + + @Override + public int lastIndexOf(Object o) { + return this.list.lastIndexOf(o); + } + + @Override + public ListIterator listIterator() { + return this.list.listIterator(); + } + + @Override + public ListIterator listIterator(int index) { + return this.list.listIterator(index); + } + + @Override + public List subList(int fromIndex, int toIndex) { + return this.list.subList(fromIndex, toIndex); + } + + public List getList() { + return this.list; + } + + public void setList(List list) { + this.list = list; + } + +} diff --git a/smallboot-api/common/core/src/main/java/com/zhengqing/common/core/enums/UserSexEnum.java b/smallboot-api/common/core/src/main/java/com/zhengqing/common/core/enums/UserSexEnum.java new file mode 100644 index 0000000..14030fe --- /dev/null +++ b/smallboot-api/common/core/src/main/java/com/zhengqing/common/core/enums/UserSexEnum.java @@ -0,0 +1,76 @@ +package com.zhengqing.common.core.enums; + +import com.baomidou.mybatisplus.annotation.EnumValue; +import com.google.common.collect.Lists; +import com.zhengqing.common.base.exception.MyException; +import lombok.AllArgsConstructor; +import lombok.Getter; + +import java.util.Arrays; +import java.util.List; + +/** + *

+ * 性别枚举类 + *

+ * + * @author zhengqingya + * @description 0->未知;1->男;2->女 + * @date 2020/11/28 22:56 + */ +@Getter +@AllArgsConstructor +public enum UserSexEnum { + + /** + * 未知 + */ + 未知((byte) 0, "未知"), + /** + * 男 + */ + 男((byte) 1, "男"), + /** + * 女 + */ + 女((byte) 2, "女"); + + /** + * mybatis-plus 需配置扫包 `type-enums-package` + * 类型值 + * {@link com.baomidou.mybatisplus.annotation.EnumValue} 标记数据库存的值是type + */ + @EnumValue + private final Byte type; + /** + * 类型描述 + * 标识前端展示 + */ +// @JsonValue + private final String desc; + + + private static final List LIST = Lists.newArrayList(); + + static { + LIST.addAll(Arrays.asList(UserSexEnum.values())); + } + + /** + * 根据指定的规则类型查找相应枚举类 + * + * @param type 类型 + * @return 类型枚举信息 + * @author zhengqingya + * @date 2022/1/10 12:52 + */ + public static UserSexEnum getEnum(Byte type) { + for (UserSexEnum itemEnum : LIST) { + if (itemEnum.getType().equals(type)) { + return itemEnum; + } + } + throw new MyException("未找到指定类型数据!"); + } + +} diff --git a/smallboot-api/common/core/src/main/java/com/zhengqing/common/core/exception/MyGlobalExceptionHandler.java b/smallboot-api/common/core/src/main/java/com/zhengqing/common/core/exception/MyGlobalExceptionHandler.java new file mode 100644 index 0000000..80dbda9 --- /dev/null +++ b/smallboot-api/common/core/src/main/java/com/zhengqing/common/core/exception/MyGlobalExceptionHandler.java @@ -0,0 +1,179 @@ +package com.zhengqing.common.core.exception; + +import cn.dev33.satoken.exception.NotLoginException; +import com.zhengqing.common.base.exception.MyException; +import com.zhengqing.common.base.exception.ParameterException; +import com.zhengqing.common.base.model.vo.ApiResult; +import lombok.extern.slf4j.Slf4j; +import org.springframework.dao.DuplicateKeyException; +import org.springframework.validation.BindException; +import org.springframework.web.bind.MethodArgumentNotValidException; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.RestControllerAdvice; +import org.springframework.web.servlet.NoHandlerFoundException; + +import javax.validation.ConstraintViolationException; +import javax.validation.ValidationException; +import java.lang.reflect.UndeclaredThrowableException; + +/** + *

+ * 全局异常处理器 + *

+ * + * @author zhengqingya + * @description 在spring 3.2中,新增了@ControllerAdvice + * 注解,可以用于定义@ExceptionHandler、@InitBinder、@ModelAttribute,并应用到所有@RequestMapping中 + * @date 2019/8/25 0025 18:56 + */ +@Slf4j +@RestControllerAdvice +public class MyGlobalExceptionHandler { + + /** + * 自定义异常处理 + */ + @ExceptionHandler(value = MyException.class) + public ApiResult myException(MyException e) { + log.error("自定义异常:", e); + if (e.getCode() != null) { + return ApiResult.fail(e.getCode(), e.getMessage()); + } + return ApiResult.fail(e.getMessage()); + } + + @ExceptionHandler(value = ParameterException.class) + public ApiResult myException(ParameterException e) { + log.error("参数异常:", e); + if (e.getCode() != null) { + return ApiResult.fail(e.getCode(), e.getMessage()); + } + return ApiResult.fail(e.getMessage()); + } + + // 参数校验异常处理 =========================================================================== + // MethodArgumentNotValidException是springBoot中进行绑定参数校验时的异常,需要在springBoot中处理,其他需要处理ConstraintViolationException异常进行处理. + + /** + * 方法参数校验 + */ + @ExceptionHandler(MethodArgumentNotValidException.class) + public ApiResult handleMethodArgumentNotValidException(MethodArgumentNotValidException e) { + log.error("方法参数校验:" + e.getMessage(), e); + return ApiResult.fail(e.getBindingResult().getFieldError().getDefaultMessage()); + } + + /** + * ValidationException + */ + @ExceptionHandler(ValidationException.class) + public ApiResult handleValidationException(ValidationException e) { + log.error("ValidationException:", e); + Throwable cause = e.getCause(); + if (cause == null) { + return ApiResult.fail(e.getMessage()); + } + String message = cause.getMessage(); + return ApiResult.fail(message); + } + + /** + * jsr303参数校验异常 + */ + @ExceptionHandler({BindException.class}) + public ApiResult exception(BindException e) { + log.error("BindException:", e); + return ApiResult.fail(e.getBindingResult().getFieldError().getDefaultMessage()); + } + + /** + * ConstraintViolationException + */ + @ExceptionHandler(ConstraintViolationException.class) + public ApiResult handleConstraintViolationException(ConstraintViolationException e) { + log.error("ValidationException:" + e.getMessage(), e); + return ApiResult.fail(e.getMessage()); + } + + @ExceptionHandler(IllegalArgumentException.class) + public ApiResult handleRuntimeException(IllegalArgumentException e) { + log.error("参数不合法:", e); + return ApiResult.fail(e.getMessage()); + } + + @ExceptionHandler(NoHandlerFoundException.class) + public ApiResult handlerNoFoundException(Exception e) { + log.error("404:", e); + return ApiResult.fail(404, "路径不存在,请检查路径是否正确"); + } + + @ExceptionHandler(DuplicateKeyException.class) + public ApiResult handleDuplicateKeyException(DuplicateKeyException e) { + log.error("数据重复,请检查后提交:", e); + return ApiResult.fail("数据重复,请检查后提交:" + e.getMessage()); + } + + // =============================================== + + @ExceptionHandler(RuntimeException.class) + public ApiResult handleRuntimeException(RuntimeException e) { + log.error("系统异常:", e); + return ApiResult.fail("系统异常,操作失败:" + e.getMessage()); + } + + @ExceptionHandler(NotLoginException.class) + public ApiResult handleNotLoginException(NotLoginException e) { + log.error("认证异常:", e); + return ApiResult.expired(e.getMessage()); + } + + /** + * 空指针异常 + */ + @ExceptionHandler(NullPointerException.class) + public ApiResult nullPointerExceptionHandler(NullPointerException e) { + log.error("空指针异常:", e); + return ApiResult.fail("空指针异常:" + e.getMessage()); + } + + /** + * 类型转换异常 + */ + @ExceptionHandler(ClassCastException.class) + public ApiResult classCastExceptionHandler(ClassCastException e) { + log.error("类型转换异常:", e); + return ApiResult.fail("类型转换异常:" + e.getMessage()); + } + + /** + * 数组越界异常 + */ + @ExceptionHandler(ArrayIndexOutOfBoundsException.class) + public ApiResult arrayIndexOutOfBoundsException(ArrayIndexOutOfBoundsException e) { + log.error("数组越界异常:", e); + return ApiResult.fail("数组越界异常:" + e.getMessage()); + } + + /** + * 包含调用处理程序抛出的未声明的检查异常 + */ + @ExceptionHandler({UndeclaredThrowableException.class}) + public ApiResult exception(UndeclaredThrowableException e) { + log.error("UndeclaredThrowableException:", e); + Throwable cause = e.getCause(); + if (cause == null) { + return ApiResult.fail(e.getMessage()); + } + return ApiResult.fail(500, cause.toString()); + } + + /** + * 其他错误 + */ + @ExceptionHandler({Exception.class}) + public ApiResult exception(Exception e) { + log.error("其他错误:", e); + return ApiResult.fail(500, "其他错误:" + e); + } + +} diff --git a/smallboot-api/common/core/src/main/java/com/zhengqing/common/core/util/DesUtil.java b/smallboot-api/common/core/src/main/java/com/zhengqing/common/core/util/DesUtil.java new file mode 100644 index 0000000..4a6154d --- /dev/null +++ b/smallboot-api/common/core/src/main/java/com/zhengqing/common/core/util/DesUtil.java @@ -0,0 +1,94 @@ +package com.zhengqing.common.core.util; + +import com.zhengqing.common.base.constant.AppConstant; +import lombok.SneakyThrows; +import sun.misc.BASE64Decoder; +import sun.misc.BASE64Encoder; + +import javax.crypto.Cipher; +import javax.crypto.SecretKey; +import javax.crypto.SecretKeyFactory; +import javax.crypto.spec.DESKeySpec; +import java.security.SecureRandom; + +/** + *

+ * DES 加密/解密工具类 + *

+ * + * @author zhengqingya + * @description + * @date 2020/4/12 0:40 + */ +public class DesUtil { + + private static final String DES_ALGORITHM = "DES"; + + public static void main(String[] args) { + String helloWorld = encrypt("hello world", AppConstant.DES_KEY); + System.out.println(helloWorld); + System.out.println(decrypt(helloWorld, AppConstant.DES_KEY)); + System.out.println(decrypt("UhWnn6jPKf5a+b+fzS7BqQ==", AppConstant.DES_KEY)); + } + + /** + * des加密 + * + * @param data 需要加密的参数 + * @param secretKey 自定义密钥字符串 + */ + @SneakyThrows(Exception.class) + public static String encrypt(String data, String secretKey) { + //将自定义密钥转为字节型 + byte[] encryptKey = secretKey.getBytes(); + + String encryptedData = null; + //DES算法要求一个可信任的随机数源 + SecureRandom sr = new SecureRandom(); + DESKeySpec deskey = new DESKeySpec(encryptKey); + + //创建一个密钥工厂,然后用它把DESKeySpec转换成一个SecretKey对象 + SecretKeyFactory keyFactory = SecretKeyFactory.getInstance(DES_ALGORITHM); + SecretKey key = keyFactory.generateSecret(deskey); + + //加密对象 + Cipher cipher = Cipher.getInstance(DES_ALGORITHM); + cipher.init(Cipher.ENCRYPT_MODE, key, sr); + + //加密,把字节数组编码成字符串 + encryptedData = new BASE64Encoder().encode(cipher.doFinal(data.getBytes())); + + return encryptedData; + } + + /** + * 解密字符串 + * + * @param secretData 需要解密的参数 + * @param secretKey 自定义密钥字符串 + */ + @SneakyThrows(Exception.class) + public static String decrypt(String secretData, String secretKey) { + //将自定义密钥转为字节型 + byte[] encryptKey = secretKey.getBytes(); + + String decryptedData = null; + + //DES算法要求有一个可信任的随机数源 + SecureRandom sr = new SecureRandom(); + DESKeySpec desKey = new DESKeySpec(encryptKey); + + //创建一个密钥工厂,把DESKeySpec转换成一个SecretKey对象 + SecretKeyFactory keyFactory = SecretKeyFactory.getInstance(DES_ALGORITHM); + SecretKey key = keyFactory.generateSecret(desKey); + + //解密对象 + Cipher cipher = Cipher.getInstance(DES_ALGORITHM); + cipher.init(Cipher.DECRYPT_MODE, key, sr); + + //字符串解码 ——> 字节数组,并解密 + decryptedData = new String(cipher.doFinal(new BASE64Decoder().decodeBuffer(secretData))); + return decryptedData; + } + +} diff --git a/smallboot-api/common/core/src/main/java/com/zhengqing/common/core/util/IdGeneratorUtil.java b/smallboot-api/common/core/src/main/java/com/zhengqing/common/core/util/IdGeneratorUtil.java new file mode 100644 index 0000000..9ff7346 --- /dev/null +++ b/smallboot-api/common/core/src/main/java/com/zhengqing/common/core/util/IdGeneratorUtil.java @@ -0,0 +1,126 @@ +package com.zhengqing.common.core.util; + +import cn.hutool.core.lang.Snowflake; +import cn.hutool.core.net.NetUtil; +import cn.hutool.core.util.IdUtil; +import cn.hutool.core.util.RandomUtil; +import com.zhengqing.common.base.exception.MyException; +import com.zhengqing.common.base.util.MyDateUtil; +import com.zhengqing.common.redis.constant.RedisConstant; +import com.zhengqing.common.redis.util.RedisUtil; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import javax.annotation.PostConstruct; +import java.util.concurrent.TimeUnit; + + +/** + *

Hutool之雪花算法生成唯一ID配置

+ * + * @author zhengqingya + * @description 可参考 https://www.bookstack.cn/read/hutool/bfd2d43bcada297e.md + * @date 2021/10/29 16:57 + */ +@Slf4j +@Component +public class IdGeneratorUtil { + + /** + * 终端ID + */ + private static long workerId = 0; + /** + * 数据中心ID + */ + private static long datacenterId = 1; + + private static Snowflake snowflake = IdUtil.createSnowflake(workerId, datacenterId); + + @PostConstruct + public void init() { + try { + workerId = NetUtil.ipv4ToLong(NetUtil.getLocalhostStr()); + log.info("当前机器的IP:[{}], workerId:[{}]", NetUtil.getLocalhostStr(), workerId); + } catch (Exception e) { + log.error("获取当前机器workerId 异常", e); + workerId = NetUtil.getLocalhostStr().hashCode(); + } + } + + /** + * 使用默认的 workerId 和 datacenterId + */ + public synchronized static long snowflakeId() { + return snowflake.nextId(); + } + + /** + * 字符串类型 + */ + public static String nextStrId() { + return String.valueOf(snowflakeId()); + } + + /** + * 使用自定义的 workerId 和 datacenterId + */ + public synchronized static long snowflakeId(long workerId, long datacenterId) { + return IdUtil.getSnowflake(workerId, datacenterId).nextId(); + } + + /** + * 使用redis确保分布式系统主键ID唯一性 + * + * @return ID + * @author zhengqingya + * @date 2022/7/4 12:47 + */ + public synchronized static long nextId() { + long id = snowflake.nextId(); + String key = RedisConstant.ID_GENERATE_KEY_PREFIX + id; + if (!RedisUtil.setIfAbsent(key, String.valueOf(id))) { + // 记录下重复数据 + RedisUtil.hPutIfAbsent(RedisConstant.ID_GENERATE_REPEAT_KEY, String.valueOf(id), MyDateUtil.nowStr()); + // 循环继续获取 + return snowflakeId(); + } + // 设置3分钟过期 + RedisUtil.expire(key, 3, TimeUnit.MINUTES); + return id; + } + + /** + * 随机码生成 -- 12位英文大写字母+数字 + * + * @return 随机码 + * @author zhengqingya + * @date 2022/4/20 16:48 + */ + public static String generateRandomCode() { + String key = RedisConstant.GENERATE_RANDOM_CODE_KEY; + String code = RandomUtil.randomStringUpper(12); + if (!RedisUtil.hPutIfAbsent(key, code, code)) { + // 如果重试次数超过5次则告警... + Long retryNum = RedisUtil.incrBy(RedisConstant.GENERATE_RANDOM_CODE_RETRY_NUM_KEY, 1); + if (retryNum > RedisConstant.GENERATE_RANDOM_CODE_MAX_RETRY_NUM) { + // 先删除key,防止下次进来直接异常退出程序 + RedisUtil.delete(RedisConstant.GENERATE_RANDOM_CODE_RETRY_NUM_KEY); + throw new MyException("随机码已用尽,请联系系统管理员!"); + } + + // 如果存在了,继续拿数据 + return generateRandomCode(); + } + // 正常拿到数据返回 + return code; + } + + public static void main(String[] args) { + for (int i = 0; i < 10; i++) { +// log.info("ID: {}", IdGeneratorUtil.snowflakeId(i % 2, i % 2)); + log.info("ID: {}", IdGeneratorUtil.snowflakeId()); + } + } + +} diff --git a/smallboot-api/common/db/pom.xml b/smallboot-api/common/db/pom.xml new file mode 100644 index 0000000..0b90ad0 --- /dev/null +++ b/smallboot-api/common/db/pom.xml @@ -0,0 +1,74 @@ + + + 4.0.0 + + + common + com.zhengqing + ${revision} + + + db + + ${project.artifactId} + ${revision} + jar + + + + + + + com.zhengqing + base + provided + + + + com.zhengqing + swagger + provided + + + + + org.springframework.boot + spring-boot-starter-jdbc + + + + + + mysql + mysql-connector-java + 8.0.25 + + + + + + com.alibaba + druid-spring-boot-starter + 1.2.11 + + + + + + com.baomidou + dynamic-datasource-spring-boot-starter + 3.5.1 + + + + + + com.baomidou + mybatis-plus-boot-starter + 3.5.2 + + + + diff --git a/smallboot-api/common/db/src/main/java/com/zhengqing/common/db/config/DruidConfig.java b/smallboot-api/common/db/src/main/java/com/zhengqing/common/db/config/DruidConfig.java new file mode 100644 index 0000000..f7a1cb9 --- /dev/null +++ b/smallboot-api/common/db/src/main/java/com/zhengqing/common/db/config/DruidConfig.java @@ -0,0 +1,141 @@ +//package com.zhengqing.common.db.config; +// +//import com.alibaba.druid.pool.DruidDataSource; +//import com.alibaba.druid.support.http.StatViewServlet; +//import com.alibaba.druid.support.http.WebStatFilter; +//import com.alibaba.druid.support.spring.stat.DruidStatInterceptor; +//import org.springframework.aop.support.DefaultPointcutAdvisor; +//import org.springframework.aop.support.JdkRegexpMethodPointcut; +//import org.springframework.beans.factory.annotation.Value; +//import org.springframework.boot.context.properties.ConfigurationProperties; +//import org.springframework.boot.web.servlet.FilterRegistrationBean; +//import org.springframework.boot.web.servlet.ServletRegistrationBean; +//import org.springframework.context.annotation.Bean; +//import org.springframework.context.annotation.Configuration; +//import org.springframework.context.annotation.Scope; +//import org.springframework.jdbc.datasource.DataSourceTransactionManager; +// +//import javax.sql.DataSource; +//import java.util.HashMap; +//import java.util.Map; +// +///** +// *

+// * Druid核心配置类 - 注册bean +// *

+// * +// * @author zhengqingya +// * @description Druid连接池监控平台 http://127.0.0.1:5000/druid/index.html +// * @date 2019/12/19 18:20 +// */ +//@Configuration +//public class DruidConfig { +// +// @Value("${spring.datasource.druid.stat-view-servlet.login-username}") +// private String loginUsername; +// +// @Value("${spring.datasource.druid.stat-view-servlet.login-password}") +// private String loginPassword; +// +// /** +// * 配置Druid监控 +// */ +// @Bean +// public ServletRegistrationBean druidServlet() { +// // 注册服务 +// ServletRegistrationBean servletRegistrationBean = +// new ServletRegistrationBean(new StatViewServlet(), "/druid/*"); +// // IP白名单(为空表示,所有的都可以访问,多个IP的时候用逗号隔开) +// servletRegistrationBean.addInitParameter("allow", "127.0.0.1"); +// // IP黑名单 (存在共同时,deny优先于allow) +// servletRegistrationBean.addInitParameter("deny", "127.0.0.2"); +// // 设置控制台登录的用户名和密码 +// servletRegistrationBean.addInitParameter("loginUsername", this.loginUsername); +// servletRegistrationBean.addInitParameter("loginPassword", this.loginPassword); +// // 是否能够重置数据 +// servletRegistrationBean.addInitParameter("resetEnable", "false"); +// return servletRegistrationBean; +// } +// +// /** +// * 配置web监控的filter +// */ +// @Bean +// public FilterRegistrationBean webStatFilter() { +// FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean(new WebStatFilter()); +// // 添加过滤规则 +// Map initParams = new HashMap<>(1); +// // 设置忽略请求 +// initParams.put("exclusions", "*.js,*.gif,*.jpg,*.bmp,*.png,*.css,*.ico,/druid/*"); +// filterRegistrationBean.setInitParameters(initParams); +// filterRegistrationBean.addInitParameter("profileEnable", "true"); +// filterRegistrationBean.addInitParameter("principalCookieName", "USER_COOKIE"); +// filterRegistrationBean.addInitParameter("principalSessionName", ""); +// filterRegistrationBean.addInitParameter("aopPatterns", "com.example.demo.service"); +// // 验证所有请求 +// filterRegistrationBean.addUrlPatterns("/*"); +// return filterRegistrationBean; +// } +// +// /** +// * 配置数据源 【 将所有前缀为spring.datasource下的配置项都加载到DataSource中 】 +// */ +// @Bean(name = "dataSource") +// @ConfigurationProperties(prefix = "spring.datasource.dynamic.datasource.master") +// public DataSource dataSource() { +// return new DruidDataSource(); +// } +// +// // @Bean(name = "dataSource2") +// // @ConfigurationProperties(prefix = "spring.datasource.dynamic.datasource.db-test") +// // public DataSource dataSource2() { +// // return new DruidDataSource(); +// // } +// +// /** +// * 配置事物管理器 +// */ +// @Bean(name = "transactionManager") +// public DataSourceTransactionManager transactionManager() { +// return new DataSourceTransactionManager(this.dataSource()); +// } +// +// // @Bean(name = "transactionManager2") +// // public DataSourceTransactionManager transactionManager2() { +// // return new DataSourceTransactionManager(dataSource2()); +// // } +// +// /** +// * ↓↓↓↓↓↓ 配置spring监控 ↓↓↓↓↓↓ +// * DruidStatInterceptor: druid提供的拦截器 +// */ +// @Bean +// public DruidStatInterceptor druidStatInterceptor() { +// DruidStatInterceptor dsInterceptor = new DruidStatInterceptor(); +// return dsInterceptor; +// } +// +// /** +// * 使用正则表达式配置切点 +// */ +// @Bean +// @Scope("prototype") +// public JdkRegexpMethodPointcut druidStatPointcut() { +// JdkRegexpMethodPointcut pointcut = new JdkRegexpMethodPointcut(); +// pointcut.setPattern("com.zhengqing.*.api.*"); +// return pointcut; +// } +// +// /** +// * DefaultPointcutAdvisor类定义advice及 pointcut 属性。advice指定使用的通知方式,也就是druid提供的DruidStatInterceptor类,pointcut指定切入点 +// */ +// @Bean +// public DefaultPointcutAdvisor druidStatAdvisor(DruidStatInterceptor druidStatInterceptor, +// JdkRegexpMethodPointcut druidStatPointcut) { +// DefaultPointcutAdvisor defaultPointAdvisor = new DefaultPointcutAdvisor(); +// defaultPointAdvisor.setPointcut(druidStatPointcut); +// defaultPointAdvisor.setAdvice(druidStatInterceptor); +// return defaultPointAdvisor; +// } +// +//} diff --git a/smallboot-api/common/db/src/main/java/com/zhengqing/common/db/config/mybatis/MyMetaObjectHandler.java b/smallboot-api/common/db/src/main/java/com/zhengqing/common/db/config/mybatis/MyMetaObjectHandler.java new file mode 100644 index 0000000..cf5635b --- /dev/null +++ b/smallboot-api/common/db/src/main/java/com/zhengqing/common/db/config/mybatis/MyMetaObjectHandler.java @@ -0,0 +1,88 @@ +package com.zhengqing.common.db.config.mybatis; + +import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler; +import com.zhengqing.common.base.constant.BaseConstant; +import com.zhengqing.common.base.context.JwtCustomUserContext; +import com.zhengqing.common.base.enums.AuthSourceEnum; +import com.zhengqing.common.base.model.bo.JwtCustomUserBO; +import com.zhengqing.common.db.constant.MybatisConstant; +import lombok.extern.slf4j.Slf4j; +import org.apache.ibatis.reflection.MetaObject; +import org.springframework.stereotype.Component; + +import java.util.Date; + +/** + *

+ * MyBatisPlus自定义字段自动填充处理类 - 实体类中使用 @TableField注解 + *

+ * + * @author zhengqingya + * @description 注意前端传值时要为null + * @date 2019/8/18 1:46 + */ +@Slf4j +@Component +public class MyMetaObjectHandler implements MetaObjectHandler { + + /** + * 创建 + */ + @Override + public void insertFill(MetaObject metaObject) { + // 用户id + Long userId = this.getUserId(); + // 当前时间 + Date nowDate = new Date(); + + // 判断对象中是否存在该参数,如果存在则插入数据 + if (metaObject.hasGetter(MybatisConstant.IS_DELETED)) { + this.setFieldValByName(MybatisConstant.IS_DELETED, false, metaObject); + } + if (metaObject.hasGetter(MybatisConstant.CREATE_BY)) { + this.setFieldValByName(MybatisConstant.CREATE_BY, userId, metaObject); + } + if (metaObject.hasGetter(MybatisConstant.CREATE_TIME)) { + this.setFieldValByName(MybatisConstant.CREATE_TIME, nowDate, metaObject); + } + if (metaObject.hasGetter(MybatisConstant.UPDATE_BY)) { + this.setFieldValByName(MybatisConstant.UPDATE_BY, userId, metaObject); + } + if (metaObject.hasGetter(MybatisConstant.UPDATE_TIME)) { + this.setFieldValByName(MybatisConstant.UPDATE_TIME, nowDate, metaObject); + } + } + + /** + * 更新 + */ + @Override + public void updateFill(MetaObject metaObject) { + if (metaObject.hasGetter(MybatisConstant.UPDATE_BY)) { + this.setFieldValByName(MybatisConstant.UPDATE_BY, this.getUserId(), metaObject); + } + if (metaObject.hasGetter(MybatisConstant.UPDATE_TIME)) { + this.setFieldValByName(MybatisConstant.UPDATE_TIME, new Date(), metaObject); + } + } + + /** + * 获取上下文中的用户id + * + * @return 用户id + * @author zhengqingya + * @date 2022/7/8 18:15 + */ + private Long getUserId() { + JwtCustomUserBO jwtCustomUserBO = JwtCustomUserContext.get(); + if (jwtCustomUserBO != null) { + if (AuthSourceEnum.B.getValue().equals(jwtCustomUserBO.getAuthSource())) { + return Long.valueOf(jwtCustomUserBO.getSysUserId()); + } else { + return Long.valueOf(jwtCustomUserBO.getUmsUserId()); + } + } + return Long.valueOf(BaseConstant.DEFAULT_CONTEXT_KEY_USER_ID); + } + +} diff --git a/smallboot-api/common/db/src/main/java/com/zhengqing/common/db/config/mybatis/MybatisPlusConfig.java b/smallboot-api/common/db/src/main/java/com/zhengqing/common/db/config/mybatis/MybatisPlusConfig.java new file mode 100644 index 0000000..368c54e --- /dev/null +++ b/smallboot-api/common/db/src/main/java/com/zhengqing/common/db/config/mybatis/MybatisPlusConfig.java @@ -0,0 +1,157 @@ +package com.zhengqing.common.db.config.mybatis; + +import cn.hutool.core.lang.Assert; +import com.baomidou.mybatisplus.annotation.DbType; +import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor; +import com.baomidou.mybatisplus.extension.plugins.handler.TenantLineHandler; +import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor; +import com.baomidou.mybatisplus.extension.plugins.inner.TenantLineInnerInterceptor; +import com.zhengqing.common.base.context.TenantIdContext; +import com.zhengqing.common.db.config.mybatis.data.permission.first.DataPermissionInterceptor; +import com.zhengqing.common.db.config.mybatis.data.permission.second.MyDataPermissionHandler; +import com.zhengqing.common.db.config.mybatis.data.permission.second.MyDataPermissionInterceptor; +import com.zhengqing.common.db.config.mybatis.plugins.LogicDeleteInterceptor; +import com.zhengqing.common.db.config.mybatis.plugins.SqlLogInterceptor; +import net.sf.jsqlparser.expression.Expression; +import net.sf.jsqlparser.expression.LongValue; +import org.mybatis.spring.annotation.MapperScan; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.transaction.annotation.EnableTransactionManagement; + +import javax.sql.DataSource; +import java.util.HashSet; +import java.util.Set; + +/** + *

+ * MybatisPlus配置类 + *

+ * + * @author zhengqingya + * @description + * @date 2019/8/23 9:46 + */ +@Configuration +@EnableTransactionManagement +@MapperScan({"com.zhengqing.*.mapper", "com.zhengqing.*.*.mapper"}) +public class MybatisPlusConfig { + + /** + * 需要设置租户ID的表 + */ + public static Set TENANT_ID_TABLE = new HashSet<>(); + + /** + * 需要逻辑删除的表 + */ + public static Set LOGIC_DELETE_TABLE = new HashSet<>(); + + static { +// TENANT_ID_TABLE.add("t_demo"); + LOGIC_DELETE_TABLE.add("t_sys_menu"); + LOGIC_DELETE_TABLE.add("t_sys_user"); + } + + + /** + * mybatis-plus分页插件 + * 文档:https://baomidou.com/pages/2976a3/#spring-boot + */ + @Bean + public MybatisPlusInterceptor mybatisPlusInterceptor() { + MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); + /** + * 1、mybatis-plus多租户插件 + * 文档:https://baomidou.com/pages/aef2f2/#tenantlineinnerinterceptor + */ + interceptor.addInnerInterceptor(new TenantLineInnerInterceptor(new TenantLineHandler() { + @Override + public Expression getTenantId() { + return new LongValue(TenantIdContext.getTenantId()); + } + + @Override + public String getTenantIdColumn() { + return "tenant_id"; + } + + // 这是 default 方法,默认返回 false 表示所有表都需要拼多租户条件 + @Override + public boolean ignoreTable(String tableName) { + if (!TENANT_ID_TABLE.contains(tableName)) { + // 不需要租户id + return true; + } + Boolean tenantIdFlag = TenantIdContext.getFlag(); + Assert.notNull(tenantIdFlag, "租户id不能为空!"); + return !tenantIdFlag; + } + })); + + // tips: 如果用了分页插件注意先 add TenantLineInnerInterceptor 再 add PaginationInnerInterceptor + + + /** + * 2、添加数据权限插件 + */ + MyDataPermissionInterceptor dataPermissionInterceptor = new MyDataPermissionInterceptor(); + // 添加自定义的数据权限处理器 + dataPermissionInterceptor.setDataPermissionHandler(new MyDataPermissionHandler()); + interceptor.addInnerInterceptor(dataPermissionInterceptor); + + /** + * 3、mybatis-plus分页插件 + * 文档:https://baomidou.com/pages/2976a3/#spring-boot + */ + interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); + return interceptor; + } + + /** + * mybatis-plus SQL性能分析插件【生产环境可以关闭】 性能分析拦截器,用于输出每条 SQL 语句及其执行时间 【注:3.2.0+ 已移除`PerformanceInterceptor`】 + */ + // @Bean + // @Profile({"dev", "test"}) // 设置 dev test 环境开启 + // public PerformanceInterceptor performanceInterceptor() { + // SQL 执行性能分析,开发环境使用,线上不推荐。 maxTime 指的是 sql 最大执行时长 + // performanceInterceptor.setMaxTime(3000); + // SQL是否格式化 默认false + // performanceInterceptor.setFormat(true); + // return new PerformanceInterceptor(); + // } + + /** + * sql 日志 + */ + @Bean + @ConditionalOnProperty( + value = "smallboot.mybatis-plus-sql-log", + havingValue = "true", + // true表示缺少此配置属性时也会加载该bean + matchIfMissing = true) + public SqlLogInterceptor sqlLogInterceptor() { + return new SqlLogInterceptor(); + } + + /** + * 数据权限插件 + */ + @Bean + @ConditionalOnMissingBean + public DataPermissionInterceptor dataScopeInterceptor(DataSource dataSource) { + return new DataPermissionInterceptor(dataSource); + } + + /** + * 逻辑删除插件 + */ + @Bean + @ConditionalOnMissingBean + public LogicDeleteInterceptor logicDeleteInterceptor() { + return new LogicDeleteInterceptor(); + } + +} diff --git a/smallboot-api/common/db/src/main/java/com/zhengqing/common/db/config/mybatis/data/permission/first/DataPermissionInterceptor.java b/smallboot-api/common/db/src/main/java/com/zhengqing/common/db/config/mybatis/data/permission/first/DataPermissionInterceptor.java new file mode 100644 index 0000000..7734982 --- /dev/null +++ b/smallboot-api/common/db/src/main/java/com/zhengqing/common/db/config/mybatis/data/permission/first/DataPermissionInterceptor.java @@ -0,0 +1,160 @@ +package com.zhengqing.common.db.config.mybatis.data.permission.first; + +import com.baomidou.mybatisplus.core.toolkit.PluginUtils; +import lombok.AllArgsConstructor; +import lombok.SneakyThrows; +import lombok.extern.slf4j.Slf4j; +import net.sf.jsqlparser.JSQLParserException; +import net.sf.jsqlparser.expression.Alias; +import net.sf.jsqlparser.expression.operators.conditional.AndExpression; +import net.sf.jsqlparser.parser.CCJSqlParserManager; +import net.sf.jsqlparser.parser.CCJSqlParserUtil; +import net.sf.jsqlparser.schema.Table; +import net.sf.jsqlparser.statement.select.PlainSelect; +import net.sf.jsqlparser.statement.select.Select; +import net.sf.jsqlparser.statement.select.SelectBody; +import net.sf.jsqlparser.statement.select.SetOperationList; +import org.apache.ibatis.executor.statement.StatementHandler; +import org.apache.ibatis.mapping.BoundSql; +import org.apache.ibatis.mapping.MappedStatement; +import org.apache.ibatis.mapping.SqlCommandType; +import org.apache.ibatis.plugin.*; +import org.apache.ibatis.reflection.MetaObject; +import org.apache.ibatis.reflection.SystemMetaObject; + +import javax.sql.DataSource; +import java.io.StringReader; +import java.sql.Connection; +import java.util.List; +import java.util.Properties; + + +/** + *

mybatis-plus 数据权限插件

+ * + * @author zhengqingya + * @description + * @date 2022/1/12 14:36 + */ +@Slf4j +@AllArgsConstructor +@Intercepts({@Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class})}) +public class DataPermissionInterceptor implements Interceptor { + + /** + * 数据源 + */ + private DataSource dataSource; + + @Override + public Object intercept(Invocation invocation) throws Throwable { + StatementHandler statementHandler = PluginUtils.realTarget(invocation.getTarget()); + MetaObject metaObject = SystemMetaObject.forObject(statementHandler); + // 先判断是不是SELECT操作 不是直接过滤 + MappedStatement mappedStatement = (MappedStatement) metaObject.getValue("delegate.mappedStatement"); + if (!SqlCommandType.SELECT.equals(mappedStatement.getSqlCommandType())) { + return invocation.proceed(); + } + BoundSql boundSql = (BoundSql) metaObject.getValue("delegate.boundSql"); + // 执行的SQL语句 + String originalSql = boundSql.getSql(); + // SQL语句的参数 + Object parameterObject = boundSql.getParameterObject(); + + // TODO 这里对执行SQL进行自定义处理... +// String finalSql = this.handleSql(originalSql); +// System.err.println("数据权限处理过后的SQL: " + finalSql); + + metaObject.setValue("delegate.boundSql.sql", originalSql); + return invocation.proceed(); + } + + + /** + * 改写SQL + * {@link com.baomidou.mybatisplus.extension.plugins.inner.DataPermissionInterceptor} + * + * @param originalSql 执行的SQL语句 + * @return 处理后的SQL + * @author zhengqingya + * @date 2022/1/13 10:43 + */ + private String handleSql(String originalSql) throws JSQLParserException { + CCJSqlParserManager parserManager = new CCJSqlParserManager(); + Select select = (Select) parserManager.parse(new StringReader(originalSql)); + SelectBody selectBody = select.getSelectBody(); + if (selectBody instanceof PlainSelect) { + this.setWhere((PlainSelect) selectBody); + } else if (selectBody instanceof SetOperationList) { + SetOperationList setOperationList = (SetOperationList) selectBody; + List selectBodyList = setOperationList.getSelects(); + selectBodyList.forEach(s -> this.setWhere((PlainSelect) s)); + } + return select.toString(); + } + + /** + * 设置 where 条件 -- 使用CCJSqlParser将原SQL进行解析并改写 + * + * @param plainSelect 查询对象 + */ + @SneakyThrows(Exception.class) + protected void setWhere(PlainSelect plainSelect) { + Table fromItem = (Table) plainSelect.getFromItem(); + // 有别名用别名,无别名用表名,防止字段冲突报错 + Alias fromItemAlias = fromItem.getAlias(); + String mainTableName = fromItemAlias == null ? fromItem.getName() : fromItemAlias.getName(); + // 构建子查询 -- 数据权限过滤SQL + String dataPermissionSql = mainTableName + ".create_by in ( 1, 2, 3 )"; + if (plainSelect.getWhere() == null) { + plainSelect.setWhere(CCJSqlParserUtil.parseCondExpression(dataPermissionSql)); + } else { + plainSelect.setWhere(new AndExpression(plainSelect.getWhere(), CCJSqlParserUtil.parseCondExpression(dataPermissionSql))); + } + } + + + /** + * 生成拦截对象的代理 + * + * @param target 目标对象 + * @return 代理对象 + */ + @Override + public Object plugin(Object target) { + if (target instanceof StatementHandler) { + return Plugin.wrap(target, this); + } + return target; + } + + /** + * mybatis配置的属性 + * + * @param properties mybatis配置的属性 + */ + @Override + public void setProperties(Properties properties) { + + } + + /** + * 查找参数是否包括DataScope对象 + * + * @param parameterObj 参数列表 + * @return DataScope + */ + // private DataScope findDataScopeObject(Object parameterObj) { + // if (parameterObj instanceof DataScope) { + // return (DataScope) parameterObj; + // } else if (parameterObj instanceof Map) { + // for (Object val : ((Map) parameterObj).values()) { + // if (val instanceof DataScope) { + // return (DataScope) val; + // } + // } + // } + // return null; + // } + +} diff --git a/smallboot-api/common/db/src/main/java/com/zhengqing/common/db/config/mybatis/data/permission/second/MyDataPermissionHandler.java b/smallboot-api/common/db/src/main/java/com/zhengqing/common/db/config/mybatis/data/permission/second/MyDataPermissionHandler.java new file mode 100644 index 0000000..8192f55 --- /dev/null +++ b/smallboot-api/common/db/src/main/java/com/zhengqing/common/db/config/mybatis/data/permission/second/MyDataPermissionHandler.java @@ -0,0 +1,120 @@ +package com.zhengqing.common.db.config.mybatis.data.permission.second; + +import cn.hutool.core.util.StrUtil; +import com.zhengqing.common.db.context.DataPermissionThreadLocal; +import com.zhengqing.common.db.enums.DataPermissionTypeEnum; +import lombok.SneakyThrows; +import lombok.extern.slf4j.Slf4j; +import net.sf.jsqlparser.expression.*; +import net.sf.jsqlparser.expression.operators.conditional.AndExpression; +import net.sf.jsqlparser.expression.operators.relational.EqualsTo; +import net.sf.jsqlparser.expression.operators.relational.ExpressionList; +import net.sf.jsqlparser.expression.operators.relational.InExpression; +import net.sf.jsqlparser.expression.operators.relational.ItemsList; +import net.sf.jsqlparser.schema.Column; +import net.sf.jsqlparser.schema.Table; +import net.sf.jsqlparser.statement.select.PlainSelect; + +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + + +/** + *

mybatis-plus 数据权限处理器

+ * + * @author zhengqingya + * @description {@link MyDataPermissionInterceptor} + * @date 2022/1/10 17:37 + */ +@Slf4j +public class MyDataPermissionHandler { + + /** + * 获取数据权限 SQL 片段 + * + * @param plainSelect 查询对象 + * @param whereSegment 查询条件片段 + * @return JSqlParser 条件表达式 + */ + @SneakyThrows(Exception.class) + public Expression getSqlSegment(PlainSelect plainSelect, String whereSegment) { + // 待执行 SQL Where 条件表达式 + Expression where = plainSelect.getWhere(); + // 获取权限过滤相关信息 + UserPermissionInfo userPermissionInfo = DataPermissionThreadLocal.get(); + if (userPermissionInfo == null) { + return where; + } + Table fromItem = (Table) plainSelect.getFromItem(); + // 有别名用别名,无别名用表名,防止字段冲突报错 + Alias fromItemAlias = fromItem.getAlias(); + String mainTableName = fromItemAlias == null ? fromItem.getName() : fromItemAlias.getName(); + + // 获取mapper层信息 + List split = StrUtil.split(whereSegment, '.'); + int index = split.size(); + String method = split.get(index - 1); + String mapper = split.get(index - 2); + + + try { + DataPermissionTypeEnum dataPermissionTypeEnum = userPermissionInfo.getDataPermissionTypeEnum(); + log.info("[数据权限过滤] dataPermissionType:[{}] where:[{}] whereSegment:[{}]", dataPermissionTypeEnum, where, whereSegment); + Expression expression = new HexValue(" 1 = 1 "); + if (where == null) { + where = expression; + } + + // 根据不同类型进行权限处理 + switch (dataPermissionTypeEnum) { + // 查看全部 + case ALL: + return where; + // 查看本人所在角色以及下属机构 + case ROLE_AUTO: + // 创建IN 表达式 + // 创建IN范围的元素集合 + Set roleIdList = userPermissionInfo.getRoleIdList(); + // 把集合转变为JSQLParser需要的元素列表 + ItemsList itemsList = new ExpressionList(roleIdList.stream().map(LongValue::new).collect(Collectors.toList())); + InExpression inExpression = new InExpression(new Column(mainTableName + ".create_role_id"), itemsList); + AndExpression andExpression = new AndExpression(where, inExpression); + log.info(" where {}", andExpression); + return andExpression; + // 查看当前角色的数据 + case ROLE: + // = 表达式 + // role_id = roleId + EqualsTo equalsTo = new EqualsTo(); + equalsTo.setLeftExpression(new Column(mainTableName + ".create_role_id")); + equalsTo.setRightExpression(new LongValue(userPermissionInfo.getRoleId())); + // 创建 AND 表达式 拼接Where 和 = 表达式 + // WHERE xxx AND role_id = 3 + AndExpression deptAndExpression = new AndExpression(where, equalsTo); + log.info(" where {}", deptAndExpression); + return deptAndExpression; + // 查看自己的数据 + case SELF: + // create_by = userId + EqualsTo selfEqualsTo = new EqualsTo(); + selfEqualsTo.setLeftExpression(new Column(mainTableName + ".create_by")); + selfEqualsTo.setRightExpression(new LongValue(userPermissionInfo.getUserId())); + AndExpression selfAndExpression = new AndExpression(where, selfEqualsTo); + log.info(" where {}", selfAndExpression); + return selfAndExpression; + case AUTO: + return new AndExpression(where, new StringValue(userPermissionInfo.getSql())); + default: + break; + } + } catch (Exception e) { + log.error("MyDataPermissionHandler 数据权限处理异常:", e); + } finally { + + } + return where; + } + + +} diff --git a/smallboot-api/common/db/src/main/java/com/zhengqing/common/db/config/mybatis/data/permission/second/MyDataPermissionInterceptor.java b/smallboot-api/common/db/src/main/java/com/zhengqing/common/db/config/mybatis/data/permission/second/MyDataPermissionInterceptor.java new file mode 100644 index 0000000..fe30abe --- /dev/null +++ b/smallboot-api/common/db/src/main/java/com/zhengqing/common/db/config/mybatis/data/permission/second/MyDataPermissionInterceptor.java @@ -0,0 +1,75 @@ +package com.zhengqing.common.db.config.mybatis.data.permission.second; + +import com.baomidou.mybatisplus.core.plugins.InterceptorIgnoreHelper; +import com.baomidou.mybatisplus.core.toolkit.PluginUtils; +import com.baomidou.mybatisplus.extension.parser.JsqlParserSupport; +import com.baomidou.mybatisplus.extension.plugins.inner.InnerInterceptor; +import lombok.*; +import net.sf.jsqlparser.expression.Expression; +import net.sf.jsqlparser.statement.select.PlainSelect; +import net.sf.jsqlparser.statement.select.Select; +import net.sf.jsqlparser.statement.select.SelectBody; +import net.sf.jsqlparser.statement.select.SetOperationList; +import org.apache.ibatis.executor.Executor; +import org.apache.ibatis.mapping.BoundSql; +import org.apache.ibatis.mapping.MappedStatement; +import org.apache.ibatis.session.ResultHandler; +import org.apache.ibatis.session.RowBounds; + +import java.sql.SQLException; +import java.util.List; + +/** + *

mybatis-plus 数据权限插件

+ * + * @author zhengqingya + * @description {@link com.baomidou.mybatisplus.extension.plugins.inner.DataPermissionInterceptor} + * @date 2022/1/13 10:51 + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +@ToString(callSuper = true) +@EqualsAndHashCode(callSuper = true) +@SuppressWarnings({"rawtypes"}) +public class MyDataPermissionInterceptor extends JsqlParserSupport implements InnerInterceptor { + + /** + * 数据权限处理器 + */ + private MyDataPermissionHandler dataPermissionHandler; + + @Override + public void beforeQuery(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException { + if (InterceptorIgnoreHelper.willIgnoreDataPermission(ms.getId())) { + return; + } + PluginUtils.MPBoundSql mpBs = PluginUtils.mpBoundSql(boundSql); + mpBs.sql(this.parserSingle(mpBs.sql(), ms.getId())); + } + + @Override + protected void processSelect(Select select, int index, String sql, Object obj) { + SelectBody selectBody = select.getSelectBody(); + if (selectBody instanceof PlainSelect) { + this.setWhere((PlainSelect) selectBody, (String) obj); + } else if (selectBody instanceof SetOperationList) { + SetOperationList setOperationList = (SetOperationList) selectBody; + List selectBodyList = setOperationList.getSelects(); + selectBodyList.forEach(s -> this.setWhere((PlainSelect) s, (String) obj)); + } + } + + /** + * 设置 where 条件 + * + * @param plainSelect 查询对象 + * @param whereSegment 查询条件片段 + */ + private void setWhere(PlainSelect plainSelect, String whereSegment) { + Expression sqlSegment = this.dataPermissionHandler.getSqlSegment(plainSelect, whereSegment); + if (null != sqlSegment) { + plainSelect.setWhere(sqlSegment); + } + } +} diff --git a/smallboot-api/common/db/src/main/java/com/zhengqing/common/db/config/mybatis/data/permission/second/UserPermissionInfo.java b/smallboot-api/common/db/src/main/java/com/zhengqing/common/db/config/mybatis/data/permission/second/UserPermissionInfo.java new file mode 100644 index 0000000..03d29d5 --- /dev/null +++ b/smallboot-api/common/db/src/main/java/com/zhengqing/common/db/config/mybatis/data/permission/second/UserPermissionInfo.java @@ -0,0 +1,43 @@ +package com.zhengqing.common.db.config.mybatis.data.permission.second; + +import com.zhengqing.common.db.enums.DataPermissionTypeEnum; +import io.swagger.annotations.ApiModelProperty; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.Set; + +/** + *

用户权限信息

+ * + * @author zhengqingya + * @description + * @date 2022/1/10 17:51 + */ +@Data +@Builder +@AllArgsConstructor +@NoArgsConstructor +public class UserPermissionInfo { + + @ApiModelProperty(value = "用户ID") + private Long userId; + + @ApiModelProperty("角色id") + private String roleId; + + @ApiModelProperty("角色ids") + private Set roleIdList; + + @ApiModelProperty("自定义权限过滤sql") + private String sql; + + /** + * {@link DataPermissionTypeEnum} + */ + @ApiModelProperty("数据权限类型") + private DataPermissionTypeEnum dataPermissionTypeEnum; + +} diff --git a/smallboot-api/common/db/src/main/java/com/zhengqing/common/db/config/mybatis/handler/DbJsonTypeHandler.java b/smallboot-api/common/db/src/main/java/com/zhengqing/common/db/config/mybatis/handler/DbJsonTypeHandler.java new file mode 100644 index 0000000..9afe261 --- /dev/null +++ b/smallboot-api/common/db/src/main/java/com/zhengqing/common/db/config/mybatis/handler/DbJsonTypeHandler.java @@ -0,0 +1,42 @@ +package com.zhengqing.common.db.config.mybatis.handler; + +import cn.hutool.core.lang.Assert; +import cn.hutool.json.JSONUtil; +import com.baomidou.mybatisplus.extension.handlers.AbstractJsonTypeHandler; +import org.apache.ibatis.type.JdbcType; +import org.apache.ibatis.type.MappedJdbcTypes; +import org.apache.ibatis.type.MappedTypes; + +/** + *

自定义 TypeHandler 类型处理器

+ * + * @author zhengqingya + * @description json类型转对象 + * @date 2022/6/6 11:25 + */ +@MappedJdbcTypes(JdbcType.VARCHAR) +@MappedTypes({Object.class}) +public class DbJsonTypeHandler extends AbstractJsonTypeHandler { + + private final Class clazz; + + public DbJsonTypeHandler(Class type, Class innerType) { + Assert.notNull(type, "Type argument cannot be null ..."); + this.clazz = innerType; + } + + @Override + protected Object parse(String json) { + if (JSONUtil.isJsonArray(json)) { + return JSONUtil.toList(JSONUtil.parseArray(json), this.clazz); + } else { + return JSONUtil.toBean(json, this.clazz); + } + } + + @Override + protected String toJson(Object obj) { + return JSONUtil.toJsonStr(obj); + } + +} diff --git a/smallboot-api/common/db/src/main/java/com/zhengqing/common/db/config/mybatis/handler/ListToStrTypeHandler.java b/smallboot-api/common/db/src/main/java/com/zhengqing/common/db/config/mybatis/handler/ListToStrTypeHandler.java new file mode 100644 index 0000000..e99e0d3 --- /dev/null +++ b/smallboot-api/common/db/src/main/java/com/zhengqing/common/db/config/mybatis/handler/ListToStrTypeHandler.java @@ -0,0 +1,54 @@ +package com.zhengqing.common.db.config.mybatis.handler; + +import com.google.common.base.Joiner; +import com.google.common.collect.Lists; +import org.apache.commons.lang3.StringUtils; +import org.apache.ibatis.type.JdbcType; +import org.apache.ibatis.type.MappedJdbcTypes; +import org.apache.ibatis.type.MappedTypes; +import org.apache.ibatis.type.TypeHandler; + +import java.sql.CallableStatement; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.List; + +/** + *

自定义 TypeHandler 类型处理器

+ * + * @author zhengqingya + * @description list类型转varchar(逗号分隔的字符串)的类型处理器 + * ex list [1,2,3] ==》 varchar 1,2,3 + * @date 2022/6/6 11:25 + */ +@MappedJdbcTypes(JdbcType.VARCHAR) +@MappedTypes({List.class}) +public class ListToStrTypeHandler implements TypeHandler> { + + @Override + public void setParameter(PreparedStatement ps, int columnIndex, List columnValueList, JdbcType jdbcType) throws SQLException { + String columnValue = Joiner.on(",").join(columnValueList); + ps.setString(columnIndex, columnValue); + } + + @Override + public List getResult(ResultSet rs, String columnName) throws SQLException { + String columnValue = rs.getString(columnName); + if (StringUtils.isBlank(columnValue)) { + return Lists.newArrayList(); + } + return Lists.newArrayList(columnValue.split(",")); + } + + @Override + public List getResult(ResultSet rs, int columnIndex) throws SQLException { + return null; + } + + @Override + public List getResult(CallableStatement cs, int columnIndex) throws SQLException { + return null; + } + +} diff --git a/smallboot-api/common/db/src/main/java/com/zhengqing/common/db/config/mybatis/plugins/LogicDeleteInterceptor.java b/smallboot-api/common/db/src/main/java/com/zhengqing/common/db/config/mybatis/plugins/LogicDeleteInterceptor.java new file mode 100644 index 0000000..08b3d71 --- /dev/null +++ b/smallboot-api/common/db/src/main/java/com/zhengqing/common/db/config/mybatis/plugins/LogicDeleteInterceptor.java @@ -0,0 +1,148 @@ +package com.zhengqing.common.db.config.mybatis.plugins; + +import com.baomidou.mybatisplus.core.toolkit.PluginUtils; +import com.zhengqing.common.db.config.mybatis.MybatisPlusConfig; +import lombok.AllArgsConstructor; +import lombok.SneakyThrows; +import lombok.extern.slf4j.Slf4j; +import net.sf.jsqlparser.JSQLParserException; +import net.sf.jsqlparser.expression.Alias; +import net.sf.jsqlparser.expression.operators.conditional.AndExpression; +import net.sf.jsqlparser.parser.CCJSqlParserManager; +import net.sf.jsqlparser.parser.CCJSqlParserUtil; +import net.sf.jsqlparser.schema.Table; +import net.sf.jsqlparser.statement.select.*; +import org.apache.ibatis.executor.statement.StatementHandler; +import org.apache.ibatis.mapping.BoundSql; +import org.apache.ibatis.mapping.MappedStatement; +import org.apache.ibatis.mapping.SqlCommandType; +import org.apache.ibatis.plugin.*; +import org.apache.ibatis.reflection.MetaObject; +import org.apache.ibatis.reflection.SystemMetaObject; + +import java.io.StringReader; +import java.sql.Connection; +import java.util.List; +import java.util.Properties; + +/** + *

+ * mybatis-plus 逻辑删除插件 + *

+ * + * @author zhengqingya + * @description 官方:https://baomidou.com/pages/6b03c5/#%E4%BD%BF%E7%94%A8%E6%96%B9%E6%B3%95 + * 主要解决在xml中写的sql 逻辑删除 失效问题 + * @date 2020/5/20 17:14 + */ +@Slf4j +@AllArgsConstructor +@Intercepts({@Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class})}) +public class LogicDeleteInterceptor implements Interceptor { + + @Override + public Object intercept(Invocation invocation) throws Throwable { + StatementHandler statementHandler = PluginUtils.realTarget(invocation.getTarget()); + MetaObject metaObject = SystemMetaObject.forObject(statementHandler); + // 判断是不是SELECT操作 不是直接过滤 + MappedStatement mappedStatement = (MappedStatement) metaObject.getValue("delegate.mappedStatement"); + if (!SqlCommandType.SELECT.equals(mappedStatement.getSqlCommandType())) { + return invocation.proceed(); + } + BoundSql boundSql = (BoundSql) metaObject.getValue("delegate.boundSql"); + // 执行的SQL语句 + String originalSql = boundSql.getSql(); + // SQL语句的参数 + Object parameterObject = boundSql.getParameterObject(); + + String finalSql = this.handleSql(originalSql); +// System.err.println("逻辑删除处理过后的SQL: \n" + finalSql); + + metaObject.setValue("delegate.boundSql.sql", finalSql); + return invocation.proceed(); + } + + + /** + * 改写SQL + * {@link com.baomidou.mybatisplus.extension.plugins.inner.DataPermissionInterceptor} + * + * @param originalSql 执行的SQL语句 + * @return 处理后的SQL + * @author zhengqingya + * @date 2022/1/13 10:43 + */ + private String handleSql(String originalSql) throws JSQLParserException { + CCJSqlParserManager parserManager = new CCJSqlParserManager(); + Select select = (Select) parserManager.parse(new StringReader(originalSql)); + SelectBody selectBody = select.getSelectBody(); + if (selectBody instanceof PlainSelect) { + this.setWhere((PlainSelect) selectBody); + } else if (selectBody instanceof SetOperationList) { + SetOperationList setOperationList = (SetOperationList) selectBody; + List selectBodyList = setOperationList.getSelects(); + selectBodyList.forEach(s -> this.setWhere((PlainSelect) s)); + } + return select.toString(); + } + + /** + * 设置 where 条件 -- 使用CCJSqlParser将原SQL进行解析并改写 + * + * @param plainSelect 查询对象 + */ + @SneakyThrows(Exception.class) + protected void setWhere(PlainSelect plainSelect) { + FromItem fromItem = plainSelect.getFromItem(); + if (fromItem instanceof SubSelect) { + // mybatis-plus分页时会查询count(*)查询总数 会有嵌套 因此这里需要处理下... + this.setWhere((PlainSelect) ((SubSelect) fromItem).getSelectBody()); + return; + } + Table fromItemOfTable = (Table) fromItem; + // 有别名用别名,无别名用表名,防止字段冲突报错 + Alias fromItemAlias = fromItemOfTable.getAlias(); + String originalTableName = fromItemOfTable.getName(); + String mainTableName = fromItemAlias == null ? originalTableName : fromItemAlias.getName(); + + // 判断是否需要逻辑删除,如果不需要直接过滤 + if (!MybatisPlusConfig.LOGIC_DELETE_TABLE.contains(originalTableName)) { + return; + } + + // 构建子查询 -- 逻辑删除 + String dataSql = mainTableName + ".is_deleted = 0"; + if (plainSelect.getWhere() == null) { + plainSelect.setWhere(CCJSqlParserUtil.parseCondExpression(dataSql)); + } else { + plainSelect.setWhere(new AndExpression(plainSelect.getWhere(), CCJSqlParserUtil.parseCondExpression(dataSql))); + } + } + + + /** + * 生成拦截对象的代理 + * + * @param target 目标对象 + * @return 代理对象 + */ + @Override + public Object plugin(Object target) { + if (target instanceof StatementHandler) { + return Plugin.wrap(target, this); + } + return target; + } + + /** + * mybatis配置的属性 + * + * @param properties mybatis配置的属性 + */ + @Override + public void setProperties(Properties properties) { + + } + + +} diff --git a/smallboot-api/common/db/src/main/java/com/zhengqing/common/db/config/mybatis/plugins/SqlLogInterceptor.java b/smallboot-api/common/db/src/main/java/com/zhengqing/common/db/config/mybatis/plugins/SqlLogInterceptor.java new file mode 100644 index 0000000..0b58955 --- /dev/null +++ b/smallboot-api/common/db/src/main/java/com/zhengqing/common/db/config/mybatis/plugins/SqlLogInterceptor.java @@ -0,0 +1,306 @@ +package com.zhengqing.common.db.config.mybatis.plugins; + +import com.baomidou.mybatisplus.core.MybatisConfiguration; +import com.baomidou.mybatisplus.core.toolkit.CollectionUtils; +import com.baomidou.mybatisplus.core.toolkit.PluginUtils; +import com.baomidou.mybatisplus.core.toolkit.StringPool; +import com.baomidou.mybatisplus.core.toolkit.SystemClock; +import lombok.extern.slf4j.Slf4j; +import org.apache.ibatis.executor.statement.StatementHandler; +import org.apache.ibatis.mapping.BoundSql; +import org.apache.ibatis.mapping.MappedStatement; +import org.apache.ibatis.mapping.ParameterMapping; +import org.apache.ibatis.plugin.*; +import org.apache.ibatis.reflection.MetaObject; +import org.apache.ibatis.reflection.SystemMetaObject; +import org.apache.ibatis.session.Configuration; +import org.apache.ibatis.session.ResultHandler; +import org.apache.ibatis.type.TypeHandlerRegistry; + +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; +import java.sql.Statement; +import java.text.DateFormat; +import java.util.*; +import java.util.regex.Matcher; + +/** + *

+ * 用于输出每条 SQL 语句及其执行时间 + *

+ * + * @author zhengqingya + * @description + * @date 2020/5/20 17:14 + */ +@Slf4j +@Intercepts({@Signature(type = StatementHandler.class, method = "query", args = {Statement.class, ResultHandler.class}), + @Signature(type = StatementHandler.class, method = "update", args = Statement.class), + @Signature(type = StatementHandler.class, method = "batch", args = Statement.class)}) +public class SqlLogInterceptor implements Interceptor { + + private static final String DRUID_POOLED_PREPARED_STATEMENT = "com.alibaba.druid.pool.DruidPooledPreparedStatement"; + private static final String T4C_PREPARED_STATEMENT = "oracle.jdbc.driver.T4CPreparedStatement"; + private static final String ORACLE_PREPARED_STATEMENT_WRAPPER = "oracle.jdbc.driver.OraclePreparedStatementWrapper"; + + private Method oracleGetOriginalSqlMethod; + private Method druidGetSqlMethod; + + @Override + public Object intercept(Invocation invocation) throws Throwable { + Statement statement; + Object firstArg = invocation.getArgs()[0]; + if (Proxy.isProxyClass(firstArg.getClass())) { + statement = (Statement) SystemMetaObject.forObject(firstArg).getValue("h.statement"); + } else { + statement = (Statement) firstArg; + } + MetaObject stmtMetaObj = SystemMetaObject.forObject(statement); + try { + statement = (Statement) stmtMetaObj.getValue("stmt.statement"); + } catch (Exception e) { + // do nothing + } + if (stmtMetaObj.hasGetter("delegate")) { + // Hikari + try { + statement = (Statement) stmtMetaObj.getValue("delegate"); + } catch (Exception ignored) { + + } + } + + // tips: `originalSql`为包含?预编译的sql + String originalSql = null; + String stmtClassName = statement.getClass().getName(); + if (DRUID_POOLED_PREPARED_STATEMENT.equals(stmtClassName)) { + try { + if (this.druidGetSqlMethod == null) { + Class clazz = Class.forName(DRUID_POOLED_PREPARED_STATEMENT); + this.druidGetSqlMethod = clazz.getMethod("getSql"); + } + Object stmtSql = this.druidGetSqlMethod.invoke(statement); + if (stmtSql instanceof String) { + originalSql = (String) stmtSql; + } + } catch (Exception e) { + e.printStackTrace(); + } + } else if (T4C_PREPARED_STATEMENT.equals(stmtClassName) + || ORACLE_PREPARED_STATEMENT_WRAPPER.equals(stmtClassName)) { + try { + if (this.oracleGetOriginalSqlMethod != null) { + Object stmtSql = this.oracleGetOriginalSqlMethod.invoke(statement); + if (stmtSql instanceof String) { + originalSql = (String) stmtSql; + } + } else { + Class clazz = Class.forName(stmtClassName); + this.oracleGetOriginalSqlMethod = this.getMethodRegular(clazz, "getOriginalSql"); + if (this.oracleGetOriginalSqlMethod != null) { + // OraclePreparedStatementWrapper is not a public class, need set this. + this.oracleGetOriginalSqlMethod.setAccessible(true); + if (null != this.oracleGetOriginalSqlMethod) { + Object stmtSql = this.oracleGetOriginalSqlMethod.invoke(statement); + if (stmtSql instanceof String) { + originalSql = (String) stmtSql; + } + } + } + } + } catch (Exception e) { + // ignore + } + } + if (originalSql == null) { + originalSql = statement.toString(); + } + originalSql = originalSql.replaceAll("[\\s]+", StringPool.SPACE); + int index = this.indexOfSqlStart(originalSql); + if (index > 0) { + // 这里拿到执行sql + originalSql = originalSql.substring(index); + } + + // 计算执行 SQL 耗时 + long start = SystemClock.now(); + Object result = invocation.proceed(); + long timing = SystemClock.now() - start; + + // SQL 打印执行结果 + Object target = PluginUtils.realTarget(invocation.getTarget()); + MetaObject metaObject = SystemMetaObject.forObject(target); + MappedStatement ms = (MappedStatement) metaObject.getValue("delegate.mappedStatement"); + + // 最终sql + String finalSql = showSql((MybatisConfiguration) metaObject.getValue("delegate.configuration"), (BoundSql) metaObject.getValue("delegate.boundSql")); + + // 打印 sql + System.err.println( + String.format( + "\n============== Sql Start ==============" + + "\nExecute ID :%s" + + "\nExecute SQL :%s" + + "\nExecute Time:%s ms" + + "\n============== Sql End ==============\n", + ms.getId(), finalSql, timing)); + return result; + } + + /** + * 进行?的替换 + */ + public static String showSql(Configuration configuration, BoundSql boundSql) { + // 获取参数 + Object parameterObject = boundSql.getParameterObject(); + List parameterMappings = boundSql.getParameterMappings(); + // sql语句中多个空格都用一个空格代替 + String sql = boundSql.getSql().replaceAll("[\\s]+", " "); + if (CollectionUtils.isNotEmpty(parameterMappings) && parameterObject != null) { + // 获取类型处理器注册器,类型处理器的功能是进行java类型和数据库类型的转换 + TypeHandlerRegistry typeHandlerRegistry = configuration.getTypeHandlerRegistry(); + // 如果根据parameterObject.getClass()可以找到对应的类型,则替换 + if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) { + sql = sql.replaceFirst("\\?", + Matcher.quoteReplacement(getParameterValue(parameterObject))); + + } else { + // MetaObject主要是封装了originalObject对象,提供了get和set的方法用于获取和设置originalObject的属性值,主要支持对JavaBean、Collection、Map三种类型对象的操作 + MetaObject metaObject = configuration.newMetaObject(parameterObject); + for (ParameterMapping parameterMapping : parameterMappings) { + String propertyName = parameterMapping.getProperty(); + if (metaObject.hasGetter(propertyName)) { + Object obj = metaObject.getValue(propertyName); + sql = sql.replaceFirst("\\?", + Matcher.quoteReplacement(getParameterValue(obj))); + } else if (boundSql.hasAdditionalParameter(propertyName)) { + // 该分支是动态sql + Object obj = boundSql.getAdditionalParameter(propertyName); + sql = sql.replaceFirst("\\?", + Matcher.quoteReplacement(getParameterValue(obj))); + } else { + // 打印出缺失,提醒该参数缺失并防止错位 + sql = sql.replaceFirst("\\?", "缺失"); + } + } + } + } + return sql; + } + + /** + * 如果参数是String,则添加单引号, + * 如果是日期,则转换为时间格式器并加单引号; + * 对参数是null和不是null的情况作了处理 + */ + private static String getParameterValue(Object obj) { + String value = null; + if (obj instanceof String) { + value = "'" + obj.toString() + "'"; + } else if (obj instanceof Date) { + DateFormat formatter = DateFormat.getDateTimeInstance(DateFormat.DEFAULT, + DateFormat.DEFAULT, Locale.CHINA); + value = "'" + formatter.format(new Date()) + "'"; + } else { + if (obj != null) { + value = obj.toString(); + } else { + value = ""; + } + } + return value; + } + + @Override + public Object plugin(Object target) { + if (target instanceof StatementHandler) { + return Plugin.wrap(target, this); + } + return target; + } + + /** + * 获取此方法名的具体 Method + * + * @param clazz class 对象 + * @param methodName 方法名 + * @return 方法 + */ + private Method getMethodRegular(Class clazz, String methodName) { + if (Object.class.equals(clazz)) { + return null; + } + for (Method method : clazz.getDeclaredMethods()) { + if (method.getName().equals(methodName)) { + return method; + } + } + return this.getMethodRegular(clazz.getSuperclass(), methodName); + } + + /** + * 获取sql语句开头部分 + * + * @param sql ignore + * @return ignore + */ + private int indexOfSqlStart(String sql) { + String upperCaseSql = sql.toUpperCase(); + Set set = new HashSet<>(); + set.add(upperCaseSql.indexOf("SELECT ")); + set.add(upperCaseSql.indexOf("UPDATE ")); + set.add(upperCaseSql.indexOf("INSERT ")); + set.add(upperCaseSql.indexOf("DELETE ")); + set.remove(-1); + if (CollectionUtils.isEmpty(set)) { + return -1; + } + List list = new ArrayList<>(set); + list.sort(Comparator.naturalOrder()); + return list.get(0); + } + + /** + * 匹配sql -- 拿到表名 + * + * @param sql sql + * @return 匹配后的sql表名 + * @author zhengqingya + * @date 2020/12/2 17:14 + */ +// private String matchSql(String sql) { +// Matcher matcher = null; +// // SELECT 列名称 FROM 表名称 +// // SELECT * FROM 表名称 +// if (sql.startsWith("SELECT")) { +// matcher = Pattern.compile("SELECT\\s.+FROM\\s(.+)WHERE\\s(.*)").matcher(sql); +// if (matcher.find()) { +// return matcher.group(1); +// } +// } +// // INSERT INTO 表名称 VALUES (值1, 值2,....) +// // INSERT INTO table_name (列1, 列2,...) VALUES (值1, 值2,....) +// if (sql.startsWith("INSERT")) { +// matcher = Pattern.compile("INSERT\\sINTO\\s(.+)\\(.*\\)\\s.*").matcher(sql); +// if (matcher.find()) { +// return matcher.group(1); +// } +// } +// // UPDATE 表名称 SET 列名称 = 新值 WHERE 列名称 = 某值 +// if (sql.startsWith("UPDATE")) { +// matcher = Pattern.compile("UPDATE\\s(.+)SET\\s.*").matcher(sql); +// if (matcher.find()) { +// return matcher.group(1); +// } +// } +// // DELETE FROM 表名称 WHERE 列名称 = 值 +// if (sql.startsWith("DELETE")) { +// matcher = Pattern.compile("DELETE\\sFROM\\s(.+)WHERE\\s(.*)").matcher(sql); +// if (matcher.find()) { +// return matcher.group(1); +// } +// } +// return null; +// } + +} diff --git a/smallboot-api/common/db/src/main/java/com/zhengqing/common/db/constant/DataSourceConstant.java b/smallboot-api/common/db/src/main/java/com/zhengqing/common/db/constant/DataSourceConstant.java new file mode 100644 index 0000000..7a143bc --- /dev/null +++ b/smallboot-api/common/db/src/main/java/com/zhengqing/common/db/constant/DataSourceConstant.java @@ -0,0 +1,21 @@ +package com.zhengqing.common.db.constant; + +/** + *

全局常用变量 - 数据源名称

+ * + * @author zhengqingya + * @description + * @date 2021/6/25 10:25 + */ +public interface DataSourceConstant { + + /** + * 主库 + */ + String MASTER = "master"; + /** + * 测试库 + */ + String DB_TEST = "db-test"; + +} diff --git a/smallboot-api/common/db/src/main/java/com/zhengqing/common/db/constant/MybatisConstant.java b/smallboot-api/common/db/src/main/java/com/zhengqing/common/db/constant/MybatisConstant.java new file mode 100644 index 0000000..d32f4ce --- /dev/null +++ b/smallboot-api/common/db/src/main/java/com/zhengqing/common/db/constant/MybatisConstant.java @@ -0,0 +1,37 @@ +package com.zhengqing.common.db.constant; + +/** + *

全局常用变量 - mybatis

+ * + * @author zhengqingya + * @description + * @date 2021/6/1 17:08 + */ +public interface MybatisConstant { + + /** + * mybatis 分页参数 + */ + String LIMIT_ONE = "LIMIT 1"; + /** + * 是否删除:true->删除,false->未删除 + */ + String IS_DELETED = "isDeleted"; + /** + * 创建人id + */ + String CREATE_BY = "createBy"; + /** + * 创建时间 + */ + String CREATE_TIME = "createTime"; + /** + * 更新人id + */ + String UPDATE_BY = "updateBy"; + /** + * 更新时间 + */ + String UPDATE_TIME = "updateTime"; + +} diff --git a/smallboot-api/common/db/src/main/java/com/zhengqing/common/db/context/DataPermissionThreadLocal.java b/smallboot-api/common/db/src/main/java/com/zhengqing/common/db/context/DataPermissionThreadLocal.java new file mode 100644 index 0000000..3109478 --- /dev/null +++ b/smallboot-api/common/db/src/main/java/com/zhengqing/common/db/context/DataPermissionThreadLocal.java @@ -0,0 +1,30 @@ +package com.zhengqing.common.db.context; + +import com.zhengqing.common.db.config.mybatis.data.permission.second.UserPermissionInfo; +import lombok.extern.slf4j.Slf4j; + +/** + *

用户数据权限信息上下文

+ * + * @author zhengqingya + * @description + * @date 2022/1/10 17:53 + */ +@Slf4j +public class DataPermissionThreadLocal { + + private static final ThreadLocal THREAD_LOCAL = new ThreadLocal<>(); + + public static void set(UserPermissionInfo userPermissionInfo) { + THREAD_LOCAL.set(userPermissionInfo); + } + + public static UserPermissionInfo get() { + return THREAD_LOCAL.get(); + } + + public static void remove() { + THREAD_LOCAL.remove(); + } + +} diff --git a/smallboot-api/common/db/src/main/java/com/zhengqing/common/db/entity/BaseEntity.java b/smallboot-api/common/db/src/main/java/com/zhengqing/common/db/entity/BaseEntity.java new file mode 100644 index 0000000..abb63ad --- /dev/null +++ b/smallboot-api/common/db/src/main/java/com/zhengqing/common/db/entity/BaseEntity.java @@ -0,0 +1,47 @@ +package com.zhengqing.common.db.entity; + +import com.baomidou.mybatisplus.annotation.FieldFill; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.extension.activerecord.Model; +import io.swagger.annotations.ApiModelProperty; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +import java.util.Date; + +/** + *

+ * BaseEntity + *

+ * + * @author zhengqingya + * @description + * @date 2019/8/18 1:30 + */ +@Getter +@Setter +@AllArgsConstructor +@NoArgsConstructor +public abstract class BaseEntity> extends Model { + + @ApiModelProperty(value = "创建人id") + @TableField(value = "create_by", fill = FieldFill.INSERT) + private Long createBy; + + @ApiModelProperty(value = "创建时间") + @TableField(value = "create_time", fill = FieldFill.INSERT) +// @Past(message = "创建时间必须是过去时间") + private Date createTime; + + @ApiModelProperty(value = "更新人id") + @TableField(value = "update_by", fill = FieldFill.INSERT_UPDATE) + private Long updateBy; + + @ApiModelProperty(value = "更新时间") + @TableField(value = "update_time", fill = FieldFill.INSERT_UPDATE) + // @Future(message = "修改时间必须是将来时间") + private Date updateTime; + +} diff --git a/smallboot-api/common/db/src/main/java/com/zhengqing/common/db/entity/IsDeletedBaseEntity.java b/smallboot-api/common/db/src/main/java/com/zhengqing/common/db/entity/IsDeletedBaseEntity.java new file mode 100644 index 0000000..0b293fe --- /dev/null +++ b/smallboot-api/common/db/src/main/java/com/zhengqing/common/db/entity/IsDeletedBaseEntity.java @@ -0,0 +1,33 @@ +package com.zhengqing.common.db.entity; + +import com.baomidou.mybatisplus.annotation.FieldFill; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableLogic; +import com.baomidou.mybatisplus.extension.activerecord.Model; +import io.swagger.annotations.ApiModelProperty; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +/** + *

+ * BaseEntity + *

+ * + * @author zhengqingya + * @description + * @date 2019/8/18 1:30 + */ +@Getter +@Setter +@AllArgsConstructor +@NoArgsConstructor +public abstract class IsDeletedBaseEntity> extends BaseEntity { + + @TableLogic + @ApiModelProperty(value = "是否删除:true->删除,false->未删除") + @TableField(value = "is_deleted", fill = FieldFill.INSERT) + private Boolean isDeleted; + +} diff --git a/smallboot-api/common/db/src/main/java/com/zhengqing/common/db/enums/DataPermissionTypeEnum.java b/smallboot-api/common/db/src/main/java/com/zhengqing/common/db/enums/DataPermissionTypeEnum.java new file mode 100644 index 0000000..fc91a45 --- /dev/null +++ b/smallboot-api/common/db/src/main/java/com/zhengqing/common/db/enums/DataPermissionTypeEnum.java @@ -0,0 +1,75 @@ +package com.zhengqing.common.db.enums; + +import com.google.common.collect.Lists; +import com.zhengqing.common.base.exception.MyException; +import lombok.AllArgsConstructor; +import lombok.Getter; + +import java.util.Arrays; +import java.util.List; + +/** + *

mybatis-plus 数据权限类型枚举类

+ * + * @author zhengqingya + * @description + * @date 2022/1/10 17:42 + */ +@Getter +@AllArgsConstructor +public enum DataPermissionTypeEnum { + + /** + * 全部 + */ + ALL(1, "全部"), + /** + * 本人所属角色 + */ + ROLE(2, "本人所属角色"), + /** + * 本人 + */ + SELF(3, "本人"), + /** + * 自定义角色 + */ + ROLE_AUTO(4, "自定义角色"), + /** + * 自定义sql过滤 + */ + AUTO(5, "自定义sql过滤"); + + /** + * 类型 + */ + private final int type; + /** + * 描述 + */ + private final String desc; + + private static final List LIST = Lists.newArrayList(); + + static { + LIST.addAll(Arrays.asList(DataPermissionTypeEnum.values())); + } + + /** + * 根据指定的规则类型查找相应枚举类 + * + * @param type 类型 + * @return 类型枚举信息 + * @author zhengqingya + * @date 2022/1/10 17:43 + */ + public static DataPermissionTypeEnum getEnum(int type) { + for (DataPermissionTypeEnum itemEnum : LIST) { + if (itemEnum.getType() == type) { + return itemEnum; + } + } + throw new MyException("未找到指定数据权限枚举信息!"); + } + +} diff --git a/smallboot-api/common/db/src/main/java/com/zhengqing/common/db/enums/DataSourceEnum.java b/smallboot-api/common/db/src/main/java/com/zhengqing/common/db/enums/DataSourceEnum.java new file mode 100644 index 0000000..a67268e --- /dev/null +++ b/smallboot-api/common/db/src/main/java/com/zhengqing/common/db/enums/DataSourceEnum.java @@ -0,0 +1,24 @@ +package com.zhengqing.common.db.enums; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + *

+ * 数据源枚举类 + *

+ * + * @author zhengqingya + * @description + * @date 2020/9/13 18:46 + */ +@Getter +@AllArgsConstructor +public enum DataSourceEnum { + + MASTER("master", "主数据库"), DB_TEST("db-test", "测试数据库"); + + private final String value; + private final String desc; + +} diff --git a/smallboot-api/common/db/src/main/java/com/zhengqing/common/db/enums/IsDeletedEnum.java b/smallboot-api/common/db/src/main/java/com/zhengqing/common/db/enums/IsDeletedEnum.java new file mode 100644 index 0000000..6f9313a --- /dev/null +++ b/smallboot-api/common/db/src/main/java/com/zhengqing/common/db/enums/IsDeletedEnum.java @@ -0,0 +1,35 @@ +package com.zhengqing.common.db.enums; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + *

数据是否删除枚举

+ * + * @author zhengqingya + * @description + * @date 2021/6/11 18:29 + */ +@Getter +@AllArgsConstructor +public enum IsDeletedEnum { + + /** + * 删除 + */ + YES(1, "删除"), + /** + * 未删除 + */ + NO(0, "未删除"); + + /** + * 值 + */ + private final Integer value; + /** + * 描述 + */ + private final String desc; + +} diff --git a/smallboot-api/common/db/src/main/java/com/zhengqing/common/db/mapper/MyBaseMapper.java b/smallboot-api/common/db/src/main/java/com/zhengqing/common/db/mapper/MyBaseMapper.java new file mode 100644 index 0000000..af4eae6 --- /dev/null +++ b/smallboot-api/common/db/src/main/java/com/zhengqing/common/db/mapper/MyBaseMapper.java @@ -0,0 +1,64 @@ +package com.zhengqing.common.db.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import org.apache.ibatis.annotations.Param; +import org.apache.ibatis.annotations.Select; +import org.apache.ibatis.annotations.Update; + +import java.util.List; +import java.util.Map; + +/** + *

+ * 扩展Mapper基类 + *

+ * + * @author zhengqingya + * @description + * @date 2020/8/3 18:33 + */ +public interface MyBaseMapper extends BaseMapper { + + /** + * 根据指定条件,查询总记录数 【注:${key} 不能使用 #{key}】 + * + * @param tableName 表名 + * @param columnMap 查询校验字段组值 + * @return 总记录数 + * @author zhengqingya + * @date 2020/8/3 18:41 + */ + @Select({""}) + Integer selectCount(@Param("table_name") String tableName, @Param("columnMap") Map columnMap); + + /** + * 根据指定条件,查询全部记录 【注:${key} 不能使用 #{key}】 + * + * @param tableName 表名 + * @param columnMap 查询校验字段组值 + * @return 全部记录 + * @author zhengqingya + * @date 2020/8/3 18:41 + */ + @Select({""}) + List> selectList(@Param("table_name") String tableName, + @Param("columnMap") Map columnMap); + + /** + * 执行sql + * + * @param sql sql + * @return void + * @author zhengqingya + * @date 2022/7/22 16:46 + */ + @Update("${sql}") + void execSql(@Param("sql") String sql); + +} diff --git a/smallboot-api/common/db/src/main/resources/application-db.yml b/smallboot-api/common/db/src/main/resources/application-db.yml new file mode 100644 index 0000000..f34a849 --- /dev/null +++ b/smallboot-api/common/db/src/main/resources/application-db.yml @@ -0,0 +1,90 @@ +spring: + # 多数据源配置 可参考 https://gitee.com/baomidou/dynamic-datasource-spring-boot-starter + datasource: + # ===================== ↓↓↓↓↓↓ 使用druid数据源【dynamic - 公共配置】 ↓↓↓↓↓↓ ===================== + druid: + stat-view-servlet: + enabled: true # 是否开启内置监控页面 + url-pattern: '/druid/*' # 访问地址 + reset-enable: true # 是否启用重置按钮 + login-username: admin # SQL监控后台登录用户名 + login-password: 123456 # SQL监控后台登录用户密码 + web-stat-filter: + enabled: true # 是否开启内置监控中的 Web-jdbc 关联监控的数据 + url-pattern: '/*' # 匹配路径 + exclusions: '*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*' # 排除路径 + session-stat-enable: true # 是否监控session + initialSize: 5 # 连接池初始化连接数量 + minIdle: 5 # 连接池最小空闲数 + maxActive: 20 # 连接池最大活跃连接数 + maxWait: 60000 # 配置获取连接等待超时的时间 + timeBetweenEvictionRunsMillis: 60000 # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 + minEvictableIdleTimeMillis: 300000 # 配置一个连接在池中最小生存的时间,单位是毫秒 + validationQuery: SELECT 1 FROM DUAL # 连接是否有效的查询语句 + testWhileIdle: true + testOnBorrow: false + testOnReturn: false + # 打开PSCache,并且指定每个连接上PSCache的大小 + poolPreparedStatements: true + maxPoolPreparedStatementPerConnectionSize: 20 + removeAbandoned: true + # 配置监控统计拦截的filters,去掉后监控界面sql无法统计,【 'stat':监控统计 'wall':用于防火墙,防御sql注入 'slf4j':日志 】 + filters: stat,wall,slf4j + # 通过connectProperties属性来打开mergeSql功能;慢SQL记录 + connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000 + #useGlobalDataSourceStat: true # 合并多个DruidDataSource的监控数据 + + type: com.alibaba.druid.pool.DruidDataSource # 连接池类型,druid连接池springboot暂无法默认支持,需要自己配置bean + dynamic: + primary: master # 设置默认的数据源或者数据源组,默认值即为master + strict: false # 设置严格模式,默认false不启动. 启动后在未匹配到指定数据源时候会抛出异常,不启动则使用默认数据源. + datasource: + master: + url: jdbc:mysql://${smallboot.mysql.master.ip}:${smallboot.mysql.master.port}/${smallboot.mysql.master.db-name}${smallboot.mysql.master.url-params} + username: ${smallboot.mysql.master.username} + password: ${smallboot.mysql.master.password} + driver-class-name: com.mysql.cj.jdbc.Driver + db-test: + url: jdbc:mysql://${smallboot.mysql.db-test.ip}:${smallboot.mysql.db-test.port}/${smallboot.mysql.db-test.db-name}${smallboot.mysql.db-test.url-params} + username: ${smallboot.mysql.db-test.username} + password: ${smallboot.mysql.db-test.password} + driver-class-name: com.mysql.cj.jdbc.Driver + + +# mybatis-plus相关配置 +mybatis-plus: + # xml扫描,多个目录用逗号或者分号分隔(告诉 Mapper 所对应的 XML 文件位置) + # -- classpath: 只会扫描当前module的class + # -- classpath*: 扫描所有jar + mapper-locations: classpath*:**/*Mapper.xml + # 实体扫描,多个package用逗号或者分号分隔 + typeAliasesPackage: com.zhengqing.*.entity + # 配置扫描通用枚举, 支持统配符 * 或者 ; 分割 + type-enums-package: com.zhengqing.*.enums,com.zhengqing.*.*.enums,com.zhengqing.*.*.*.enums + # 以下配置均有默认值,可以不设置 + global-config: + # 关闭MP3.0+自带的banner + banner: false + db-config: + # 主键类型 0:"数据库ID自增", 1:"不操作", 2:"用户输入ID",3:"数字型snowflake", 4:"全局唯一ID UUID", 5:"字符串型snowflake"; + id-type: auto + # 字段策略 + insert-strategy: not_null + update-strategy: not_null + select-strategy: not_null + # 驼峰下划线转换 + table-underline: true + # 逻辑删除配置 + logic-delete-field: isDeleted # 全局逻辑删除的实体字段名(since 3.3.0,这里配置后可以忽略不配置`@TableLogic`) + logic-delete-value: 1 # 逻辑删除全局值(0表示已删除,默认为 1) + logic-not-delete-value: 0 # 逻辑未删除全局值(1表示未删除,默认为 0) + configuration: + # 是否开启自动驼峰命名规则映射:从数据库列名到Java属性驼峰命名的类似映射 + map-underscore-to-camel-case: true + cache-enabled: false + # 如果查询结果中包含空值的列,则 MyBatis 在映射的时候,不会映射这个字段 + call-setters-on-nulls: true + # 这个配置会将执行的sql打印出来,在开发或测试的时候可以用 + # log-impl: org.apache.ibatis.logging.stdout.StdOutImpl + # 解决oracle更新数据为null时无法转换报错,mysql不会出现此情况 + jdbc-type-for-null: 'null' diff --git a/smallboot-api/common/file/pom.xml b/smallboot-api/common/file/pom.xml new file mode 100644 index 0000000..f62fe0f --- /dev/null +++ b/smallboot-api/common/file/pom.xml @@ -0,0 +1,55 @@ + + + 4.0.0 + + + common + com.zhengqing + ${revision} + + + file + + ${project.artifactId} + ${revision} + jar + + + + + + + + + io.minio + minio + 8.4.6 + + + + com.squareup.okhttp3 + okhttp + 4.8.1 + + + + org.springframework.boot + spring-boot-starter-web + provided + + + + + + + org.springframework + spring-mock + 2.0.8 + + + + + + diff --git a/smallboot-api/common/file/src/main/java/com/zhengqing/common/file/api/MinIoController.java b/smallboot-api/common/file/src/main/java/com/zhengqing/common/file/api/MinIoController.java new file mode 100644 index 0000000..bd97065 --- /dev/null +++ b/smallboot-api/common/file/src/main/java/com/zhengqing/common/file/api/MinIoController.java @@ -0,0 +1,49 @@ +package com.zhengqing.common.file.api; + +import com.zhengqing.common.file.config.MinIoProperties; +import com.zhengqing.common.file.util.MinIoUtil; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; + +import javax.annotation.Resource; +import javax.servlet.http.HttpServletResponse; + +/** + *

+ * MinIO测试接口 + *

+ * + * @author zhengqing + * @description + * @date 2020/8/16 20:39 + */ +@RestController +@RequestMapping("/file") +@Api(tags = {"文件上传"}) +public class MinIoController { + + @Resource + private MinIoProperties minIoProperties; + + @ApiOperation(value = "上传文件") + @PostMapping(value = "/upload") + public String upload(@RequestPart @RequestParam MultipartFile file) { + return MinIoUtil.upload(this.minIoProperties.getBucketName(), file); + } + + @ApiOperation(value = "下载文件") + @GetMapping(value = "/download") + public void download(@RequestParam("fileName") String fileName, HttpServletResponse response) { + MinIoUtil.download(this.minIoProperties.getBucketName(), fileName, response); + } + + @ApiOperation(value = "删除文件") + @GetMapping(value = "/delete") + public String delete(@RequestParam("fileName") String fileName) { + MinIoUtil.deleteFile(this.minIoProperties.getBucketName(), fileName); + return "删除成功"; + } + +} diff --git a/smallboot-api/common/file/src/main/java/com/zhengqing/common/file/config/MinIoProperties.java b/smallboot-api/common/file/src/main/java/com/zhengqing/common/file/config/MinIoProperties.java new file mode 100644 index 0000000..66507c0 --- /dev/null +++ b/smallboot-api/common/file/src/main/java/com/zhengqing/common/file/config/MinIoProperties.java @@ -0,0 +1,46 @@ +package com.zhengqing.common.file.config; + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Configuration; + +/** + *

+ * MinIO属性类 + *

+ * + * @author zhengqing + * @description + * @date 2020/8/15 16:01 + */ +@Data +@Configuration +@ConfigurationProperties(prefix = "minio") +public class MinIoProperties { + + /** + * minio地址+端口号 -- 内网 + */ + private String url; + + /** + * minio地址+端口号 -- 外网 + */ + private String outUrl; + + /** + * minio用户名 + */ + private String accessKey; + + /** + * minio密码 + */ + private String secretKey; + + /** + * 文件桶的名称 + */ + private String bucketName; + +} diff --git a/smallboot-api/common/file/src/main/java/com/zhengqing/common/file/util/MinIoUtil.java b/smallboot-api/common/file/src/main/java/com/zhengqing/common/file/util/MinIoUtil.java new file mode 100644 index 0000000..e673d3f --- /dev/null +++ b/smallboot-api/common/file/src/main/java/com/zhengqing/common/file/util/MinIoUtil.java @@ -0,0 +1,216 @@ +package com.zhengqing.common.file.util; + +import com.zhengqing.common.file.config.MinIoProperties; +import io.minio.*; +import io.minio.http.Method; +import io.minio.messages.Bucket; +import lombok.SneakyThrows; +import lombok.extern.slf4j.Slf4j; +import org.apache.tomcat.util.http.fileupload.IOUtils; +import org.springframework.stereotype.Component; +import org.springframework.web.multipart.MultipartFile; + +import javax.annotation.PostConstruct; +import javax.annotation.Resource; +import javax.servlet.http.HttpServletResponse; +import java.io.InputStream; +import java.net.URLEncoder; +import java.util.List; +import java.util.concurrent.TimeUnit; + +/** + *

+ * MinIO工具类 + *

+ * + * @author zhengqing + * @description Java Client API参考文档:https://min.io/docs/minio/linux/developers/java/API.html + * @date 2020/8/16 20:16 + */ +@Slf4j +@Component +public class MinIoUtil { + + @Resource + private MinIoProperties minIoProperties; + + private static MinioClient minioClient; + + /** + * 初始化minio配置 + * + * @param : + * @return void + * @date 2020/8/16 20:56 + */ + @PostConstruct + public void init() { + try { + minioClient = MinioClient.builder() + .endpoint(this.minIoProperties.getUrl()) + .credentials(this.minIoProperties.getAccessKey(), this.minIoProperties.getSecretKey()) + .build(); + createBucket(this.minIoProperties.getBucketName()); + } catch (Exception e) { + log.error("初始化minio配置异常:", e); + } + } + + // **************************** ↓↓↓↓↓↓ 桶操作 ↓↓↓↓↓↓ **************************** + + /** + * 判断桶是否存在 + * + * @param bucketName 桶名 + * @return true:存在 false:不存在 + * @date 2020/8/16 20:53 + */ + @SneakyThrows(Exception.class) + public static boolean bucketExists(String bucketName) { + return minioClient.bucketExists(BucketExistsArgs.builder().bucket(bucketName).build()); + } + + /** + * 创建桶 + * + * @param bucketName 桶名 + * @return void + * @date 2020/8/16 20:53 + */ + @SneakyThrows(Exception.class) + public static void createBucket(String bucketName) { + boolean isExist = bucketExists(bucketName); + if (!isExist) { + minioClient.makeBucket(MakeBucketArgs.builder().bucket(bucketName).build()); + } + } + + /** + * 获取全部桶 + * + * @return 桶信息 + * @date 2020/8/16 23:28 + */ + @SneakyThrows(Exception.class) + public static List getAllBuckets() { + return minioClient.listBuckets(); + } + + // **************************** ↓↓↓↓↓↓ 文件操作 ↓↓↓↓↓↓ **************************** + + /** + * 文件上传 + * + * @param bucketName 桶名 + * @param file 文件 + * @return 文件url地址 + * @date 2020/8/16 23:40 + */ + @SneakyThrows(Exception.class) + public static String upload(String bucketName, MultipartFile file) { + final InputStream inputStream = file.getInputStream(); + final String fileName = file.getOriginalFilename(); + minioClient.putObject(PutObjectArgs.builder() + .bucket(bucketName) + .object(fileName) + .stream(inputStream, file.getSize(), -1) + .contentType(file.getContentType()) + .build()); + inputStream.close(); + return getFileUrl(bucketName, fileName); + } + + /** + * 文件上传 + * + * @param bucketName 桶名 + * @param file 文件 + * @param objectName 存储的文件对象路径 + * @return 文件url地址 + * @date 2020/8/16 23:40 + */ + @SneakyThrows(Exception.class) + public static String upload(String bucketName, MultipartFile file, String objectName) { + final InputStream inputStream = file.getInputStream(); + minioClient.putObject(PutObjectArgs.builder() + .bucket(bucketName) + .object(objectName) + .stream(inputStream, file.getSize(), -1) + .contentType(file.getContentType()) + .build()); + inputStream.close(); + return getFileUrl(bucketName, objectName); + } + + /** + * 删除文件 + * + * @param bucketName 桶名 + * @param fileName 文件名 + * @return void + * @date 2020/8/16 20:53 + */ + @SneakyThrows(Exception.class) + public static void deleteFile(String bucketName, String fileName) { + minioClient.removeObject(RemoveObjectArgs.builder().bucket(bucketName).object(fileName).build()); + } + + /** + * 下载文件 + * + * @param bucketName 桶名 + * @param fileName 文件名 + * @param response + * @return void + * @date 2020/8/17 0:34 + */ + @SneakyThrows(Exception.class) + public static void download(String bucketName, String fileName, HttpServletResponse response) { + // 获取对象的元数据 + final StatObjectResponse stat = minioClient.statObject(StatObjectArgs.builder().bucket(bucketName).object(fileName).build()); + response.setContentType(stat.contentType()); + response.setCharacterEncoding("UTF-8"); + response.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(fileName, "UTF-8")); + InputStream is = minioClient.getObject(GetObjectArgs.builder().bucket(bucketName).object(fileName).build()); + IOUtils.copy(is, response.getOutputStream()); + is.close(); + } + + /** + * 获取minio文件的预览地址 + * + * @param bucketName 桶名 + * @param fileName 文件名 + * @return 预览地址 + * @date 2020/8/16 22:07 + */ + @SneakyThrows(Exception.class) + public static String getFileUrl(String bucketName, String fileName) { + return minioClient.getPresignedObjectUrl(GetPresignedObjectUrlArgs.builder() + .method(Method.GET) + .bucket(bucketName) + .object(fileName) + .build()); + } + + /** + * 获取minio文件的预览地址 + * + * @param bucketName 桶名 + * @param fileName 文件名 + * @param timeUnit 过期时间单位 + * @param time 过期时间 + * @return 预览地址 + * @date 2020/8/16 22:07 + */ + @SneakyThrows(Exception.class) + public static String getFileUrl(String bucketName, String fileName, TimeUnit timeUnit, int time) { + return minioClient.getPresignedObjectUrl(GetPresignedObjectUrlArgs.builder() + .method(Method.GET) + .bucket(bucketName) + .object(fileName) + .expiry(time, timeUnit) + .build()); + } + +} diff --git a/smallboot-api/common/file/src/main/resources/application-file.yml b/smallboot-api/common/file/src/main/resources/application-file.yml new file mode 100644 index 0000000..f7e18d9 --- /dev/null +++ b/smallboot-api/common/file/src/main/resources/application-file.yml @@ -0,0 +1,15 @@ +spring: + servlet: + multipart: + enabled: true # 是否开启文件上传支持,默认为true + # location: E:/tmp # 指定文件上传路径 【注:文件路径必须存在! windows: "E:/tmp" liunx: "/tmp"】 + max-file-size: 100MB # 指定文件大小最大值,默认1MB + max-request-size: 100MB # 指定每次请求的最大值,默认为10MB + file-size-threshold: 0 # 设定文件写入磁盘的阈值,单位为MB或KB,默认为0。表示收到文件到达这么大后,不在放入缓存,而是写入临时文件 + +# ====================== ↓↓↓↓↓↓ MinIO文件服务器 ↓↓↓↓↓↓ ====================== +minio: + url: ${smallboot.minio.url} # TODO 不能使用内网ip -- 下载文件的时候无法下载 403... + accessKey: ${smallboot.minio.accessKey} + secretKey: ${smallboot.minio.secretKey} + bucketName: ${smallboot.minio.bucketName} diff --git a/smallboot-api/common/log/pom.xml b/smallboot-api/common/log/pom.xml new file mode 100644 index 0000000..3faaff7 --- /dev/null +++ b/smallboot-api/common/log/pom.xml @@ -0,0 +1,26 @@ + + + 4.0.0 + + + common + com.zhengqing + ${revision} + + + log + + ${project.artifactId} + ${revision} + jar + + + + + + + + + diff --git a/smallboot-api/common/log/src/main/resources/application-log.yml b/smallboot-api/common/log/src/main/resources/application-log.yml new file mode 100644 index 0000000..7b5d795 --- /dev/null +++ b/smallboot-api/common/log/src/main/resources/application-log.yml @@ -0,0 +1,9 @@ +# 配置日志地址 +logging: + # config: classpath:log/logback.xml + config: classpath:logback-spring.xml + log: + home: logs # 日志文件保存位置 + max-history-day: 30 # 日志文件保留天数 +# file: +# name: ${logging.log.home}/${spring.application.name}.log # Spring Boot Admin提供了基于Web页面的方式实时查看服务输出的本地日志,前提是服务中配置了`logging.file.name` diff --git a/smallboot-api/common/log/src/main/resources/banner.txt b/smallboot-api/common/log/src/main/resources/banner.txt new file mode 100644 index 0000000..4aff8f2 --- /dev/null +++ b/smallboot-api/common/log/src/main/resources/banner.txt @@ -0,0 +1,15 @@ +${AnsiColor.BRIGHT_GREEN} + +${AnsiColor.BRIGHT_CYAN} _ _ _ _ +${AnsiColor.BRIGHT_CYAN} | | | | | | | | +${AnsiColor.BRIGHT_CYAN} ___ _ __ ___ __ _ | | | | | |__ ___ ___ | |_ +${AnsiColor.BRIGHT_CYAN} / __| | '_ ` _ \ / _` | | | | | | '_ \ / _ \ / _ \ | __| +${AnsiColor.BRIGHT_CYAN} \__ \ | | | | | | | (_| | | | | | | |_) | | (_) | | (_) | | |_ +${AnsiColor.BRIGHT_CYAN} |___/ |_| |_| |_| \__,_| |_| |_| |_.__/ \___/ \___/ \__| + +${AnsiColor.BLUE} +Application Name: ${spring.application.name} +Application Profile: ${AnsiColor.RED}${spring.profiles.active}${AnsiColor.BLUE} +Server Port: ${server.port} +Spring Boot Version: ${spring-boot.version}${spring-boot.formatted-version} +Knife4j 访问地址: http://127.0.0.1:${server.port}/doc.html diff --git a/smallboot-api/common/log/src/main/resources/logback-spring.xml b/smallboot-api/common/log/src/main/resources/logback-spring.xml new file mode 100644 index 0000000..b0ebc0a --- /dev/null +++ b/smallboot-api/common/log/src/main/resources/logback-spring.xml @@ -0,0 +1,155 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + DEBUG + + + + UTF-8 + + ${CONSOLE_LOG_PATTERN} + + + + + + + + + + + + + + + + + + + + + + ${LOG_FILE_NAME} + + ${LOG_MAX_HISTORY_DAY} + + + + + UTF-8 + + %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n + + + + + 100MB + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/smallboot-api/common/pom.xml b/smallboot-api/common/pom.xml new file mode 100644 index 0000000..3d46063 --- /dev/null +++ b/smallboot-api/common/pom.xml @@ -0,0 +1,35 @@ + + + 4.0.0 + + + smallboot-api + com.zhengqing + ${revision} + + + common + pom + + 公共模块 + + + auth + base + core + db + file + log + redis + swagger + web + + + + + + + + diff --git a/smallboot-api/common/redis/pom.xml b/smallboot-api/common/redis/pom.xml new file mode 100644 index 0000000..6b56c6f --- /dev/null +++ b/smallboot-api/common/redis/pom.xml @@ -0,0 +1,47 @@ + + + 4.0.0 + + + common + com.zhengqing + ${revision} + + + redis + + ${project.artifactId} + ${revision} + jar + + + + + + + + + + + + org.springframework.boot + spring-boot-starter-data-redis-reactive + + + + org.apache.commons + commons-pool2 + + + + + + org.redisson + redisson-spring-boot-starter + 3.17.6 + + + + diff --git a/smallboot-api/common/redis/src/main/java/com/zhengqing/common/redis/config/RedisConfig.java b/smallboot-api/common/redis/src/main/java/com/zhengqing/common/redis/config/RedisConfig.java new file mode 100644 index 0000000..35bfa27 --- /dev/null +++ b/smallboot-api/common/redis/src/main/java/com/zhengqing/common/redis/config/RedisConfig.java @@ -0,0 +1,48 @@ +package com.zhengqing.common.redis.config; + +import com.fasterxml.jackson.annotation.JsonAutoDetect; +import com.fasterxml.jackson.annotation.PropertyAccessor; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.redis.connection.RedisConnectionFactory; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer; +import org.springframework.data.redis.serializer.StringRedisSerializer; + +/** + *

+ * Redis配置类 (注意设置key和value的序列化方式,否则存到redis里的数据会乱码) + *

+ * + * @author zhengqingya + * @description + * @date 2019/11/27 12:34 + */ +@Configuration +public class RedisConfig { + + @Bean + @SuppressWarnings("all") + public RedisTemplate redisTemplate(RedisConnectionFactory factory) { + RedisTemplate template = new RedisTemplate(); + template.setConnectionFactory(factory); + Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class); + ObjectMapper om = new ObjectMapper(); + om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY); + om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL); + jackson2JsonRedisSerializer.setObjectMapper(om); + StringRedisSerializer stringRedisSerializer = new StringRedisSerializer(); + // key采用String的序列化方式 + template.setKeySerializer(stringRedisSerializer); + // hash的key也采用String的序列化方式 + template.setHashKeySerializer(stringRedisSerializer); + // value序列化方式采用jackson + template.setValueSerializer(jackson2JsonRedisSerializer); + // hash的value序列化方式采用jackson + template.setHashValueSerializer(jackson2JsonRedisSerializer); + template.afterPropertiesSet(); + return template; + } + +} diff --git a/smallboot-api/common/redis/src/main/java/com/zhengqing/common/redis/config/RedisKeyExpirationListener.java b/smallboot-api/common/redis/src/main/java/com/zhengqing/common/redis/config/RedisKeyExpirationListener.java new file mode 100644 index 0000000..81e660f --- /dev/null +++ b/smallboot-api/common/redis/src/main/java/com/zhengqing/common/redis/config/RedisKeyExpirationListener.java @@ -0,0 +1,41 @@ +package com.zhengqing.common.redis.config; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.data.redis.connection.Message; +import org.springframework.data.redis.listener.KeyExpirationEventMessageListener; +import org.springframework.data.redis.listener.RedisMessageListenerContainer; +import org.springframework.stereotype.Component; + +/** + *

+ * 监听redis过期事件 + *

+ * + * @author zhengqingya + * @description + * @date 2020/11/13 13:55 + */ +@Slf4j +@Component +public class RedisKeyExpirationListener extends KeyExpirationEventMessageListener { + + public RedisKeyExpirationListener(RedisMessageListenerContainer listenerContainer) { + super(listenerContainer); + } + + /** + * redis - key失效事件监听 + * + * @param message message.toString()获取失效事件key + * @param pattern + * @return void + * @author zhengqingya + * @date 2020/11/13 14:12 + */ + @Override + public void onMessage(Message message, byte[] pattern) { + String expiredKey = message.toString(); + log.debug(" ****************** redis -> 过期key: 【{}】 ****************** ", expiredKey); + } + +} diff --git a/smallboot-api/common/redis/src/main/java/com/zhengqing/common/redis/config/RedisListenerConfig.java b/smallboot-api/common/redis/src/main/java/com/zhengqing/common/redis/config/RedisListenerConfig.java new file mode 100644 index 0000000..6ff122e --- /dev/null +++ b/smallboot-api/common/redis/src/main/java/com/zhengqing/common/redis/config/RedisListenerConfig.java @@ -0,0 +1,41 @@ +package com.zhengqing.common.redis.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Primary; +import org.springframework.data.redis.connection.RedisConnectionFactory; +import org.springframework.data.redis.listener.RedisMessageListenerContainer; + +/** + *

+ * redis监听事件配置 + *

+ * + * @author zhengqingya + * @description 1.监听redis过期事件 {@link RedisKeyExpirationListener} + * - 2.发布订阅 {@link com.zhengqing.app.config.DemoRedisListenerConfig } + * @date 2020/11/13 13:56 + */ +@Configuration +public class RedisListenerConfig { + @Bean + @Primary + RedisMessageListenerContainer redisMessageListenerContainer(RedisConnectionFactory connectionFactory +// , MessageListener redisTestListener + ) { + RedisMessageListenerContainer container = new RedisMessageListenerContainer(); + container.setConnectionFactory(connectionFactory); + // 加入发布订阅配置 + // 添加消息监听者,可以是多个 +// container.addMessageListener( +// new MessageListenerAdapter(redisTestListener), +// new ChannelTopic(RedisConstant.REDIS_CHANNEL_TEST) +// ); +// container.addMessageListener( +// new MessageListenerAdapter(new MyListener()), +// new ChannelTopic(RedisConstant.REDIS_CHANNEL_ZQ) +// ); + return container; + } + +} diff --git a/smallboot-api/common/redis/src/main/java/com/zhengqing/common/redis/config/RedissonConfig.java b/smallboot-api/common/redis/src/main/java/com/zhengqing/common/redis/config/RedissonConfig.java new file mode 100644 index 0000000..5f9f17c --- /dev/null +++ b/smallboot-api/common/redis/src/main/java/com/zhengqing/common/redis/config/RedissonConfig.java @@ -0,0 +1,51 @@ +package com.zhengqing.common.redis.config; + +import org.redisson.Redisson; +import org.redisson.api.RedissonClient; +import org.redisson.config.Config; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + *

Redisson配置类

+ * + * @author zhengqingya + * @description https://www.bookstack.cn/read/redisson-wiki-zh/2.-%E9%85%8D%E7%BD%AE%E6%96%B9%E6%B3%95.md + * @date 2022/1/14 11:03 下午 + */ +@Configuration +public class RedissonConfig { + + @Value("${spring.redis.host}") + private String host; + + @Value("${spring.redis.port}") + private String port; + + @Value("${spring.redis.password}") + private String password; + + @Value("${spring.redis.database}") + private Integer database; + + @Value("${spring.redis.jedis.pool.min-idle}") + private Integer connectionMinimumIdleSize; + + @Value("${spring.redis.timeout}") + private Integer timeout; + + @Bean + public RedissonClient getRedisson() { + Config config = new Config(); + config + .useSingleServer() + .setAddress("redis://" + this.host + ":" + this.port) + .setPassword(this.password) + .setDatabase(this.database) + .setConnectionMinimumIdleSize(this.connectionMinimumIdleSize) + .setTimeout(this.timeout); + return Redisson.create(config); + } + +} diff --git a/smallboot-api/common/redis/src/main/java/com/zhengqing/common/redis/constant/RedisConstant.java b/smallboot-api/common/redis/src/main/java/com/zhengqing/common/redis/constant/RedisConstant.java new file mode 100644 index 0000000..75397e3 --- /dev/null +++ b/smallboot-api/common/redis/src/main/java/com/zhengqing/common/redis/constant/RedisConstant.java @@ -0,0 +1,43 @@ +package com.zhengqing.common.redis.constant; + +/** + *

+ * 全局常用变量 - Redis缓存 + *

+ * + * @author zhengqingya + * @description + * @date 2019/10/12 14:47 + */ +public interface RedisConstant { + + /** + * ID生成 + */ + String ID_GENERATE_KEY_PREFIX = "smallboot:id_generate:"; + /** + * 记录重复ID + */ + String ID_GENERATE_REPEAT_KEY = "smallboot:id_generate_repeat"; + + /** + * 随机code码生成 + */ + String GENERATE_RANDOM_CODE_KEY = "smallboot:generate-random-code"; + /** + * 随机code码生成尝试次数记录 -- 用于码用尽告警 + */ + String GENERATE_RANDOM_CODE_RETRY_NUM_KEY = "smallboot:generate-random-code:retry-num"; + int GENERATE_RANDOM_CODE_MAX_RETRY_NUM = 5; + + /** + * 发布订阅通道 + */ + String REDIS_CHANNEL_TEST = "channel_test"; + + /** + * 缓存null值过期时间 + */ + Long CACHE_NULL_TTL = 2L; + +} diff --git a/smallboot-api/common/redis/src/main/java/com/zhengqing/common/redis/listener/RedisTestListener.java b/smallboot-api/common/redis/src/main/java/com/zhengqing/common/redis/listener/RedisTestListener.java new file mode 100644 index 0000000..dda1d98 --- /dev/null +++ b/smallboot-api/common/redis/src/main/java/com/zhengqing/common/redis/listener/RedisTestListener.java @@ -0,0 +1,25 @@ +package com.zhengqing.common.redis.listener; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.data.redis.connection.Message; +import org.springframework.data.redis.connection.MessageListener; +import org.springframework.stereotype.Component; + +/** + *

发布订阅 -- 测试消费者

+ * + * @author zhengqingya + * @description + * @date 2022/6/17 17:38 + */ +@Slf4j +@Component +public class RedisTestListener implements MessageListener { + + @Override + public void onMessage(Message message, byte[] pattern) { + String msg = new String(message.getBody()); + log.info("[redis] 收到订阅消息:{}", msg); + } + +} \ No newline at end of file diff --git a/smallboot-api/common/redis/src/main/java/com/zhengqing/common/redis/model/bo/RedisGeoPoint.java b/smallboot-api/common/redis/src/main/java/com/zhengqing/common/redis/model/bo/RedisGeoPoint.java new file mode 100644 index 0000000..f2d4b69 --- /dev/null +++ b/smallboot-api/common/redis/src/main/java/com/zhengqing/common/redis/model/bo/RedisGeoPoint.java @@ -0,0 +1,39 @@ +package com.zhengqing.common.redis.model.bo; + +import lombok.*; + + +/** + *

+ * Redis GEO 坐标信息 + *

+ * + * @author zhengqingya + * @description + * @date 2019/11/27 14:38 + */ +@Getter +@Setter +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class RedisGeoPoint { + + /** + * key + */ + private String key; + /** + * members + */ + private String member; + /** + * 经度 + */ + private Double lng; + /** + * 纬度 + */ + private Double lat; + +} diff --git a/smallboot-api/common/redis/src/main/java/com/zhengqing/common/redis/util/RedisBusinessCacheUtil.java b/smallboot-api/common/redis/src/main/java/com/zhengqing/common/redis/util/RedisBusinessCacheUtil.java new file mode 100644 index 0000000..9019bc4 --- /dev/null +++ b/smallboot-api/common/redis/src/main/java/com/zhengqing/common/redis/util/RedisBusinessCacheUtil.java @@ -0,0 +1,250 @@ +package com.zhengqing.common.redis.util; + +import cn.hutool.core.util.BooleanUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.json.JSONObject; +import cn.hutool.json.JSONUtil; +import com.zhengqing.common.redis.constant.RedisConstant; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.SuperBuilder; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.redis.core.StringRedisTemplate; +import org.springframework.stereotype.Component; + +import java.time.LocalDateTime; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; +import java.util.function.Function; + +/** + *

+ * Redis业务缓存工具类 + *

+ * + * @author zhengqingya + * @description + * @date 2019/11/27 14:38 + */ +@Component +public class RedisBusinessCacheUtil { + + private static StringRedisTemplate redisTemplate; + + @Autowired + public RedisBusinessCacheUtil(StringRedisTemplate redisTemplate) { + RedisBusinessCacheUtil.redisTemplate = redisTemplate; + } + + + // ============================ ↓↓↓↓↓↓ 统一解决缓存问题 ↓↓↓↓↓↓ ============================ + + @Data + @AllArgsConstructor + @NoArgsConstructor + @SuperBuilder + static class RedisData { + private LocalDateTime expireTime; + private Object data; + } + + /** + * 逻辑过期 + * + * @param key 缓存key + * @param value 值 + * @param time 时间 + * @param unit 时间单位 + * @return void + * @author zhengqingya + * @date 2022/9/30 13:50 + */ + public static void setWithLogicalExpire(String key, Object value, Long time, TimeUnit unit) { + // 设置逻辑过期 + RedisData redisData = RedisData.builder() + .data(value) + // LocalDateTime.now() 获取当前时间 + // plusSeconds 添加秒数 + // 使用TimeUnit包的 toSeconds将时间转换为秒数 + .expireTime(LocalDateTime.now().plusSeconds(unit.toSeconds(time))) + .build(); + // 写入Redis + RedisUtil.set(key, JSONUtil.toJsonStr(redisData)); + } + + /** + * 利用缓存空值的方式解决缓存穿透 + * + * @param key 缓存key + * @param id 查db时的参数 + * @param type 返回结果类型 + * @param dbFallback ID:参数 R:返回值 + * @param time 时间 + * @param unit 时间单位 + * @return 返回值 + * @author zhengqingya + * @date 2022/9/30 13:55 + */ + public static R queryWithPassThrough(String key, ID id, Class type, + Function dbFallback, + Long time, TimeUnit unit) { + // 1、先查缓存 + String json = RedisUtil.get(key); + if (StrUtil.isNotBlank(json)) { + return JSONUtil.toBean(json, type); + } + // 判断命中的是否是空值“” + if (json != null) { + return null; + } + // 2、查库 -- 根据不同业务查询各自的数据库 + R r = dbFallback.apply(id); + if (r == null) { + // 将空值写入redis + RedisUtil.setEx(key, "", RedisConstant.CACHE_NULL_TTL, TimeUnit.MINUTES); + return null; + } + // 3、结果写入缓存 + RedisUtil.setEx(key, r, time, unit); + return r; + } + + /** + * 定义线程池 + */ + private static final ExecutorService CACHE_REBUILD_EXECUTOR = Executors.newFixedThreadPool(10); + + /** + * 缓存击穿 -- 逻辑过期 + * + * @param key 缓存key + * @param id 查db时的参数 + * @param type 返回结果类型 + * @param dbFallback ID:参数 R:返回值 + * @param time 时间 + * @param unit 时间单位 + * @param lockKey 锁的key + * @return 返回值 + * @author zhengqingya + * @date 2022/9/30 14:15 + */ + public static R queryWithLogicalExpire(String key, ID id, Class type, + Function dbFallback, + Long time, TimeUnit unit, String lockKey) { + // 1、先查缓存 + String json = RedisUtil.get(key); + if (StrUtil.isBlank(json)) { + return null; + } + RedisData redisData = JSONUtil.toBean(json, RedisData.class); + R r = JSONUtil.toBean((JSONObject) redisData.getData(), type); + LocalDateTime expireTime = redisData.getExpireTime(); + // 2、判断是否过期 + if (expireTime.isAfter(LocalDateTime.now())) { + // 2.1、未过期,直接返回 + return r; + } + // 2.2、已过期 -- 缓存重建 + // 获取互斥锁 + boolean isLock = tryLock(lockKey); + if (isLock) { + // 开启独立线程,实现缓存重建 + CACHE_REBUILD_EXECUTOR.submit(() -> { + try { + // 查询数据库 + R newR = dbFallback.apply(id); + // 重建缓存 + setWithLogicalExpire(key, newR, time, unit); + } catch (Exception e) { + throw new RuntimeException(e); + } finally { + // 释放锁 + unlock(lockKey); + } + }); + } + // 返回过期的信息 + return r; + } + + /** + * 缓存击穿 -- 互斥锁 + * + * @param key 缓存key + * @param id 查db时的参数 + * @param type 返回结果类型 + * @param dbFallback ID:参数 R:返回值 + * @param time 时间 + * @param unit 时间单位 + * @param lockKey 锁的key + * @return 返回值 + * @author zhengqingya + * @date 2022/9/30 14:15 + */ + public static R queryWithMutex(String key, ID id, Class type, + Function dbFallback, + Long time, TimeUnit unit, String lockKey) { + // 1、先查缓存 + String json = RedisUtil.get(key); + if (StrUtil.isNotBlank(json)) { + return JSONUtil.toBean(json, type); + } + if (json != null) { + return null; + } + // 2、实现缓存重建 + // 获取互斥锁 + R r = null; + try { + boolean isLock = tryLock(lockKey); + if (!isLock) { + // 获取锁失败,休眠并重试 + Thread.sleep(50); + return queryWithMutex(key, id, type, dbFallback, time, unit, lockKey); + } + // 获取锁成功,根据id查询数据库 + r = dbFallback.apply(id); + if (r == null) { + // 将空值写入redis + RedisUtil.setEx(key, "", RedisConstant.CACHE_NULL_TTL, TimeUnit.MINUTES); + return null; + } + // 存在则将数据写入redis + RedisUtil.setEx(key, r, time, unit); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } finally { + // 释放锁 + unlock(lockKey); + } + // 3、返回 + return r; + } + + /** + * 获取互斥锁 + * + * @param key 缓存key + * @return true:成功 false:失败 + * @author zhengqingya + * @date 2022/9/30 14:15 + */ + private static boolean tryLock(String key) { + Boolean flag = redisTemplate.opsForValue().setIfAbsent(key, "1", 10, TimeUnit.SECONDS); + return BooleanUtil.isTrue(flag); + } + + /** + * 释放锁 + * + * @param key 缓存key + * @author zhengqingya + * @date 2022/9/30 14:15 + */ + private static void unlock(String key) { + RedisUtil.delete(key); + } + +} diff --git a/smallboot-api/common/redis/src/main/java/com/zhengqing/common/redis/util/RedisGeoUtil.java b/smallboot-api/common/redis/src/main/java/com/zhengqing/common/redis/util/RedisGeoUtil.java new file mode 100644 index 0000000..455e546 --- /dev/null +++ b/smallboot-api/common/redis/src/main/java/com/zhengqing/common/redis/util/RedisGeoUtil.java @@ -0,0 +1,171 @@ +package com.zhengqing.common.redis.util; + +import com.google.common.collect.Lists; +import com.zhengqing.common.redis.model.bo.RedisGeoPoint; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.geo.*; +import org.springframework.data.redis.connection.RedisGeoCommands; +import org.springframework.data.redis.core.StringRedisTemplate; +import org.springframework.stereotype.Service; + +import java.util.List; + +/** + *

+ * Redis GEO 工具类 + *

+ * + * @author zhengqingya + * @description + * @date 2019/11/27 14:38 + */ +@Service +public class RedisGeoUtil { + + private static StringRedisTemplate redisTemplate; + + @Autowired + public RedisGeoUtil(StringRedisTemplate redisTemplate) { + RedisGeoUtil.redisTemplate = redisTemplate; + } + + /** + * 添加经纬度信息 + * redis命令: geoadd key 116.405285 39.904989 "北京" + * + * @param key key + * @param point 坐标 + * @param member 成员 + * @return java.lang.Long + * @author zhengqingya + * @date 2019/11/27 15:53 + */ + public static Long geoAdd(String key, Point point, String member) { + if (redisTemplate.hasKey(key)) { + redisTemplate.opsForGeo().remove(key, member); + } + return redisTemplate.opsForGeo().add(key, point, member); + } + + /** + * 查找指定key的经纬度信息,可以指定多个member,批量返回 + * redis命令: geopos key 北京 + * + * @param key key + * @param members 成员 + * @return 坐标 + * @author zhengqingya + * @date 2019/11/27 15:57 + */ + public static List geoPos(String key, String... members) { + return redisTemplate.opsForGeo().position(key, members); + } + + /** + * 返回两个位置的距离,可以指定单位,比如米m,千米km,英里mi,英尺ft + * redis命令: geodist key 北京 上海 + * + * @param key key + * @param member1 成员1 + * @param member2 成员2 + * @param metric 单位 + * @return 距离 + * @author zhengqingya + * @date 2019/11/27 15:58 + */ + public static Distance geoDist(String key, String member1, String member2, Metric metric) { + return redisTemplate.opsForGeo().distance(key, member1, member2, metric); + } + + /** + * 根据给定的经纬度,返回半径不超过指定距离的元素 + * redis命令: georadius key 116.405285 39.904989 100 km WITHDIST WITHCOORD ASC + * + * @param key key + * @param circle 半径信息 + * @param count 限定返回的记录数 + * @return 满足条件的数据 + * @author zhengqingya + * @date 2019/11/27 15:58 + */ + public static GeoResults> geoRadius(String key, Circle circle, long count) { + // includeDistance 包含距离 + // includeCoordinates 包含经纬度 + // sortAscending 正序排序 + // limit 限定返回的记录数 + RedisGeoCommands.GeoRadiusCommandArgs args = RedisGeoCommands.GeoRadiusCommandArgs.newGeoRadiusArgs() + .includeDistance().includeCoordinates().sortAscending().limit(count); + return redisTemplate.opsForGeo().radius(key, circle, args); + } + + + /** + * 根据指定经纬度返回指定范围内最近的元素 + * + * @param key key + * @param lng 经度 + * @param lat 纬度 + * @param distanceValue 距离值 + * @param metric 距离单位 + * @param count 限定返回的记录数 + * @return 满足条件的数据 + * @author zhengqingya + * @date 2019/11/27 15:58 + */ + public static List geoNear(String key, Double lng, Double lat, + double distanceValue, Metric metric, long count) { + Circle circle = new Circle(new Point(lng, lat), new Distance(distanceValue, metric)); + GeoResults> geoLocationList = RedisGeoUtil.geoRadius(key, circle, count); + List resultList = Lists.newLinkedList(); + geoLocationList.forEach(item -> { + RedisGeoCommands.GeoLocation location = item.getContent(); + Point point = location.getPoint(); + RedisGeoPoint position = RedisGeoPoint.builder() + .key(key) + .member(location.getName()) + .lng(point.getX()) + .lat(point.getY()) + .build(); + resultList.add(position); + }); + return resultList; + } + + /** + * 根据指定的地点查询半径在指定范围内的位置 + * redis命令: georadiusbymember key 北京 100 km WITHDIST WITHCOORD ASC COUNT 5 + * + * @param key key + * @param member 成员 + * @param distance 距离 + * @param count 限定返回的记录数 + * @return 满足条件的数据 + * @author zhengqingya + * @date 2019/11/27 15:58 + */ + public static GeoResults> geoRadiusByMember( + String key, String member, Distance distance, long count) { + // includeDistance 包含距离 + // includeCoordinates 包含经纬度 + // sortAscending 正序排序 + // limit 限定返回的记录数 + RedisGeoCommands.GeoRadiusCommandArgs args = RedisGeoCommands.GeoRadiusCommandArgs.newGeoRadiusArgs() + .includeDistance().includeCoordinates().sortAscending().limit(count); + return redisTemplate.opsForGeo().radius(key, member, distance, args); + } + + /** + * 获取一个或多个位置元素的 geohash 值 + * redis命令: geohash key 北京 + * + * @param key key + * @param members 成员 + * @return 结果 + * @author zhengqingya + * @date 2019/11/27 16:13 + */ + public static List geoHash(String key, String... members) { + return redisTemplate.opsForGeo().hash(key, members); + } + +} diff --git a/smallboot-api/common/redis/src/main/java/com/zhengqing/common/redis/util/RedisUtil.java b/smallboot-api/common/redis/src/main/java/com/zhengqing/common/redis/util/RedisUtil.java new file mode 100644 index 0000000..7d188d6 --- /dev/null +++ b/smallboot-api/common/redis/src/main/java/com/zhengqing/common/redis/util/RedisUtil.java @@ -0,0 +1,933 @@ +package com.zhengqing.common.redis.util; + +import cn.hutool.json.JSONUtil; +import org.redisson.api.RedissonClient; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.redis.connection.DataType; +import org.springframework.data.redis.core.Cursor; +import org.springframework.data.redis.core.ScanOptions; +import org.springframework.data.redis.core.StringRedisTemplate; +import org.springframework.data.redis.core.ZSetOperations.TypedTuple; +import org.springframework.stereotype.Component; + +import java.util.*; +import java.util.Map.Entry; +import java.util.concurrent.TimeUnit; + +/** + *

+ * Redis工具类 + *

+ * + * @author zhengqingya + * @description + * @date 2019/11/27 14:38 + */ +@Component +public class RedisUtil { + + private static StringRedisTemplate redisTemplate; + + private static RedissonClient redissonClient; + + @Autowired + public RedisUtil(StringRedisTemplate redisTemplate, RedissonClient redissonClient) { + RedisUtil.redisTemplate = redisTemplate; + RedisUtil.redissonClient = redissonClient; + } + + /** -------------------key相关操作--------------------- */ + + /** + * 删除key + */ + public static void delete(String key) { + redisTemplate.delete(key); + } + + /** + * 批量删除key + */ + public static void delete(Collection keys) { + redisTemplate.delete(keys); + } + + /** + * 序列化key + */ + public static byte[] dump(String key) { + return redisTemplate.dump(key); + } + + /** + * 是否存在key + */ + public static Boolean hasKey(String key) { + return redisTemplate.hasKey(key); + } + + /** + * 设置过期时间 + */ + public static Boolean expire(String key, long timeout, TimeUnit unit) { + return redisTemplate.expire(key, timeout, unit); + } + + /** + * 设置过期时间 + */ + public static Boolean expireAt(String key, Date date) { + return redisTemplate.expireAt(key, date); + } + + /** + * 查找匹配的key + */ + public static Set keys(String pattern) { + return redisTemplate.keys(pattern); + } + + /** + * 将当前数据库的 key 移动到给定的数据库 db 当中 + */ + public static Boolean move(String key, int dbIndex) { + return redisTemplate.move(key, dbIndex); + } + + /** + * 移除 key 的过期时间,key 将持久保持 + */ + public static Boolean persist(String key) { + return redisTemplate.persist(key); + } + + /** + * 返回 key 的剩余的过期时间 + */ + public static Long getExpire(String key, TimeUnit unit) { + return redisTemplate.getExpire(key, unit); + } + + /** + * 返回 key 的剩余的过期时间 + */ + public static Long getExpire(String key) { + return redisTemplate.getExpire(key); + } + + /** + * 从当前数据库中随机返回一个 key + */ + public static String randomKey() { + return redisTemplate.randomKey(); + } + + /** + * 修改 key 的名称 + */ + public static void rename(String oldKey, String newKey) { + redisTemplate.rename(oldKey, newKey); + } + + /** + * 仅当 newkey 不存在时,将 oldKey 改名为 newkey + */ + public static Boolean renameIfAbsent(String oldKey, String newKey) { + return redisTemplate.renameIfAbsent(oldKey, newKey); + } + + /** + * 返回 key 所储存的值的类型 + */ + public static DataType type(String key) { + return redisTemplate.type(key); + } + + /** -------------------string相关操作--------------------- */ + + /** + * 设置指定 key 的值 + */ + public static void set(String key, String value) { + redisTemplate.opsForValue().set(key, value); + } + + /** + * 获取指定 key 的值 + */ + public static String get(String key) { + return redisTemplate.opsForValue().get(key); + } + + /** + * 返回 key 中字符串值的子字符 + */ + public static String getRange(String key, long start, long end) { + return redisTemplate.opsForValue().get(key, start, end); + } + + /** + * 将给定 key 的值设为 value ,并返回 key 的旧值(old value) + */ + public static String getAndSet(String key, String value) { + return redisTemplate.opsForValue().getAndSet(key, value); + } + + /** + * 对 key 所储存的字符串值,获取指定偏移量上的位(bit) + */ + public static Boolean getBit(String key, long offset) { + return redisTemplate.opsForValue().getBit(key, offset); + } + + /** + * 批量获取 + */ + public static List multiGet(Collection keys) { + return redisTemplate.opsForValue().multiGet(keys); + } + + /** + * 设置ASCII码, 字符串'a'的ASCII码是97, 转为二进制是'01100001', 此方法是将二进制第offset位值变为value + * + * @param key + * @param offset 位置 + * @param value 值,true为1, false为0 + * @return + */ + public static boolean setBit(String key, long offset, boolean value) { + return redisTemplate.opsForValue().setBit(key, offset, value); + } + + /** + * 将值 value 关联到 key ,并将 key 的过期时间设为 timeout + * + * @param key 缓存key + * @param value 值 + * @param timeout 过期时间 + * @param unit 时间单位, 天:TimeUnit.DAYS 小时:TimeUnit.HOURS 分钟:TimeUnit.MINUTES 秒:TimeUnit.SECONDS 毫秒:TimeUnit.MILLISECONDS + */ + public static void setEx(String key, String value, long timeout, TimeUnit unit) { + redisTemplate.opsForValue().set(key, value, timeout, unit); + } + + /** + * 将值 value 关联到 key ,并将 key 的过期时间设为 timeout + * + * @param key 缓存key + * @param value 值 + * @param timeout 过期时间 + * @param unit 时间单位, 天:TimeUnit.DAYS 小时:TimeUnit.HOURS 分钟:TimeUnit.MINUTES 秒:TimeUnit.SECONDS 毫秒:TimeUnit.MILLISECONDS + */ + public static void setEx(String key, Object value, long timeout, TimeUnit unit) { + redisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(value), timeout, unit); + } + + /** + * 只有在 key 不存在时设置 key 的值 + * + * @param key + * @param value + * @return 之前已经存在返回false, 不存在返回true + */ + public static boolean setIfAbsent(String key, String value) { + return redisTemplate.opsForValue().setIfAbsent(key, value); + } + + /** + * 用 value 参数覆写给定 key 所储存的字符串值,从偏移量 offset 开始 + * + * @param key + * @param value + * @param offset 从指定位置开始覆写 + */ + public static void setRange(String key, String value, long offset) { + redisTemplate.opsForValue().set(key, value, offset); + } + + /** + * 获取字符串的长度 + */ + public static Long size(String key) { + return redisTemplate.opsForValue().size(key); + } + + /** + * 批量添加 + */ + public static void multiSet(Map maps) { + redisTemplate.opsForValue().multiSet(maps); + } + + /** + * 同时设置一个或多个 key-value 对,当且仅当所有给定 key 都不存在 + * + * @param maps + * @return 之前已经存在返回false, 不存在返回true + */ + public static boolean multiSetIfAbsent(Map maps) { + return redisTemplate.opsForValue().multiSetIfAbsent(maps); + } + + /** + * 增加(自增长), 负数则为自减 + * + * @param key + * @param value + * @return key对应value值 + */ + public static Long incrBy(String key, long increment) { + return redisTemplate.opsForValue().increment(key, increment); + } + + /** + * @param key + * @param value + * @return + */ + public static Double incrByFloat(String key, double increment) { + return redisTemplate.opsForValue().increment(key, increment); + } + + /** + * 追加到末尾 + */ + public static Integer append(String key, String value) { + return redisTemplate.opsForValue().append(key, value); + } + + /** -------------------hash相关操作------------------------- */ + + /** + * 获取存储在哈希表中指定字段的值 + */ + public static Object hGet(String key, String field) { + return redisTemplate.opsForHash().get(key, field); + } + + /** + * 获取所有给定字段的值 + */ + public static Map hGetAll(String key) { + return redisTemplate.opsForHash().entries(key); + } + + /** + * 获取所有给定字段的值 + */ + public static List hMultiGet(String key, Collection fields) { + return redisTemplate.opsForHash().multiGet(key, fields); + } + + public static void hPut(String key, String hashKey, String value) { + redisTemplate.opsForHash().put(key, hashKey, value); + } + + public static void hPutAll(String key, Map maps) { + redisTemplate.opsForHash().putAll(key, maps); + } + + /** + * 仅当hashKey不存在时才设置 + */ + public static Boolean hPutIfAbsent(String key, String hashKey, String value) { + return redisTemplate.opsForHash().putIfAbsent(key, hashKey, value); + } + + /** + * 删除一个或多个哈希表字段 + */ + public static Long hDelete(String key, Object... fields) { + return redisTemplate.opsForHash().delete(key, fields); + } + + /** + * 查看哈希表 key 中,指定的字段是否存在 + */ + public static boolean hExists(String key, String field) { + return redisTemplate.opsForHash().hasKey(key, field); + } + + /** + * 为哈希表 key 中的指定字段的整数值加上增量 increment + */ + public static Long hIncrBy(String key, Object field, long increment) { + return redisTemplate.opsForHash().increment(key, field, increment); + } + + /** + * 为哈希表 key 中的指定字段的整数值加上增量 increment + */ + public static Double hIncrByFloat(String key, Object field, double delta) { + return redisTemplate.opsForHash().increment(key, field, delta); + } + + /** + * 获取所有哈希表中的字段 + */ + public static Set hKeys(String key) { + return redisTemplate.opsForHash().keys(key); + } + + /** + * 获取哈希表中字段的数量 + */ + public static Long hSize(String key) { + return redisTemplate.opsForHash().size(key); + } + + /** + * 获取哈希表中所有值 + */ + public static List hValues(String key) { + return redisTemplate.opsForHash().values(key); + } + + /** + * 迭代哈希表中的键值对 + */ + public static Cursor> hScan(String key, ScanOptions options) { + return redisTemplate.opsForHash().scan(key, options); + } + + /** ------------------------list相关操作---------------------------- */ + + /** + * 通过索引获取列表中的元素 + */ + public static String lIndex(String key, long index) { + return redisTemplate.opsForList().index(key, index); + } + + /** + * 获取列表指定范围内的元素 + * + * @param key + * @param start 开始位置, 0是开始位置 + * @param end 结束位置, -1返回所有 + * @return + */ + public static List lRange(String key, long start, long end) { + return redisTemplate.opsForList().range(key, start, end); + } + + /** + * 存储在list头部 + */ + public static Long lLeftPush(String key, String value) { + return redisTemplate.opsForList().leftPush(key, value); + } + + public static Long lLeftPushAll(String key, String... value) { + return redisTemplate.opsForList().leftPushAll(key, value); + } + + public static Long lLeftPushAll(String key, Collection value) { + return redisTemplate.opsForList().leftPushAll(key, value); + } + + /** + * 当list存在的时候才加入 + */ + public static Long lLeftPushIfPresent(String key, String value) { + return redisTemplate.opsForList().leftPushIfPresent(key, value); + } + + /** + * 如果pivot存在,再pivot前面添加 + */ + public static Long lLeftPush(String key, String pivot, String value) { + return redisTemplate.opsForList().leftPush(key, pivot, value); + } + + public static Long lRightPush(String key, String value) { + return redisTemplate.opsForList().rightPush(key, value); + } + + public static Long lRightPushAll(String key, String... value) { + return redisTemplate.opsForList().rightPushAll(key, value); + } + + public static Long lRightPushAll(String key, Collection value) { + return redisTemplate.opsForList().rightPushAll(key, value); + } + + /** + * 为已存在的列表添加值 + */ + public static Long lRightPushIfPresent(String key, String value) { + return redisTemplate.opsForList().rightPushIfPresent(key, value); + } + + /** + * 在pivot元素的右边添加值 + */ + public static Long lRightPush(String key, String pivot, String value) { + return redisTemplate.opsForList().rightPush(key, pivot, value); + } + + /** + * 通过索引设置列表元素的值 + * + * @param key + * @param index 位置 + * @param value + */ + public static void lSet(String key, long index, String value) { + redisTemplate.opsForList().set(key, index, value); + } + + /** + * 移出并获取列表的第一个元素 + * + * @param key + * @return 删除的元素 + */ + public static String lLeftPop(String key) { + return redisTemplate.opsForList().leftPop(key); + } + + /** + * 移出并获取列表的第一个元素, 如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止 + * + * @param key + * @param timeout 等待时间 + * @param unit 时间单位 + * @return + */ + public static String lBLeftPop(String key, long timeout, TimeUnit unit) { + return redisTemplate.opsForList().leftPop(key, timeout, unit); + } + + /** + * 移除并获取列表最后一个元素 + * + * @param key + * @return 删除的元素 + */ + public static String lRightPop(String key) { + return redisTemplate.opsForList().rightPop(key); + } + + /** + * 移出并获取列表的最后一个元素, 如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止 + * + * @param key + * @param timeout 等待时间 + * @param unit 时间单位 + * @return + */ + public static String lBRightPop(String key, long timeout, TimeUnit unit) { + return redisTemplate.opsForList().rightPop(key, timeout, unit); + } + + /** + * 移除列表的最后一个元素,并将该元素添加到另一个列表并返回 + */ + public static String lRightPopAndLeftPush(String sourceKey, String destinationKey) { + return redisTemplate.opsForList().rightPopAndLeftPush(sourceKey, destinationKey); + } + + /** + * 从列表中弹出一个值,将弹出的元素插入到另外一个列表中并返回它; 如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止 + */ + public static String lBRightPopAndLeftPush(String sourceKey, String destinationKey, long timeout, TimeUnit unit) { + return redisTemplate.opsForList().rightPopAndLeftPush(sourceKey, destinationKey, timeout, unit); + } + + /** + * 删除集合中值等于value得元素 + * + * @param key + * @param index index=0, 删除所有值等于value的元素; index>0, 从头部开始删除第一个值等于value的元素; index<0, 从尾部开始删除第一个值等于value的元素; + * @param value + * @return + */ + public static Long lRemove(String key, long index, String value) { + return redisTemplate.opsForList().remove(key, index, value); + } + + /** + * 裁剪list + */ + public static void lTrim(String key, long start, long end) { + redisTemplate.opsForList().trim(key, start, end); + } + + /** + * 获取列表长度 + */ + public static Long lLen(String key) { + return redisTemplate.opsForList().size(key); + } + + /** --------------------set相关操作-------------------------- */ + + /** + * set添加元素 + */ + public static Long sAdd(String key, String... values) { + return redisTemplate.opsForSet().add(key, values); + } + + /** + * set移除元素 + */ + public static Long sRemove(String key, Object... values) { + return redisTemplate.opsForSet().remove(key, values); + } + + /** + * 移除并返回集合的一个随机元素 + */ + public static String sPop(String key) { + return redisTemplate.opsForSet().pop(key); + } + + /** + * 将元素value从一个集合移到另一个集合 + */ + public static Boolean sMove(String key, String value, String destKey) { + return redisTemplate.opsForSet().move(key, value, destKey); + } + + /** + * 获取集合的大小 + */ + public static Long sSize(String key) { + return redisTemplate.opsForSet().size(key); + } + + /** + * 判断集合是否包含value + */ + public static Boolean sIsMember(String key, Object value) { + return redisTemplate.opsForSet().isMember(key, value); + } + + /** + * 获取两个集合的交集 + */ + public static Set sIntersect(String key, String otherKey) { + return redisTemplate.opsForSet().intersect(key, otherKey); + } + + /** + * 获取key集合与多个集合的交集 + */ + public static Set sIntersect(String key, Collection otherKeys) { + return redisTemplate.opsForSet().intersect(key, otherKeys); + } + + /** + * key集合与otherKey集合的交集存储到destKey集合中 + */ + public static Long sIntersectAndStore(String key, String otherKey, String destKey) { + return redisTemplate.opsForSet().intersectAndStore(key, otherKey, destKey); + } + + /** + * key集合与多个集合的交集存储到destKey集合中 + */ + public static Long sIntersectAndStore(String key, Collection otherKeys, String destKey) { + return redisTemplate.opsForSet().intersectAndStore(key, otherKeys, destKey); + } + + /** + * 获取两个集合的并集 + */ + public static Set sUnion(String key, String otherKeys) { + return redisTemplate.opsForSet().union(key, otherKeys); + } + + /** + * 获取key集合与多个集合的并集 + */ + public static Set sUnion(String key, Collection otherKeys) { + return redisTemplate.opsForSet().union(key, otherKeys); + } + + /** + * key集合与otherKey集合的并集存储到destKey中 + */ + public static Long sUnionAndStore(String key, String otherKey, String destKey) { + return redisTemplate.opsForSet().unionAndStore(key, otherKey, destKey); + } + + /** + * key集合与多个集合的并集存储到destKey中 + */ + public static Long sUnionAndStore(String key, Collection otherKeys, String destKey) { + return redisTemplate.opsForSet().unionAndStore(key, otherKeys, destKey); + } + + /** + * 获取两个集合的差集 + */ + public static Set sDifference(String key, String otherKey) { + return redisTemplate.opsForSet().difference(key, otherKey); + } + + /** + * 获取key集合与多个集合的差集 + */ + public static Set sDifference(String key, Collection otherKeys) { + return redisTemplate.opsForSet().difference(key, otherKeys); + } + + /** + * key集合与otherKey集合的差集存储到destKey中 + */ + public static Long sDifference(String key, String otherKey, String destKey) { + return redisTemplate.opsForSet().differenceAndStore(key, otherKey, destKey); + } + + /** + * key集合与多个集合的差集存储到destKey中 + */ + public static Long sDifference(String key, Collection otherKeys, String destKey) { + return redisTemplate.opsForSet().differenceAndStore(key, otherKeys, destKey); + } + + /** + * 获取集合所有元素 + */ + public static Set setMembers(String key) { + return redisTemplate.opsForSet().members(key); + } + + /** + * 随机获取集合中的一个元素 + */ + public static String sRandomMember(String key) { + return redisTemplate.opsForSet().randomMember(key); + } + + /** + * 随机获取集合中count个元素 + */ + public static List sRandomMembers(String key, long count) { + return redisTemplate.opsForSet().randomMembers(key, count); + } + + /** + * 随机获取集合中count个元素并且去除重复的 + */ + public static Set sDistinctRandomMembers(String key, long count) { + return redisTemplate.opsForSet().distinctRandomMembers(key, count); + } + + public static Cursor sScan(String key, ScanOptions options) { + return redisTemplate.opsForSet().scan(key, options); + } + + /** ------------------zSet相关操作-------------------------------- */ + + /** + * 添加元素,有序集合是按照元素的score值由小到大排列 + */ + public static Boolean zAdd(String key, String value, double score) { + return redisTemplate.opsForZSet().add(key, value, score); + } + + public static Long zAdd(String key, Set> values) { + return redisTemplate.opsForZSet().add(key, values); + } + + public static Long zRemove(String key, Object... values) { + return redisTemplate.opsForZSet().remove(key, values); + } + + /** + * 增加元素的score值,并返回增加后的值 + */ + public static Double zIncrementScore(String key, String value, double delta) { + return redisTemplate.opsForZSet().incrementScore(key, value, delta); + } + + /** + * 返回元素在集合的排名,有序集合是按照元素的score值由小到大排列 + * + * @param key + * @param value + * @return 0表示第一位 + */ + public static Long zRank(String key, Object value) { + return redisTemplate.opsForZSet().rank(key, value); + } + + /** + * 返回元素在集合的排名,按元素的score值由大到小排列 + */ + public static Long zReverseRank(String key, Object value) { + return redisTemplate.opsForZSet().reverseRank(key, value); + } + + /** + * 获取集合的元素, 从小到大排序 + * + * @param key + * @param start 开始位置 + * @param end 结束位置, -1查询所有 + * @return + */ + public static Set zRange(String key, long start, long end) { + return redisTemplate.opsForZSet().range(key, start, end); + } + + /** + * 获取集合元素, 并且把score值也获取 + */ + public static Set> zRangeWithScores(String key, long start, long end) { + return redisTemplate.opsForZSet().rangeWithScores(key, start, end); + } + + /** + * 根据Score值查询集合元素 + * + * @param key + * @param min 最小值 + * @param max 最大值 + * @return + */ + public static Set zRangeByScore(String key, double min, double max) { + return redisTemplate.opsForZSet().rangeByScore(key, min, max); + } + + /** + * 根据Score值查询集合元素, 从小到大排序 + * + * @param key + * @param min 最小值 + * @param max 最大值 + * @return + */ + public static Set> zRangeByScoreWithScores(String key, double min, double max) { + return redisTemplate.opsForZSet().rangeByScoreWithScores(key, min, max); + } + + public static Set> zRangeByScoreWithScores(String key, double min, double max, long start, + long end) { + return redisTemplate.opsForZSet().rangeByScoreWithScores(key, min, max, start, end); + } + + /** + * 获取集合的元素, 从大到小排序 + */ + public static Set zReverseRange(String key, long start, long end) { + return redisTemplate.opsForZSet().reverseRange(key, start, end); + } + + /** + * 获取集合的元素, 从大到小排序, 并返回score值 + */ + public static Set> zReverseRangeWithScores(String key, long start, long end) { + return redisTemplate.opsForZSet().reverseRangeWithScores(key, start, end); + } + + /** + * 根据Score值查询集合元素, 从大到小排序 + */ + public static Set zReverseRangeByScore(String key, double min, double max) { + return redisTemplate.opsForZSet().reverseRangeByScore(key, min, max); + } + + /** + * 根据Score值查询集合元素, 从大到小排序 + */ + public static Set> zReverseRangeByScoreWithScores(String key, double min, double max) { + return redisTemplate.opsForZSet().reverseRangeByScoreWithScores(key, min, max); + } + + public static Set zReverseRangeByScore(String key, double min, double max, long start, long end) { + return redisTemplate.opsForZSet().reverseRangeByScore(key, min, max, start, end); + } + + /** + * 根据score值获取集合元素数量 + */ + public static Long zCount(String key, double min, double max) { + return redisTemplate.opsForZSet().count(key, min, max); + } + + /** + * 获取集合大小 + */ + public static Long zSize(String key) { + return redisTemplate.opsForZSet().size(key); + } + + /** + * 获取集合大小 + */ + public static Long zZCard(String key) { + return redisTemplate.opsForZSet().zCard(key); + } + + /** + * 获取集合中value元素的score值 + */ + public static Double zScore(String key, Object value) { + return redisTemplate.opsForZSet().score(key, value); + } + + /** + * 移除指定索引位置的成员 + */ + public static Long zRemoveRange(String key, long start, long end) { + return redisTemplate.opsForZSet().removeRange(key, start, end); + } + + /** + * 根据指定的score值的范围来移除成员 + */ + public static Long zRemoveRangeByScore(String key, double min, double max) { + return redisTemplate.opsForZSet().removeRangeByScore(key, min, max); + } + + /** + * 获取key和otherKey的并集并存储在destKey中 + */ + public static Long zUnionAndStore(String key, String otherKey, String destKey) { + return redisTemplate.opsForZSet().unionAndStore(key, otherKey, destKey); + } + + public static Long zUnionAndStore(String key, Collection otherKeys, String destKey) { + return redisTemplate.opsForZSet().unionAndStore(key, otherKeys, destKey); + } + + /** + * 交集 + */ + public static Long zIntersectAndStore(String key, String otherKey, String destKey) { + return redisTemplate.opsForZSet().intersectAndStore(key, otherKey, destKey); + } + + /** + * 交集 + */ + public static Long zIntersectAndStore(String key, Collection otherKeys, String destKey) { + return redisTemplate.opsForZSet().intersectAndStore(key, otherKeys, destKey); + } + + public static Cursor> zScan(String key, ScanOptions options) { + return redisTemplate.opsForZSet().scan(key, options); + } + + // ============================ ↓↓↓↓↓↓ 发布订阅 ↓↓↓↓↓↓ ============================ + + + /** + * 发布消息 + * + * @param channel 通道 + * @param msg 消息内容 + * @return void + * @author zhengqingya + * @date 2022/6/17 17:29 + */ + public static void publish(String channel, String msg) { + redisTemplate.convertAndSend(channel, msg); + } + +} diff --git a/smallboot-api/common/redis/src/main/java/com/zhengqing/common/redis/util/RedissonUtil.java b/smallboot-api/common/redis/src/main/java/com/zhengqing/common/redis/util/RedissonUtil.java new file mode 100644 index 0000000..dc9f17e --- /dev/null +++ b/smallboot-api/common/redis/src/main/java/com/zhengqing/common/redis/util/RedissonUtil.java @@ -0,0 +1,62 @@ +package com.zhengqing.common.redis.util; + +import org.redisson.api.RLock; +import org.redisson.api.RedissonClient; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import java.util.concurrent.TimeUnit; + +/** + *

+ * Redisson工具类 + *

+ * + * @author zhengqingya + * @description 文档见: + * - https://github.com/redisson/redisson/wiki/%E7%9B%AE%E5%BD%95 + * - https://www.bookstack.cn/read/redisson-wiki-zh/spilt.1.8.-%E5%88%86%E5%B8%83%E5%BC%8F%E9%94%81%E5%92%8C%E5%90%8C%E6%AD%A5%E5%99%A8.md + * @date 2019/11/27 14:38 + */ +@Component +public class RedissonUtil { + + private static RedissonClient redissonClient; + + @Autowired + public RedissonUtil(RedissonClient redissonClient) { + RedissonUtil.redissonClient = redissonClient; + } + + + // ============================ ↓↓↓↓↓↓ 操作 锁 ↓↓↓↓↓↓ ============================ + + /** + * 可重入锁 + * + * @param key key + * @return 锁 + */ + public static RLock getLock(String key) { + return redissonClient.getLock(key); + } + + /** + * 可重入锁 + * + * @param key key + * @param leaseTime 时间 + * @param unit 时间单位 + * @return 锁 + * @author zhengqingya + * @date 2022/1/14 9:25 下午 + */ + public static RLock lock(String key, long leaseTime, TimeUnit unit) { + RLock lock = redissonClient.getLock(key); + // 加锁leaseTime以后自动解锁 + // 无需调用unlock方法手动解锁 + lock.lock(leaseTime, unit); + return lock; + } + +} diff --git a/smallboot-api/common/redis/src/main/resources/application-redis.yml b/smallboot-api/common/redis/src/main/resources/application-redis.yml new file mode 100644 index 0000000..59a5737 --- /dev/null +++ b/smallboot-api/common/redis/src/main/resources/application-redis.yml @@ -0,0 +1,18 @@ +# Redis数据源配置 +spring: + redis: + # Redis数据库索引(默认为0) + database: ${smallboot.redis.database} + # Redis服务器地址 + host: ${smallboot.redis.host} + # Redis服务器连接端口 + port: ${smallboot.redis.port} + timeout: 6000 + # Redis服务器连接密码(默认为空) + password: ${smallboot.redis.password} + jedis: + pool: + max-active: 1000 # 连接池最大连接数(使用负值表示没有限制) + max-wait: -1 # 连接池最大阻塞等待时间(使用负值表示没有限制) + max-idle: 10 # 连接池中的最大空闲连接 + min-idle: 5 # 连接池中的最小空闲连接 diff --git a/smallboot-api/common/swagger/pom.xml b/smallboot-api/common/swagger/pom.xml new file mode 100644 index 0000000..1756d61 --- /dev/null +++ b/smallboot-api/common/swagger/pom.xml @@ -0,0 +1,50 @@ + + + 4.0.0 + + + common + com.zhengqing + ${revision} + + + swagger + + ${project.artifactId} + ${revision} + jar + + + + + + + org.springframework.boot + spring-boot-starter-web + provided + + + + + + com.github.xiaoymin + knife4j-spring-boot-starter + 3.0.3 + + + + org.springframework.boot + spring-boot-actuator + + + + org.springframework.boot + spring-boot-actuator-autoconfigure + + + + + + diff --git a/smallboot-api/common/swagger/src/main/java/com/zhengqing/common/swagger/config/Knife4jConfig.java b/smallboot-api/common/swagger/src/main/java/com/zhengqing/common/swagger/config/Knife4jConfig.java new file mode 100644 index 0000000..2a87fd8 --- /dev/null +++ b/smallboot-api/common/swagger/src/main/java/com/zhengqing/common/swagger/config/Knife4jConfig.java @@ -0,0 +1,191 @@ +package com.zhengqing.common.swagger.config; + +import com.github.xiaoymin.knife4j.spring.extension.OpenApiExtensionResolver; +import io.swagger.annotations.Api; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.actuate.autoconfigure.endpoint.web.CorsEndpointProperties; +import org.springframework.boot.actuate.autoconfigure.endpoint.web.WebEndpointProperties; +import org.springframework.boot.actuate.autoconfigure.web.server.ManagementPortType; +import org.springframework.boot.actuate.endpoint.ExposableEndpoint; +import org.springframework.boot.actuate.endpoint.web.*; +import org.springframework.boot.actuate.endpoint.web.annotation.ControllerEndpointsSupplier; +import org.springframework.boot.actuate.endpoint.web.annotation.ServletEndpointsSupplier; +import org.springframework.boot.actuate.endpoint.web.servlet.WebMvcEndpointHandlerMapping; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.core.env.Environment; +import org.springframework.util.StringUtils; +import springfox.bean.validators.configuration.BeanValidatorPluginsConfiguration; +import springfox.documentation.builders.ApiInfoBuilder; +import springfox.documentation.builders.PathSelectors; +import springfox.documentation.builders.RequestHandlerSelectors; +import springfox.documentation.service.ApiInfo; +import springfox.documentation.service.Contact; +import springfox.documentation.spi.DocumentationType; +import springfox.documentation.spring.web.plugins.Docket; +import springfox.documentation.swagger2.annotations.EnableSwagger2WebMvc; + +import javax.annotation.Resource; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +/** + *

Knife4j配置类

+ * + * @author zhengqingya + * @description + * @date 2022/6/21 14:01 + */ +@Slf4j +@Configuration +//@EnableKnife4j +@EnableSwagger2WebMvc +// 对JSR303提供支持 +@Import(BeanValidatorPluginsConfiguration.class) +public class Knife4jConfig { + + @Value("${spring.application.name}") + private String applicationName; + + @Value("${server.port}") + private String port; + + @Value("${knife4j.passwordTokenUrl}") + private String passwordTokenUrl; + + @Resource + private OpenApiExtensionResolver openApiExtensionResolver; + + @Bean + public Docket defaultApi() { + Docket docket = new Docket(DocumentationType.SWAGGER_2) + .apiInfo(this.apiInfo()) + .groupName(this.applicationName) + .select() + // 添加@Api注解才显示 + .apis(RequestHandlerSelectors.withClassAnnotation(Api.class)) +// .apis(RequestHandlerSelectors.basePackage("com.zhengqing")) + .paths(PathSelectors.any()) + .build() + // 插件扩展 -- ex:自定义md文档 + .extensions(this.openApiExtensionResolver.buildExtensions(this.applicationName)) +// // 默认全局参数 +// .globalRequestParameters( +// Lists.newArrayList( +// new RequestParameterBuilder() +// .name(SwaggerConstant.TENANT_ID) +// .description("租户ID") +// .in(ParameterType.HEADER) +// .required(true) +// .build() +// ) +// ) + ; + + // 网关|授权服务开启授权认证请求头 +// if (SwaggerConstant.GATEWAY_PORT.equals(this.port) || SwaggerConstant.AUTH_PORT.equals(this.port)) { + // 2.0.9版本 + // context +// List securityContexts = Lists.newArrayList( +// SecurityContext.builder() +// .securityReferences( +// CollectionUtil.newArrayList( +// new SecurityReference("oauth2", +// Lists.newArrayList( +// new AuthorizationScope("read", "read resources"), +// new AuthorizationScope("write", "write resources"), +// new AuthorizationScope("reads", "read all resources"), +// new AuthorizationScope("writes", "write all resources") +// ).toArray(new AuthorizationScope[]{}) +// ) +// ) +// ) +// .forPaths(PathSelectors.ant("/**")) +// .build() +// ); +// // 密码模式 +// List securitySchemes = Lists.newArrayList( +// new OAuthBuilder() +// .name("oauth2") +// .grantTypes(Lists.newArrayList(new ResourceOwnerPasswordCredentialsGrant(this.passwordTokenUrl))) +// .build() +// ); + + + // 3.0.3版本 参考 https://gitee.com/xiaoym/swagger-bootstrap-ui-demo/blob/master/knife4j-springfox-boot-v3-demo/src/main/java/com/xiaominfo/knife4j/config/Knife4jConfig.java +// AuthorizationScope authorizationScope = new AuthorizationScope("global", "accessEverything"); +// AuthorizationScope[] authorizationScopes = new AuthorizationScope[1]; +// authorizationScopes[0] = authorizationScope; +// +// List securityContexts = Lists.newArrayList( +// SecurityContext.builder() +// .securityReferences( +// CollectionUtil.newArrayList( +// new SecurityReference(HttpHeaders.AUTHORIZATION, authorizationScopes) +// ) +// ) +// .forPaths(PathSelectors.regex("/.*")) +// .build()); +// +// List securitySchemes = Lists.newArrayList( +// HttpAuthenticationScheme.JWT_BEARER_BUILDER +// .name(SwaggerConstant.REQUEST_HEADER_AUTHORIZATION) +// .description("Bearer Token") +// .build() +// ); + +// docket.securityContexts(securityContexts).securitySchemes(securitySchemes); +// } + return docket; + } + + /** + * swagger-api接口描述信息 + */ + private ApiInfo apiInfo() { + return new ApiInfoBuilder() + .title("API文档") + .description("API文档") + .termsOfServiceUrl(String.format("127.0.0.1:%s/", this.port)) + .contact( + new Contact( + "zhengqingya", + "https://gitee.com/zhengqingya", + "zhengqingya@it.com" + ) + ) + .version("1.0.0") + .build(); + } + + /** + * 解决Spring Boot 2.6.x以上 与 Swagger 3.0.0 不兼容问题 + * 参考 https://github.com/springfox/springfox/issues/3462 + */ + @Bean + public WebMvcEndpointHandlerMapping webEndpointServletHandlerMapping(WebEndpointsSupplier webEndpointsSupplier, + ServletEndpointsSupplier servletEndpointsSupplier, + ControllerEndpointsSupplier controllerEndpointsSupplier, + EndpointMediaTypes endpointMediaTypes, + CorsEndpointProperties corsProperties, + WebEndpointProperties webEndpointProperties, + Environment environment) { + List> allEndpoints = new ArrayList(); + Collection webEndpoints = webEndpointsSupplier.getEndpoints(); + allEndpoints.addAll(webEndpoints); + allEndpoints.addAll(servletEndpointsSupplier.getEndpoints()); + allEndpoints.addAll(controllerEndpointsSupplier.getEndpoints()); + String basePath = webEndpointProperties.getBasePath(); + EndpointMapping endpointMapping = new EndpointMapping(basePath); + boolean shouldRegisterLinksMapping = this.shouldRegisterLinksMapping(webEndpointProperties, environment, basePath); + return new WebMvcEndpointHandlerMapping(endpointMapping, webEndpoints, endpointMediaTypes, corsProperties.toCorsConfiguration(), new EndpointLinksResolver(allEndpoints, basePath), shouldRegisterLinksMapping, null); + } + + private boolean shouldRegisterLinksMapping(WebEndpointProperties webEndpointProperties, Environment environment, String basePath) { + return webEndpointProperties.getDiscovery().isEnabled() && (StringUtils.hasText(basePath) || ManagementPortType.get(environment).equals(ManagementPortType.DIFFERENT)); + } + +} diff --git a/smallboot-api/common/swagger/src/main/java/com/zhengqing/common/swagger/config/SwaggerOperationParameterRequestConditionReader.java b/smallboot-api/common/swagger/src/main/java/com/zhengqing/common/swagger/config/SwaggerOperationParameterRequestConditionReader.java new file mode 100644 index 0000000..d50c489 --- /dev/null +++ b/smallboot-api/common/swagger/src/main/java/com/zhengqing/common/swagger/config/SwaggerOperationParameterRequestConditionReader.java @@ -0,0 +1,71 @@ +package com.zhengqing.common.swagger.config; + +import com.fasterxml.classmate.TypeResolver; +import com.zhengqing.common.swagger.constant.SwaggerConstant; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Primary; +import org.springframework.core.annotation.Order; +import org.springframework.stereotype.Component; +import springfox.documentation.builders.RequestParameterBuilder; +import springfox.documentation.service.ParameterType; +import springfox.documentation.service.RequestParameter; +import springfox.documentation.spi.service.contexts.OperationContext; +import springfox.documentation.spring.web.readers.operation.AbstractOperationParameterRequestConditionReader; +import springfox.documentation.spring.wrapper.NameValueExpression; +import springfox.documentation.swagger.common.SwaggerPluginSupport; + +import java.util.Set; + +/** + *

+ * swagger 全局参数 + *

+ * + * @author zhengqingya + * @description + * @date 2020/12/28 23:10 + */ +@Primary +@Component +@Order(SwaggerPluginSupport.SWAGGER_PLUGIN_ORDER + 10000) +public class SwaggerOperationParameterRequestConditionReader extends AbstractOperationParameterRequestConditionReader { + + private final TypeResolver resolver; + + private final String IPAGE_CLASS_NAME = "com.baomidou.mybatisplus.core.metadata.IPage"; + + @Autowired + public SwaggerOperationParameterRequestConditionReader(TypeResolver resolver) { + super(resolver); + this.resolver = resolver; + } + + @Override + public void apply(OperationContext context) { + Set> headers = context.headers(); + Set requestParameters = this.getRequestParameters(headers, ParameterType.HEADER); + /** + * 分页列表api添加分页请求头参数 + * {@link com.zhengqing.common.base.model.dto.BasePageDTO} + */ + if (this.IPAGE_CLASS_NAME.equals(context.getReturnType().getErasedType().getName())) { + requestParameters.add( + new RequestParameterBuilder() + .name(SwaggerConstant.PAGE_NUM) + .description("当前页") + .in(ParameterType.HEADER) + .required(true) + .build() + ); + requestParameters.add( + new RequestParameterBuilder() + .name(SwaggerConstant.PAGE_SIZE) + .description("每页显示数量") + .in(ParameterType.HEADER) + .required(true) + .build()); + context.operationBuilder().requestParameters(requestParameters); + } + } + +} diff --git a/smallboot-api/common/swagger/src/main/java/com/zhengqing/common/swagger/constant/SwaggerConstant.java b/smallboot-api/common/swagger/src/main/java/com/zhengqing/common/swagger/constant/SwaggerConstant.java new file mode 100644 index 0000000..4afc779 --- /dev/null +++ b/smallboot-api/common/swagger/src/main/java/com/zhengqing/common/swagger/constant/SwaggerConstant.java @@ -0,0 +1,38 @@ +package com.zhengqing.common.swagger.constant; + +/** + *

全局常用变量 - swagger

+ * + * @author zhengqingya + * @description + * @date 2021/6/1 17:08 + */ +public interface SwaggerConstant { + + /** + * 当前页、每页显示数量 + */ + String PAGE_NUM = "pageNum"; + String PAGE_SIZE = "pageSize"; + + /** + * 认证请求头 + */ + String REQUEST_HEADER_AUTHORIZATION = "Authorization-smallboot"; + + /** + * 租户ID + */ + String TENANT_ID = "TENANT_ID"; + + /** + * 网关服务端口 + */ + String GATEWAY_PORT = "1218"; + + /** + * 授权服务端口 + */ + String AUTH_PORT = "1219"; + +} diff --git a/smallboot-api/common/swagger/src/main/resources/application-swagger.yml b/smallboot-api/common/swagger/src/main/resources/application-swagger.yml new file mode 100644 index 0000000..e55fa42 --- /dev/null +++ b/smallboot-api/common/swagger/src/main/resources/application-swagger.yml @@ -0,0 +1,27 @@ +spring: + mvc: + pathmatch: + matching-strategy: ANT_PATH_MATCHER # 解决springboot高版本Knife4j报错问题 + +# https://doc.xiaominfo.com/knife4j +knife4j: + # 开启增强配置 + enable: true + # 是否开启生产环境屏蔽 true:关闭swagger,false:开启swagger + production: false + # 自定义文档 + documents: + - group: demo + name: 测试自定义标题分组 + # 某一个文件夹下所有的.md文件 + locations: classpath:markdown/* + # 开启Swagger的Basic认证功能,默认是false + basic: + # 是否开启认证 + enable: false + # Basic认证用户名 + username: admin + # Basic认证密码 + password: 123456 + passwordTokenUrl: http://127.0.0.1:1218/auth/oauth/token # 网关服务api +# passwordTokenUrl: http://127.0.0.1:1219/oauth/token # 授权服务api diff --git a/smallboot-api/common/swagger/src/main/resources/markdown/OTHER.md b/smallboot-api/common/swagger/src/main/resources/markdown/OTHER.md new file mode 100644 index 0000000..3b42709 --- /dev/null +++ b/smallboot-api/common/swagger/src/main/resources/markdown/OTHER.md @@ -0,0 +1,19 @@ +# 项目 + +### 后端 + +```shell +java -jar app.jar +``` + +### 前端 + +```shell +cnpm install + +cnpm run dev +``` + +### knife4j + +https://doc.xiaominfo.com/knife4j diff --git a/smallboot-api/common/web/pom.xml b/smallboot-api/common/web/pom.xml new file mode 100644 index 0000000..d00d9ad --- /dev/null +++ b/smallboot-api/common/web/pom.xml @@ -0,0 +1,91 @@ + + + 4.0.0 + + + common + com.zhengqing + ${revision} + + + web + + ${project.artifactId} + ${revision} + jar + + + + + + + com.zhengqing + base + provided + + + + + org.springframework.boot + spring-boot-starter-web + + + + + + + + + + + + + + + + + org.hibernate.validator + hibernate-validator + 6.2.3.Final + + + + + + org.springframework.retry + spring-retry + + + + + + org.freemarker + freemarker + + + + + + org.yaml + snakeyaml + 1.30 + + + + + org.apache.httpcomponents + httpcore + 4.4.10 + + + org.apache.httpcomponents + httpmime + 4.5.6 + + + + + + diff --git a/smallboot-api/common/web/src/main/java/com/zhengqing/common/web/config/CorsConfig.java b/smallboot-api/common/web/src/main/java/com/zhengqing/common/web/config/CorsConfig.java new file mode 100644 index 0000000..a17de54 --- /dev/null +++ b/smallboot-api/common/web/src/main/java/com/zhengqing/common/web/config/CorsConfig.java @@ -0,0 +1,42 @@ +package com.zhengqing.common.web.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.cors.CorsConfiguration; +import org.springframework.web.cors.reactive.CorsWebFilter; +import org.springframework.web.cors.reactive.UrlBasedCorsConfigurationSource; + +/** + *

+ * 全局配置解决跨域 + *

+ * + * @author zhengqingya + * @description + * @date 2019/8/22 9:09 + */ +@Configuration +public class CorsConfig { + + @Bean + public CorsWebFilter corsWebFilter() { + UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); + CorsConfiguration corsConfig = new CorsConfiguration(); + // 设置你要允许的网站域名,如果全允许则设为 * +// corsConfig.addAllowedOrigin("*"); +// corsConfig.addAllowedOrigin("http://www.zhengqingya.com"); + // 当allowCredentials为true时,allowedOrigins不能包含特殊值"*",因为它不能在"Access-Control-Allow-Origin"响应头中设置。 + // 要允许凭证指向一组起源,可以显式地列出它们,考虑使用“allowedOriginPatterns”代替。 + corsConfig.addAllowedOriginPattern("*"); + // 如果要限制 HEADER 或 METHOD 请自行更改 + corsConfig.addAllowedHeader("*"); + // 设置允许的方法 + corsConfig.addAllowedMethod("*"); + // 是否允许证书 + corsConfig.setAllowCredentials(true); + // 设置允许跨域请求的路由 + source.registerCorsConfiguration("/**", corsConfig); + return new CorsWebFilter(source); + } + +} diff --git a/smallboot-api/common/web/src/main/java/com/zhengqing/common/web/config/FastjsonConfig.java b/smallboot-api/common/web/src/main/java/com/zhengqing/common/web/config/FastjsonConfig.java new file mode 100644 index 0000000..4a623ba --- /dev/null +++ b/smallboot-api/common/web/src/main/java/com/zhengqing/common/web/config/FastjsonConfig.java @@ -0,0 +1,69 @@ +//package com.zhengqing.common.web.config; +// +//import com.alibaba.fastjson.serializer.SerializerFeature; +//import com.alibaba.fastjson.support.config.FastJsonConfig; +//import com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter; +//import com.google.common.collect.Lists; +//import lombok.extern.slf4j.Slf4j; +//import org.springframework.boot.autoconfigure.http.HttpMessageConverters; +//import org.springframework.context.annotation.Bean; +//import org.springframework.context.annotation.Configuration; +//import org.springframework.http.MediaType; +// +///** +// *

+// * 配置fastjson规则 +// *

+// * +// * @author zhengqingya +// * @description Date类型数据转化有两种方式: +// * 第一种就是在配置类中直接定义日期格式(全局统一格式时推荐使用) +// * fastJsonConfig.setDateFormat("yyyy-MM-dd HH:mm:ss"); +// * 第二种在字段上进行注解(灵活性大,可根据情况不同字段设置不同的时间格式) +// * -- @DateTimeFormat( pattern = "yyyy-MM-dd HH:mm:ss" ) +// * -- @JSONField(format="yyyy-MM-dd HH:mm:ss") +// * -- private Date time; +// *

+// * 注:如果存在时差13小时问题,需配置数据库连接参数`&serverTimezone=Asia/Shanghai` +// * @date 2019/8/19 23:08 +// */ +//@Slf4j +//@Configuration +//public class FastjsonConfig { +// +// @Bean +// public HttpMessageConverters fastJsonConfigure() { +// // 1. 先定义一个convert 转换消息的对象 +// FastJsonHttpMessageConverter fastConverter = new FastJsonHttpMessageConverter(); +// +// // 2. 添加fastJson的配置信息,比如,是否需要格式化返回的json数据 +// FastJsonConfig fastJsonConfig = new FastJsonConfig(); +// // 空值特别处理 +// // WriteNullListAsEmpty 将Collection类型字段的字段空值输出为[] +// // WriteNullStringAsEmpty 将字符串类型字段的空值输出为空字符串 "" +// // WriteNullNumberAsZero 将数值类型字段的空值输出为0 +// // WriteNullBooleanAsFalse 将Boolean类型字段的空值输出为false +// // DisableCircularReferenceDetect 消除对同一对象循环引用的问题,默认为false(如果不配置有可能会进入死循环) +// fastJsonConfig.setSerializerFeatures( +// SerializerFeature.PrettyFormat, +// SerializerFeature.WriteNullListAsEmpty, +// SerializerFeature.DisableCircularReferenceDetect, +// SerializerFeature.WriteNullStringAsEmpty, +// SerializerFeature.WriteMapNullValue); +// // 日期格式化 +// fastJsonConfig.setDateFormat("yyyy-MM-dd HH:mm:ss"); +// +// /** +// * 配置mybatis-plus序列化枚举值为数据库存储值 -- tips:实测不配置这里也可 +// * https://baomidou.com/pages/8390a4/#fastjson +// */ +//// fastJsonConfig.setSerializerFeatures(SerializerFeature.WriteEnumUsingToString); +// +// // 3. 在convert中添加配置信息 +// fastConverter.setFastJsonConfig(fastJsonConfig); +// // 4. 处理中文乱码问题 +// fastConverter.setSupportedMediaTypes(Lists.newArrayList(MediaType.APPLICATION_JSON_UTF8)); +// return new HttpMessageConverters(fastConverter); +// } +// +//} diff --git a/smallboot-api/common/web/src/main/java/com/zhengqing/common/web/config/ReturnValueConfig.java b/smallboot-api/common/web/src/main/java/com/zhengqing/common/web/config/ReturnValueConfig.java new file mode 100644 index 0000000..c8446b8 --- /dev/null +++ b/smallboot-api/common/web/src/main/java/com/zhengqing/common/web/config/ReturnValueConfig.java @@ -0,0 +1,43 @@ +package com.zhengqing.common.web.config; + +import com.zhengqing.common.web.handler.MyHandlerMethodReturnValueHandler; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.method.support.HandlerMethodReturnValueHandler; +import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter; +import org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor; + +import javax.annotation.Resource; +import java.util.ArrayList; +import java.util.List; + +/** + *

+ * 返回值配置 + *

+ * + * @author zhengqingya + * @description 用定制的Handler替换默认Handler + * @date 2020/8/1 18:42 + */ +@Configuration +public class ReturnValueConfig implements InitializingBean { + + @Resource + RequestMappingHandlerAdapter requestMappingHandlerAdapter; + + @Override + public void afterPropertiesSet() throws Exception { + List unmodifiableList = this.requestMappingHandlerAdapter.getReturnValueHandlers(); + List list = new ArrayList<>(unmodifiableList.size()); + for (HandlerMethodReturnValueHandler returnValueHandler : unmodifiableList) { + if (returnValueHandler instanceof RequestResponseBodyMethodProcessor) { + list.add(new MyHandlerMethodReturnValueHandler(returnValueHandler)); + } else { + list.add(returnValueHandler); + } + } + this.requestMappingHandlerAdapter.setReturnValueHandlers(list); + } + +} diff --git a/smallboot-api/common/web/src/main/java/com/zhengqing/common/web/config/ServletInit.java b/smallboot-api/common/web/src/main/java/com/zhengqing/common/web/config/ServletInit.java new file mode 100644 index 0000000..1f0ebc8 --- /dev/null +++ b/smallboot-api/common/web/src/main/java/com/zhengqing/common/web/config/ServletInit.java @@ -0,0 +1,71 @@ +package com.zhengqing.common.web.config; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.boot.web.servlet.ServletContextInitializer; +import org.springframework.context.annotation.Configuration; + +import javax.servlet.ServletContext; +import javax.servlet.ServletException; + +/** + *

+ * Servlet初始化执行 + *

+ * + * @author zhengqingya + * @description + * @date 2019/8/19 23:08 + */ +@Slf4j +@Configuration +public class ServletInit implements ServletContextInitializer { + + @Override + public void onStartup(ServletContext servletContext) throws ServletException { + this.initProperties(); +// loadAllUrlMapping(servletContext); + } + + /** + * 初始化 + */ + private void initProperties() { + log.info("============= ↓↓↓↓↓↓ [初始化系统配置参数] ↓↓↓↓↓↓ ============="); + } + + /** + * 读取系统URL映射 + * + * @param servletContext + */ +// private void loadAllUrlMapping(ServletContext servletContext) { +// log.info("============= ↓↓↓↓↓↓ [读取系统URL映射] ↓↓↓↓↓↓ ============="); +// ApplicationContext webApplicationContext = WebApplicationContextUtils.getWebApplicationContext(servletContext); +// Map requestMappings = BeanFactoryUtils.beansOfTypeIncludingAncestors(webApplicationContext, HandlerMapping.class, true, false); +// for (HandlerMapping handlerMapping : requestMappings.values()) { +// if (handlerMapping instanceof RequestMappingHandlerMapping) { +// RequestMappingHandlerMapping requestMappingHandlerMapping = (RequestMappingHandlerMapping) handlerMapping; +// Map handlerMethods = requestMappingHandlerMapping.getHandlerMethods(); +// for (RequestMappingInfo rmi : handlerMethods.keySet()) { +// PatternsRequestCondition prc = rmi.getPatternsCondition(); +// HandlerMethod handlerMethod = handlerMethods.get(rmi); +// Set patterns = prc.getPatterns(); +// for (String uStr : patterns) { +// Method methodItem = handlerMethod.getMethod(); +// ApiOperation apiOperation = methodItem.getAnnotation(ApiOperation.class); +// if (apiOperation != null) { +// String apiOperationValue = apiOperation.value(); +// Api annotation = methodItem.getDeclaringClass().getAnnotation(Api.class); +// String apiTags = annotation.tags()[0]; +// log.debug("名称:[{}] URI:[{}] Controller方法:[{}]", +// apiTags + "-" + apiOperationValue, +// uStr, +// methodItem.getDeclaringClass().getSimpleName() + "." + methodItem.getName()); +// } +// } +// } +// } +// } +// } + +} diff --git a/smallboot-api/common/web/src/main/java/com/zhengqing/common/web/config/ValidatorConfig.java b/smallboot-api/common/web/src/main/java/com/zhengqing/common/web/config/ValidatorConfig.java new file mode 100644 index 0000000..39f8a19 --- /dev/null +++ b/smallboot-api/common/web/src/main/java/com/zhengqing/common/web/config/ValidatorConfig.java @@ -0,0 +1,34 @@ +package com.zhengqing.common.web.config; + +import lombok.extern.slf4j.Slf4j; +import org.hibernate.validator.HibernateValidator; +import org.springframework.context.annotation.Bean; +import org.springframework.stereotype.Component; + +import javax.validation.Validation; +import javax.validation.Validator; +import javax.validation.ValidatorFactory; + +/** + *

校验配置

+ * + * @author zhengqingya + * @description 快速失败: Spring Validation默认会校验完所有字段,然后才抛出异常。可以通过一些简单的配置,开启`Fail Fast`模式,一旦校验失败就立即返回。 + * @date 2021/12/16 9:30 + */ +@Slf4j +@Component +public class ValidatorConfig { + + @Bean + public Validator validator() { + ValidatorFactory validatorFactory = Validation.byProvider(HibernateValidator.class) + .configure() + // 快速失败模式 + .failFast(true) + .buildValidatorFactory(); + log.info("Spring Validation 快速失败模式开启 ..."); + return validatorFactory.getValidator(); + } + +} diff --git a/smallboot-api/common/web/src/main/java/com/zhengqing/common/web/config/jackson/CustomNullJsonSerializer.java b/smallboot-api/common/web/src/main/java/com/zhengqing/common/web/config/jackson/CustomNullJsonSerializer.java new file mode 100644 index 0000000..f544393 --- /dev/null +++ b/smallboot-api/common/web/src/main/java/com/zhengqing/common/web/config/jackson/CustomNullJsonSerializer.java @@ -0,0 +1,71 @@ +package com.zhengqing.common.web.config.jackson; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.JsonSerializer; +import com.fasterxml.jackson.databind.SerializerProvider; +import org.apache.commons.lang3.StringUtils; + +import java.io.IOException; + +/** + *

Jackson 自定义null值序列化处理器

+ * + * @author zhengqingya + * @description 处理不同字段类型的null值 + * @date 2022/7/26 9:53 + */ +public class CustomNullJsonSerializer { + + /** + * 处理数组集合类型的null值 + */ + public static class NullArrayJsonSerializer extends JsonSerializer { + @Override + public void serialize(Object value, JsonGenerator jsonGenerator, SerializerProvider provider) throws IOException { + jsonGenerator.writeStartArray(); + jsonGenerator.writeEndArray(); + } + } + + /** + * 处理字符串类型的null值 + */ + public static class NullStringJsonSerializer extends JsonSerializer { + @Override + public void serialize(Object o, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException { + jsonGenerator.writeString(StringUtils.EMPTY); + } + } + + /** + * 处理数值类型的null值 + */ + public static class NullNumberJsonSerializer extends JsonSerializer { + @Override + public void serialize(Object o, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException { + jsonGenerator.writeNumber(0); + } + } + + /** + * 处理boolean类型的null值 + */ + public static class NullBooleanJsonSerializer extends JsonSerializer { + @Override + public void serialize(Object o, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException { + jsonGenerator.writeBoolean(false); + } + } + + /** + * 处理实体对象类型的null值 + */ + public static class NullObjectJsonSerializer extends JsonSerializer { + @Override + public void serialize(Object value, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException { + jsonGenerator.writeStartObject(); + jsonGenerator.writeEndObject(); + } + } + +} diff --git a/smallboot-api/common/web/src/main/java/com/zhengqing/common/web/config/jackson/JacksonConfig.java b/smallboot-api/common/web/src/main/java/com/zhengqing/common/web/config/jackson/JacksonConfig.java new file mode 100644 index 0000000..23ecb02 --- /dev/null +++ b/smallboot-api/common/web/src/main/java/com/zhengqing/common/web/config/jackson/JacksonConfig.java @@ -0,0 +1,43 @@ +package com.zhengqing.common.web.config.jackson; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.module.SimpleModule; +import com.fasterxml.jackson.databind.ser.std.ToStringSerializer; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Primary; +import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder; + +/** + *

Jackson配置

+ * + * @author zhengqingya + * @description tips: 继承`WebMvcConfigurationSupport`会导致`application-web.yml`中jackson配置失效 + * @date 2022/7/26 9:53 + */ +@Configuration +public class JacksonConfig { + + @Bean + @Primary + @ConditionalOnMissingBean(ObjectMapper.class) + public ObjectMapper jacksonObjectMapper(Jackson2ObjectMapperBuilder builder) { + ObjectMapper objectMapper = builder.createXmlMapper(false).build(); + // 注册Factory + objectMapper.setSerializerFactory(objectMapper.getSerializerFactory().withSerializerModifier(new MyBeanSerializerModifier())); + // 对象null值转变为{} +// objectMapper.getSerializerProvider().setNullValueSerializer(new CustomNullJsonSerializer.NullObjectJsonSerializer()); + + // 属性命名策略 -> 驼峰式 +// objectMapper.setPropertyNamingStrategy(MyPropertyNamingStrategy.LOWER_CAMEL_CASE_HUMP); + + // Long类型转换为字符串类型(解决前端js展示精度丢失问题) + SimpleModule simpleModule = new SimpleModule(); + simpleModule.addSerializer(Long.class, ToStringSerializer.instance); + simpleModule.addSerializer(Long.TYPE, ToStringSerializer.instance); + objectMapper.registerModule(simpleModule); + return objectMapper; + } + +} diff --git a/smallboot-api/common/web/src/main/java/com/zhengqing/common/web/config/jackson/MyBeanSerializerModifier.java b/smallboot-api/common/web/src/main/java/com/zhengqing/common/web/config/jackson/MyBeanSerializerModifier.java new file mode 100644 index 0000000..a657e4a --- /dev/null +++ b/smallboot-api/common/web/src/main/java/com/zhengqing/common/web/config/jackson/MyBeanSerializerModifier.java @@ -0,0 +1,76 @@ +package com.zhengqing.common.web.config.jackson; + +import com.fasterxml.jackson.databind.BeanDescription; +import com.fasterxml.jackson.databind.SerializationConfig; +import com.fasterxml.jackson.databind.ser.BeanPropertyWriter; +import com.fasterxml.jackson.databind.ser.BeanSerializerModifier; + +import java.util.Collection; +import java.util.List; + + +/** + *

Jackson序列化修改器

+ * + * @author zhengqingya + * @description SpringBoot返回Json数据中null值处理 + * @date 2022/7/26 9:51 + */ +public class MyBeanSerializerModifier extends BeanSerializerModifier { + + @Override + public List changeProperties(SerializationConfig config, BeanDescription beanDesc, List beanProperties) { + for (Object beanProperty : beanProperties) { + BeanPropertyWriter writer = (BeanPropertyWriter) beanProperty; + // 根据字段的类型进行处理 + if (this.isArrayType(writer)) { + // 将集合数组类型null值转换为[] + writer.assignNullSerializer(new CustomNullJsonSerializer.NullArrayJsonSerializer()); + } else if (this.isNumberType(writer)) { + // 将数字类型null值转换为0 +// writer.assignNullSerializer(new CustomNullJsonSerializer.NullNumberJsonSerializer()); + } else if (this.isBooleanType(writer)) { + // 将布尔类型null值转换为false + writer.assignNullSerializer(new CustomNullJsonSerializer.NullBooleanJsonSerializer()); + } else if (this.isStringType(writer)) { + // 将字符串类型null值转换为"" + writer.assignNullSerializer(new CustomNullJsonSerializer.NullStringJsonSerializer()); + } + } + return beanProperties; + } + + /** + * 是否是数组 + */ + private boolean isArrayType(BeanPropertyWriter writer) { + Class clazz = writer.getType().getRawClass(); + return clazz.isArray() || Collection.class.isAssignableFrom(clazz); + } + + /** + * 是否是string + */ + private boolean isStringType(BeanPropertyWriter writer) { + Class clazz = writer.getType().getRawClass(); + return CharSequence.class.isAssignableFrom(clazz) || Character.class.isAssignableFrom(clazz); + } + + + /** + * 是否是int + */ + private boolean isNumberType(BeanPropertyWriter writer) { + Class clazz = writer.getType().getRawClass(); + return Number.class.isAssignableFrom(clazz); + } + + /** + * 是否是boolean + */ + private boolean isBooleanType(BeanPropertyWriter writer) { + Class clazz = writer.getType().getRawClass(); + return clazz.equals(Boolean.class); + } + +} diff --git a/smallboot-api/common/web/src/main/java/com/zhengqing/common/web/config/jackson/MyPropertyNamingStrategy.java b/smallboot-api/common/web/src/main/java/com/zhengqing/common/web/config/jackson/MyPropertyNamingStrategy.java new file mode 100644 index 0000000..70fa1b5 --- /dev/null +++ b/smallboot-api/common/web/src/main/java/com/zhengqing/common/web/config/jackson/MyPropertyNamingStrategy.java @@ -0,0 +1,27 @@ +package com.zhengqing.common.web.config.jackson; + +import cn.hutool.core.util.StrUtil; +import com.fasterxml.jackson.databind.PropertyNamingStrategies; +import com.fasterxml.jackson.databind.PropertyNamingStrategy; + +/** + *

Jackson自定义属性命名策略

+ * + * @author zhengqingya + * @description 驼峰式命名 ex:test_data -> testData + * tips:原生的不支持下划线转驼峰 + * @date 2022/7/26 16:34 + */ +public class MyPropertyNamingStrategy extends PropertyNamingStrategy { + + public static final PropertyNamingStrategy LOWER_CAMEL_CASE_HUMP = new LowerCamelCaseStrategy2(); + + public static class LowerCamelCaseStrategy2 extends PropertyNamingStrategies.NamingBase { + private static final long serialVersionUID = 2L; + + @Override + public String translate(String propertyName) { + return StrUtil.toCamelCase(propertyName); + } + } +} diff --git a/smallboot-api/common/web/src/main/java/com/zhengqing/common/web/config/jackson/ToLongSerializer.java b/smallboot-api/common/web/src/main/java/com/zhengqing/common/web/config/jackson/ToLongSerializer.java new file mode 100644 index 0000000..311ea67 --- /dev/null +++ b/smallboot-api/common/web/src/main/java/com/zhengqing/common/web/config/jackson/ToLongSerializer.java @@ -0,0 +1,24 @@ +package com.zhengqing.common.web.config.jackson; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.JsonSerializer; +import com.fasterxml.jackson.databind.SerializerProvider; + +import java.io.IOException; + +/** + *

jackson 转Long类型

+ * + * @author zhengqingya + * @description 解决全局将Long类型转换为String类型后,部分类又需要Long还是原本Long类型问题 + * 使用方式:添加注解`@JsonSerialize(using = ToLongSerializer.class)` + * @date 2022/8/5 17:22 + */ +public class ToLongSerializer extends JsonSerializer { + + @Override + public void serialize(Long value, JsonGenerator gen, SerializerProvider serializers) throws IOException { + gen.writeNumber(value); + } + +} \ No newline at end of file diff --git a/smallboot-api/common/web/src/main/java/com/zhengqing/common/web/config/jackson/WebMvcConfig.java b/smallboot-api/common/web/src/main/java/com/zhengqing/common/web/config/jackson/WebMvcConfig.java new file mode 100644 index 0000000..9df7e9f --- /dev/null +++ b/smallboot-api/common/web/src/main/java/com/zhengqing/common/web/config/jackson/WebMvcConfig.java @@ -0,0 +1,14 @@ +package com.zhengqing.common.web.config.jackson; + +/** + *

webmvc配置类

+ * + * @author zhengqingya + * @description + * @date 2022/7/26 10:21 + */ +//@Configuration +//public class WebMvcConfig extends WebMvcConfigurationSupport { +// +// +//} \ No newline at end of file diff --git a/smallboot-api/common/web/src/main/java/com/zhengqing/common/web/handler/MyHandlerMethodReturnValueHandler.java b/smallboot-api/common/web/src/main/java/com/zhengqing/common/web/handler/MyHandlerMethodReturnValueHandler.java new file mode 100644 index 0000000..6802ecc --- /dev/null +++ b/smallboot-api/common/web/src/main/java/com/zhengqing/common/web/handler/MyHandlerMethodReturnValueHandler.java @@ -0,0 +1,68 @@ +package com.zhengqing.common.web.handler; + +import com.zhengqing.common.base.constant.AppConstant; +import com.zhengqing.common.base.model.vo.ApiResult; +import com.zhengqing.common.base.model.vo.PageVO; +import com.zhengqing.common.base.util.MyBeanUtil; +import org.springframework.core.MethodParameter; +import org.springframework.util.AntPathMatcher; +import org.springframework.util.PathMatcher; +import org.springframework.web.context.request.NativeWebRequest; +import org.springframework.web.method.support.HandlerMethodReturnValueHandler; +import org.springframework.web.method.support.ModelAndViewContainer; + +import javax.servlet.http.HttpServletRequest; + +/** + *

+ * 封装返回值处理 + *

+ * + * @author zhengqingya + * @description {@link ApiResult} + * @date 2020/8/1 18:40 + */ +public class MyHandlerMethodReturnValueHandler implements HandlerMethodReturnValueHandler { + + private final String IPAGE_CLASS_NAME = "com.baomidou.mybatisplus.extension.plugins.pagination.Page"; + + private final HandlerMethodReturnValueHandler returnValueHandler; + + public MyHandlerMethodReturnValueHandler(HandlerMethodReturnValueHandler returnValueHandler) { + this.returnValueHandler = returnValueHandler; + } + + @Override + public boolean supportsReturnType(MethodParameter methodParameter) { + return this.returnValueHandler.supportsReturnType(methodParameter); + } + + @Override + public void handleReturnValue(Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception { + HttpServletRequest nativeRequest = webRequest.getNativeRequest(HttpServletRequest.class); + // 判断外层是否由ApiResult包裹 + if (returnValue instanceof ApiResult) { + this.returnValueHandler.handleReturnValue(returnValue, returnType, mavContainer, webRequest); + return; + } + // 判断该api是否需要是否处理返回值 + String method = nativeRequest.getMethod(); + String servletPath = nativeRequest.getServletPath(); + // "POST:/auth/oauth/token" + String restfulPath = method + ":" + servletPath; + boolean ifHandleReturnValue = true; + PathMatcher pathMatcher = new AntPathMatcher(); + for (String api : AppConstant.RETURN_VALUE_HANDLER_EXCLUDE_API_LIST) { + if (pathMatcher.match(api, restfulPath)) { + ifHandleReturnValue = false; + break; + } + } + if (this.IPAGE_CLASS_NAME.equals(returnType.getNestedParameterType().getName())) { + // 转换一下只保留前端有效使用字段 + returnValue = MyBeanUtil.copyProperties(returnValue, PageVO.class); + } + this.returnValueHandler.handleReturnValue(ifHandleReturnValue ? ApiResult.ok(returnValue) : returnValue, returnType, mavContainer, webRequest); + } + +} diff --git a/smallboot-api/common/web/src/main/java/com/zhengqing/common/web/schedule/Timer.java b/smallboot-api/common/web/src/main/java/com/zhengqing/common/web/schedule/Timer.java new file mode 100644 index 0000000..0cef3ec --- /dev/null +++ b/smallboot-api/common/web/src/main/java/com/zhengqing/common/web/schedule/Timer.java @@ -0,0 +1,29 @@ +package com.zhengqing.common.web.schedule; + +import com.zhengqing.common.base.util.MyDateUtil; +import lombok.extern.slf4j.Slf4j; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Component; + +/** + *

+ * 数据定时任务 + *

+ * + * @author zhengqingya + * @description + * @date 2020/4/12 0:48 + */ +@Slf4j +@Component +public class Timer { + + /** + * 每1小时执行一次 + */ + @Scheduled(cron = "0 0 0/1 * * ? ") + public void printCurrentTime() { + log.debug("现在时间:【{}】", MyDateUtil.nowStr()); + } + +} diff --git a/smallboot-api/common/web/src/main/java/com/zhengqing/common/web/util/FreeMarkerUtil.java b/smallboot-api/common/web/src/main/java/com/zhengqing/common/web/util/FreeMarkerUtil.java new file mode 100644 index 0000000..d928eca --- /dev/null +++ b/smallboot-api/common/web/src/main/java/com/zhengqing/common/web/util/FreeMarkerUtil.java @@ -0,0 +1,47 @@ +package com.zhengqing.common.web.util; + +import freemarker.template.Configuration; +import freemarker.template.Template; +import lombok.SneakyThrows; + +import java.io.StringReader; +import java.io.StringWriter; +import java.util.Map; + +/** + *

+ * FreeMarker工具类 + *

+ * + * @author zhengqingya + * @description + * @date 2021/2/25 11:16 + */ +public class FreeMarkerUtil { + + /** + * 将模板和数据模型合并 --> 输出合并模板内容 + * + * @param templateDataMap 数据模型 + * @param templateContent 模板内容 + * @return 合并模板结果 + * @author zhengqingya + * @date 2021/2/25 11:23 + */ + @SneakyThrows(Exception.class) + public static String generateTemplateData(Map templateDataMap, String templateContent) { + String templateData = ""; + try { + StringWriter stringWriter = new StringWriter(); + Template template = new Template("template", new StringReader(templateContent), + new Configuration(Configuration.VERSION_2_3_28)); + template.process(templateDataMap, stringWriter); + templateData = stringWriter.toString(); + stringWriter.close(); + } catch (Exception e) { + throw new Exception("《FreeMarker合并模板》 异常:" + e.getMessage()); + } + return templateData; + } + +} diff --git a/smallboot-api/common/web/src/main/java/com/zhengqing/common/web/util/IpUtil.java b/smallboot-api/common/web/src/main/java/com/zhengqing/common/web/util/IpUtil.java new file mode 100644 index 0000000..64d533e --- /dev/null +++ b/smallboot-api/common/web/src/main/java/com/zhengqing/common/web/util/IpUtil.java @@ -0,0 +1,276 @@ +package com.zhengqing.common.web.util; + +import javax.servlet.http.HttpServletRequest; +import java.net.InetAddress; +import java.net.NetworkInterface; +import java.net.UnknownHostException; +import java.util.Enumeration; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + *

+ * IP工具类 + *

+ * + * @author zhengqingya + * @description + * @date 2019/9/18 10:07 + */ +public class IpUtil { + + /** + * 获取客户机ip地址 + * + * @param request: + * @return java.lang.String + */ + public static String getIpAdrress(HttpServletRequest request) { + String ip = request.getHeader("x-forwarded-for"); + if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { + ip = request.getHeader("Proxy-Client-IP"); + } + if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { + ip = request.getHeader("WL-Proxy-Client-IP"); + } + if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { + ip = request.getRemoteAddr(); + } + if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { + ip = request.getHeader("http_client_ip"); + } + if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { + ip = request.getHeader("HTTP_X_FORWARDED_FOR"); + } + // 如果是多级代理,那么取第一个ip为客户ip + if (ip != null && ip.indexOf(",") != -1) { + ip = ip.substring(ip.lastIndexOf(",") + 1, ip.length()).trim(); + } + return ip; + } + + public static boolean internalIp(String ip) { + byte[] addr = textToNumericFormatV4(ip); + return internalIp(addr) || "127.0.0.1".equals(ip); + } + + private static boolean internalIp(byte[] addr) { + final byte b0 = addr[0]; + final byte b1 = addr[1]; + // 10.x.x.x/8 + final byte SECTION_1 = 0x0A; + // 172.16.x.x/12 + final byte SECTION_2 = (byte) 0xAC; + final byte SECTION_3 = (byte) 0x10; + final byte SECTION_4 = (byte) 0x1F; + // 192.168.x.x/16 + final byte SECTION_5 = (byte) 0xC0; + final byte SECTION_6 = (byte) 0xA8; + switch (b0) { + case SECTION_1: + return true; + case SECTION_2: + if (b1 >= SECTION_3 && b1 <= SECTION_4) { + return true; + } + case SECTION_5: + if (b1 == SECTION_6) { + return true; + } + default: + return false; + } + } + + /** + * 将IPv4地址转换成字节 + * + * @param text IPv4地址 + * @return byte 字节 + */ + public static byte[] textToNumericFormatV4(String text) { + if (text.length() == 0) { + return null; + } + + byte[] bytes = new byte[4]; + String[] elements = text.split("\\.", -1); + try { + long l; + int i; + switch (elements.length) { + case 1: + l = Long.parseLong(elements[0]); + if ((l < 0L) || (l > 4294967295L)) { + return null; + } + bytes[0] = (byte) (int) (l >> 24 & 0xFF); + bytes[1] = (byte) (int) ((l & 0xFFFFFF) >> 16 & 0xFF); + bytes[2] = (byte) (int) ((l & 0xFFFF) >> 8 & 0xFF); + bytes[3] = (byte) (int) (l & 0xFF); + break; + case 2: + l = Integer.parseInt(elements[0]); + if ((l < 0L) || (l > 255L)) { + return null; + } + bytes[0] = (byte) (int) (l & 0xFF); + l = Integer.parseInt(elements[1]); + if ((l < 0L) || (l > 16777215L)) { + return null; + } + bytes[1] = (byte) (int) (l >> 16 & 0xFF); + bytes[2] = (byte) (int) ((l & 0xFFFF) >> 8 & 0xFF); + bytes[3] = (byte) (int) (l & 0xFF); + break; + case 3: + for (i = 0; i < 2; ++i) { + l = Integer.parseInt(elements[i]); + if ((l < 0L) || (l > 255L)) { + return null; + } + bytes[i] = (byte) (int) (l & 0xFF); + } + l = Integer.parseInt(elements[2]); + if ((l < 0L) || (l > 65535L)) { + return null; + } + bytes[2] = (byte) (int) (l >> 8 & 0xFF); + bytes[3] = (byte) (int) (l & 0xFF); + break; + case 4: + for (i = 0; i < 4; ++i) { + l = Integer.parseInt(elements[i]); + if ((l < 0L) || (l > 255L)) { + return null; + } + bytes[i] = (byte) (int) (l & 0xFF); + } + break; + default: + return null; + } + } catch (NumberFormatException e) { + return null; + } + return bytes; + } + + public static String getHostIp() { + try { + return InetAddress.getLocalHost().getHostAddress(); + } catch (UnknownHostException e) { + } + return "127.0.0.1"; + } + + public static String getHostName() { + try { + return InetAddress.getLocalHost().getHostName(); + } catch (UnknownHostException e) { + } + return "未知"; + } + + /** + * 检查IP是否合法 + * + * @param ip + * @return + */ + public static boolean ipValid(String ip) { + String regex0 = "(2[0-4]\\d)" + "|(25[0-5])"; + String regex1 = "1\\d{2}"; + String regex2 = "[1-9]\\d"; + String regex3 = "\\d"; + String regex = "(" + regex0 + ")|(" + regex1 + ")|(" + regex2 + ")|(" + regex3 + ")"; + regex = "(" + regex + ").(" + regex + ").(" + regex + ").(" + regex + ")"; + Pattern p = Pattern.compile(regex); + Matcher m = p.matcher(ip); + return m.matches(); + } + + /** + * 获取本地ip 适合windows与linux + * + * @param : + * @return java.lang.String + */ + public static String getLocalIP() { + String localIP = "127.0.0.1"; + try { + Enumeration netInterfaces = NetworkInterface.getNetworkInterfaces(); + while (netInterfaces.hasMoreElements()) { + NetworkInterface ni = (NetworkInterface) netInterfaces.nextElement(); + InetAddress ip = ni.getInetAddresses().nextElement(); + if (!ip.isLoopbackAddress() && ip.getHostAddress().indexOf(":") == -1) { + localIP = ip.getHostAddress(); + break; + } + } + } catch (Exception e) { + try { + localIP = InetAddress.getLocalHost().getHostAddress(); + } catch (UnknownHostException e1) { + e1.printStackTrace(); + } + } + return localIP; + } + + /** + * 把ip转化为整数 + * + * @param ip + * @return + */ + public static long translateIP2Int(String ip) { + String[] intArr = ip.split("\\."); + int[] ipInt = new int[intArr.length]; + for (int i = 0; i < intArr.length; i++) { + ipInt[i] = new Integer(intArr[i]).intValue(); + } + return ipInt[0] * 256 * 256 * 256 + +ipInt[1] * 256 * 256 + ipInt[2] * 256 + ipInt[3]; + } + + /** + * 获取url中的ip+端口前缀信息 + * ex: `http://127.0.0.1:666/xxx/test.html` -> `http://127.0.0.1:666` + * + * @param url 路径 + * @return java.lang.String + * @author zhengqingya + * @date 2023/2/2 15:58 + */ + public static String getIP(String url) { + //使用正则表达式过滤, + String re = "((http|ftp|https)://)(([a-zA-Z0-9._-]+)|([0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}))(([a-zA-Z]{2,6})|(:[0-9]{1,4})?)"; + String str = ""; + // 编译正则表达式 + Pattern pattern = Pattern.compile(re); + // 忽略大小写的写法 + // Pattern pat = Pattern.compile(regEx, Pattern.CASE_INSENSITIVE); + Matcher matcher = pattern.matcher(url); + //若url==http://127.0.0.1:888或www.baidu.com的,正则表达式表示匹配 + if (matcher.matches()) { + str = url; + } else { + String[] split2 = url.split(re); + if (split2.length > 1) { + String substring = url.substring(0, url.length() - split2[1].length()); + str = substring; + } else { + str = split2[0]; + } + } + return str; + } + + public static void main(String[] args) { + System.out.println(getLocalIP()); + System.out.println(getIP("http://127.0.0.1:666/xxx/test.html")); + System.out.println(getIP("https://127.0.0.1:888/xxx/test.html")); + System.out.println(getIP("https://www.zhengqingya.com:888/xxx/test.html")); + } + +} diff --git a/smallboot-api/common/web/src/main/java/com/zhengqing/common/web/util/MyValidatorUtil.java b/smallboot-api/common/web/src/main/java/com/zhengqing/common/web/util/MyValidatorUtil.java new file mode 100644 index 0000000..258b093 --- /dev/null +++ b/smallboot-api/common/web/src/main/java/com/zhengqing/common/web/util/MyValidatorUtil.java @@ -0,0 +1,42 @@ +package com.zhengqing.common.web.util; + +import lombok.SneakyThrows; +import lombok.extern.slf4j.Slf4j; + +import javax.validation.*; +import java.util.Set; + +/** + *

+ * 校验工具类 + *

+ * + * @author zhengqingya + * @date 2019/9/10 9:28 + */ +@Slf4j +public class MyValidatorUtil { + + /** + * 手动校验 (把注解@valid放在servevice层上是没有效果的,只有放在Controller上才有效果,因此通过调用ValidatorFactory工厂方法创建一个实例对象来进行手动校验 ) + * 百度建议: @Validated放到实现上,@NotNull,@Valid等声明放到接口上 + * + * @param obj 校验对象 + * @param groups 组校验 + * @return void + * @author zhengqingya + * @date 2020/10/19 11:27 + */ + @SneakyThrows(Exception.class) + public static void validate(Object obj, Class... groups) { + ValidatorFactory vf = Validation.buildDefaultValidatorFactory(); + Validator validator = vf.getValidator(); + Set> set = validator.validate(obj, groups); + for (ConstraintViolation constraintViolation : set) { + String violationMessage = constraintViolation.getMessage(); + log.error(" 【{}】", constraintViolation.getPropertyPath() + ":" + violationMessage); + throw new ValidationException(violationMessage); + } + } + +} diff --git a/smallboot-api/common/web/src/main/java/com/zhengqing/common/web/util/RequestContextUtil.java b/smallboot-api/common/web/src/main/java/com/zhengqing/common/web/util/RequestContextUtil.java new file mode 100644 index 0000000..75682f7 --- /dev/null +++ b/smallboot-api/common/web/src/main/java/com/zhengqing/common/web/util/RequestContextUtil.java @@ -0,0 +1,49 @@ +package com.zhengqing.common.web.util; + +import com.google.common.collect.Maps; +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.context.request.ServletRequestAttributes; + +import javax.servlet.http.HttpServletRequest; +import java.util.Enumeration; +import java.util.Map; + +/** + *

上下文请求信息工具类

+ * + * @author zhengqingya + * @description + * @date 2021/6/30 9:38 下午 + */ +@Slf4j +public class RequestContextUtil { + + /** + * 获取请求头数据 + * + * @return key->请求头名称 value->请求头值 + * @author zhengqingya + * @date 2021/6/30 9:39 下午 + */ + public static Map getHeaderMap() { + Map headerMap = Maps.newLinkedHashMap(); + try { + ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); + if (requestAttributes == null) { + return headerMap; + } + HttpServletRequest request = requestAttributes.getRequest(); + Enumeration enumeration = request.getHeaderNames(); + while (enumeration.hasMoreElements()) { + String key = enumeration.nextElement(); + String value = request.getHeader(key); + headerMap.put(key, value); + } + } catch (Exception e) { + log.error("《RequestContextUtil》 获取请求头参数失败:", e); + } + return headerMap; + } + +} diff --git a/smallboot-api/common/web/src/main/java/com/zhengqing/common/web/util/ServletUtil.java b/smallboot-api/common/web/src/main/java/com/zhengqing/common/web/util/ServletUtil.java new file mode 100644 index 0000000..37e12ac --- /dev/null +++ b/smallboot-api/common/web/src/main/java/com/zhengqing/common/web/util/ServletUtil.java @@ -0,0 +1,167 @@ +package com.zhengqing.common.web.util; + +import cn.hutool.core.convert.Convert; +import org.apache.commons.lang3.StringUtils; +import org.springframework.web.context.request.RequestAttributes; +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.context.request.ServletRequestAttributes; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpSession; +import java.io.IOException; +import java.net.URLDecoder; + +/** + *

+ * 客户端工具类 + *

+ * + * @author zhengqingya + * @description + * @date 2020/8/1 18:22 + */ +public class ServletUtil { + + private static final String X_REAL_IP = "X-Real-IP"; + private static final String X_FORWARDED_FOR = "X-Forwarded-For"; + private static final String X_FORWARDED_FOR_SPLIT_SYMBOL = ","; + + /** + * 获取String参数 + */ + public static String getParameter(String name) { + HttpServletRequest request = getRequest(); + if (request != null) { + String requestHeaderResult = request.getHeader(name); + if (requestHeaderResult != null) { + requestHeaderResult = URLDecoder.decode(requestHeaderResult); + } + return request.getParameter(name) == null ? requestHeaderResult : request.getParameter(name); + } + return null; + } + + /** + * 获取String参数 + */ + public static String getParameter(String name, String defaultValue) { + return StringUtils.defaultString(getParameter(name), defaultValue); + } + + /** + * 获取int参数 + */ + public static int getParameterToInt(String name) { + return Convert.toInt(getParameter(name)); + } + + /** + * 获取int参数 + */ + public static int getParameterToInt(String name, int defaultValue) { + return Convert.toInt(getParameter(name), defaultValue); + } + + /** + * 获取long参数 + */ + public static Long getParameterToLong(String name, Long defaultValue) { + return Convert.toLong(getParameter(name), defaultValue); + } + + /** + * 获取request + */ + public static HttpServletRequest getRequest() { + ServletRequestAttributes attributes = getRequestAttributes(); + return attributes == null ? null : attributes.getRequest(); + } + + /** + * 获取response + */ + public static HttpServletResponse getResponse() { + return getRequestAttributes().getResponse(); + } + + /** + * 获取session + */ + public static HttpSession getSession() { + return getRequest().getSession(); + } + + public static ServletRequestAttributes getRequestAttributes() { + RequestAttributes attributes = RequestContextHolder.getRequestAttributes(); + return (ServletRequestAttributes) attributes; + } + + /** + * 将字符串渲染到客户端 + * + * @param response 渲染对象 + * @param string 待渲染的字符串 + * @return null + */ + public static String renderString(HttpServletResponse response, String string) { + try { + response.setContentType("application/json"); + response.setCharacterEncoding("utf-8"); + response.getWriter().print(string); + } catch (IOException e) { + e.printStackTrace(); + } + return null; + } + + /** + * 获取项目webroot根目录 + */ + public static String getWebRootPath() { + return getRequest().getSession().getServletContext().getRealPath("/"); + } + + /** + * 是否是Ajax异步请求 + * + * @param request + */ + public static boolean isAjaxRequest(HttpServletRequest request) { + String accept = request.getHeader("accept"); + if (accept != null && accept.indexOf("application/json") != -1) { + return true; + } + + String xRequestedWith = request.getHeader("X-Requested-With"); + if (xRequestedWith != null && xRequestedWith.indexOf("XMLHttpRequest") != -1) { + return true; + } + + String uri = request.getRequestURI(); + if (StringUtils.containsIgnoreCase(uri, ".json") || StringUtils.containsIgnoreCase(uri, ".xml")) { + return true; + } + + String ajax = request.getParameter("__ajax"); + if (StringUtils.containsIgnoreCase(ajax, ".json") || StringUtils.containsIgnoreCase(ajax, ".xml")) { + return true; + } + return false; + } + + /** + * 获取实际请求地址 + * + * @param request + * @return IP地址 + */ + public static String getRemoteIp(HttpServletRequest request) { + String xForwardedFor = request.getHeader(X_FORWARDED_FOR); + if (!StringUtils.isBlank(xForwardedFor)) { + return xForwardedFor.split(X_FORWARDED_FOR_SPLIT_SYMBOL)[0].trim(); + } + String nginxHeader = request.getHeader(X_REAL_IP); + return StringUtils.isBlank(nginxHeader) ? request.getRemoteAddr() : nginxHeader; + } +} diff --git a/smallboot-api/common/web/src/main/java/com/zhengqing/common/web/util/YmlUtil.java b/smallboot-api/common/web/src/main/java/com/zhengqing/common/web/util/YmlUtil.java new file mode 100644 index 0000000..1c933ce --- /dev/null +++ b/smallboot-api/common/web/src/main/java/com/zhengqing/common/web/util/YmlUtil.java @@ -0,0 +1,38 @@ +package com.zhengqing.common.web.util; + +import cn.hutool.core.lang.Assert; +import lombok.SneakyThrows; +import lombok.extern.slf4j.Slf4j; +import org.yaml.snakeyaml.Yaml; + +import java.io.FileInputStream; + +/** + *

读取yml文件工具类

+ * + * @author zhengqingya + * @description + * @date 2021/8/24 10:28 下午 + */ +@Slf4j +public class YmlUtil { + + /** + * 读取yml内容转对象 + * + * @param ymlResourceLocation yml文件内容 + * @param clz 目标对象class + * @return T + * @author zhengqingya + * @date 2021/8/24 10:52 下午 + */ + @SneakyThrows(Exception.class) + public static T getYml(String ymlResourceLocation, Class clz) { + Assert.notBlank(ymlResourceLocation, "yml文件不存在!"); + Yaml yaml = new Yaml(); + T t = yaml.loadAs(new FileInputStream(ymlResourceLocation), clz); + log.info("[读取yml] 文件内容:{}", t); + return t; + } + +} diff --git a/smallboot-api/common/web/src/main/resources/application-web.yml b/smallboot-api/common/web/src/main/resources/application-web.yml new file mode 100644 index 0000000..2c885aa --- /dev/null +++ b/smallboot-api/common/web/src/main/resources/application-web.yml @@ -0,0 +1,14 @@ +spring: + # jackson全局处理 + jackson: + date-format: yyyy-MM-dd HH:mm:ss # JSON入参及返回值Date时间格式 + time-zone: GMT+8 # 时区 + # property-naming-strategy: LOWER_CAMEL_CASE # 属性命名策略->转驼峰式 {@see com.fasterxml.jackson.databind.PropertyNamingStrategies } + lifecycle: + timeout-per-shutdown-phase: 30s # 设置缓冲期,最大等待时间(即超时间后,无论线程任务是否执行完毕都会停机处理,需合理设置哦) + mvc: + throw-exception-if-no-handler-found: true # 关闭springboot的异常自动资源映射,让其抛出异常 + web: + resources: + add-mappings: true # 是否开启默认的资源处理,默认为true + diff --git a/smallboot-api/deploy.sh b/smallboot-api/deploy.sh new file mode 100644 index 0000000..acd1807 --- /dev/null +++ b/smallboot-api/deploy.sh @@ -0,0 +1,27 @@ +#################################### +# @description 构建docker镜像 +# @params $? => 代表上一个命令执行后的退出状态: 0->成功,1->失败 +# @example => deploy.sh +# @author zhengqingya +# @date 2023/2/2 14:55 +#################################### + +# 在执行过程中若遇到使用了未定义的变量或命令返回值为非零,将直接报错退出 +set -eu + +# 删除旧jar包 +rm -rf docker/*.jar + +# 打包 +mvn clean install -Dmaven.test.skip=true + +# 移动jar包 +mv app/target/app.jar docker + +# 构建docker镜像 +cd docker +docker build -f Dockerfile --build-arg JAVA_OPTS="-XX:+UseG1GC" -t "registry.cn-hangzhou.aliyuncs.com/zhengqingya/smallboot-api:prod" . --no-cache +docker push registry.cn-hangzhou.aliyuncs.com/zhengqingya/smallboot-api:prod + +# 删除jar包 +rm -rf *.jar \ No newline at end of file diff --git a/smallboot-api/doc/smallboot.sql b/smallboot-api/doc/smallboot.sql new file mode 100644 index 0000000..a80c5a7 --- /dev/null +++ b/smallboot-api/doc/smallboot.sql @@ -0,0 +1,407 @@ +/* + Navicat Premium Data Transfer + + Source Server : 3306 + Source Server Type : MySQL + Source Server Version : 50726 + Source Host : localhost:3306 + Source Schema : smallboot + + Target Server Type : MySQL + Target Server Version : 50726 + File Encoding : 65001 + + Date: 10/02/2023 15:08:30 +*/ + +SET NAMES utf8mb4; +SET FOREIGN_KEY_CHECKS = 0; + +-- ---------------------------- +-- Table structure for t_sys_dict +-- ---------------------------- +DROP TABLE IF EXISTS `t_sys_dict`; +CREATE TABLE `t_sys_dict` ( + `id` int(11) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键ID', + `dict_type_id` int(11) UNSIGNED NOT NULL COMMENT '字典类型id', + `code` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '字典类型编码', + `name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '字典名', + `value` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '字典值', + `status` tinyint(1) UNSIGNED NOT NULL DEFAULT 1 COMMENT '状态(0->停用 1->正常)', + `sort` int(11) NOT NULL COMMENT '排序', + `remark` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '备注', + `create_by` int(11) UNSIGNED NOT NULL COMMENT '创建人', + `create_time` datetime NOT NULL COMMENT '创建时间', + `update_by` int(11) UNSIGNED NOT NULL COMMENT '修改人', + `update_time` datetime NOT NULL COMMENT '修改时间', + `is_deleted` tinyint(1) UNSIGNED NOT NULL DEFAULT 0 COMMENT '是否删除(1->是,0->否)', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 186 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '基础模块-数据字典' ROW_FORMAT = COMPACT; + +-- ---------------------------- +-- Records of t_sys_dict +-- ---------------------------- +INSERT INTO `t_sys_dict` VALUES (75, 1, 'permission_btn', '添加', 'add', 1, 1, '', 1, '2020-08-22 15:01:51', 1, '2021-08-28 00:31:47', 0); +INSERT INTO `t_sys_dict` VALUES (76, 1, 'permission_btn', '删除', 'delete', 1, 2, NULL, 1, '2020-08-22 15:01:51', 0, '2021-08-28 00:03:00', 0); +INSERT INTO `t_sys_dict` VALUES (77, 1, 'permission_btn', '编辑', 'edit', 1, 3, NULL, 1, '2020-08-22 15:01:51', 0, '2021-08-28 00:03:00', 0); +INSERT INTO `t_sys_dict` VALUES (78, 1, 'permission_btn', '查询', 'query', 1, 4, NULL, 1, '2020-08-22 15:01:51', 0, '2021-08-28 00:03:00', 0); +INSERT INTO `t_sys_dict` VALUES (79, 1, 'permission_btn', '重置', 'reset', 1, 5, NULL, 1, '2020-08-22 15:01:51', 0, '2021-08-28 00:03:00', 0); +INSERT INTO `t_sys_dict` VALUES (80, 1, 'permission_btn', '详情', 'detail', 1, 6, NULL, 1, '2020-08-22 15:01:51', 0, '2021-08-28 00:03:00', 0); +INSERT INTO `t_sys_dict` VALUES (81, 1, 'permission_btn', '保存', 'save', 1, 7, NULL, 1, '2020-08-22 15:01:51', 0, '2021-08-28 00:03:00', 0); +INSERT INTO `t_sys_dict` VALUES (98, 1, 'permission_btn', '权限', 'permission', 1, 24, NULL, 1, '2020-08-22 15:01:51', 0, '2021-08-28 00:03:01', 0); +INSERT INTO `t_sys_dict` VALUES (117, 1, 'permission_btn', '开启授权', 'open_authorization', 1, 27, NULL, 1, '2020-08-22 15:01:51', 0, '2021-08-28 00:03:01', 0); +INSERT INTO `t_sys_dict` VALUES (141, 1, 'permission_btn', '刷新', 'refresh', 1, 28, NULL, 1, '2020-08-22 15:01:51', 0, '2021-08-28 00:03:01', 0); +INSERT INTO `t_sys_dict` VALUES (153, 1, 'permission_btn', '导出', 'export', 1, 29, NULL, 1, '2020-08-22 15:01:51', 0, '2021-08-28 00:03:01', 0); +INSERT INTO `t_sys_dict` VALUES (154, 1, 'permission_btn', '表格列过滤', 'column_filter', 1, 30, NULL, 1, '2020-08-22 15:01:51', 0, '2021-08-28 00:03:01', 0); +INSERT INTO `t_sys_dict` VALUES (155, 2, 'file_suffix', '.java', '.java', 1, 1, NULL, 1, '2020-08-22 15:01:51', 0, '2021-08-28 00:03:01', 0); +INSERT INTO `t_sys_dict` VALUES (156, 2, 'file_suffix', '.xml', '.xml', 1, 2, NULL, 1, '2020-08-22 15:01:51', 0, '2021-08-28 00:03:01', 0); +INSERT INTO `t_sys_dict` VALUES (157, 2, 'file_suffix', '.py', '.py', 1, 3, NULL, 1, '2020-08-22 15:01:51', 0, '2021-08-28 00:03:01', 0); +INSERT INTO `t_sys_dict` VALUES (158, 2, 'file_suffix', '.vue', '.vue', 1, 4, NULL, 1, '2020-08-22 15:01:51', 0, '2021-08-28 00:03:01', 0); +INSERT INTO `t_sys_dict` VALUES (159, 2, 'file_suffix', '.md', '.md', 1, 5, NULL, 1, '2020-08-22 15:01:51', 0, '2021-08-28 00:03:01', 0); +INSERT INTO `t_sys_dict` VALUES (160, 2, 'file_suffix', '.php', '.php', 1, 6, NULL, 1, '2020-08-22 15:01:51', 0, '2021-08-28 00:03:01', 0); +INSERT INTO `t_sys_dict` VALUES (161, 2, 'file_suffix', '.html', '.html', 1, 7, NULL, 1, '2020-08-22 15:01:51', 0, '2021-08-28 00:03:01', 0); +INSERT INTO `t_sys_dict` VALUES (162, 2, 'file_suffix', '.js', '.js', 1, 8, NULL, 1, '2020-08-22 15:01:51', 0, '2021-08-28 00:03:01', 0); +INSERT INTO `t_sys_dict` VALUES (163, 2, 'file_suffix', '.jsp', '.jsp', 1, 9, NULL, 1, '2020-08-22 15:01:51', 0, '2021-08-28 00:03:01', 0); +INSERT INTO `t_sys_dict` VALUES (165, 3, 'element_icon', 'AddLocation', 'AddLocation', 1, 1, '', 1, '2020-08-30 03:05:05', 1, '2022-07-22 09:57:53', 0); +INSERT INTO `t_sys_dict` VALUES (166, 3, 'element_icon', 'Aim', 'Aim', 1, 2, '', 1, '2020-08-30 03:05:24', 1, '2022-07-22 09:58:01', 0); +INSERT INTO `t_sys_dict` VALUES (167, 3, 'element_icon', 'AlarmClock', 'AlarmClock', 1, 3, '', 1, '2020-08-30 03:05:31', 1, '2022-07-22 09:58:07', 0); +INSERT INTO `t_sys_dict` VALUES (168, 3, 'element_icon', 'Apple', 'Apple', 1, 4, '', 1, '2020-08-30 03:12:30', 1, '2022-07-22 09:58:15', 0); +INSERT INTO `t_sys_dict` VALUES (169, 3, 'element_icon', 'ArrowDown', 'ArrowDown', 1, 5, '', 1, '2020-08-30 03:14:05', 1, '2022-07-22 09:58:38', 0); +INSERT INTO `t_sys_dict` VALUES (170, 3, 'element_icon', 'ArrowDownBold', 'ArrowDownBold', 1, 6, '', 1, '2020-08-30 03:14:28', 1, '2022-07-22 09:58:45', 0); +INSERT INTO `t_sys_dict` VALUES (171, 3, 'element_icon', 'ArrowLeft', 'ArrowLeft', 1, 7, '', 1, '2020-08-30 03:14:56', 1, '2022-07-22 09:58:51', 0); +INSERT INTO `t_sys_dict` VALUES (172, 3, 'element_icon', 'ArrowRight', 'ArrowRight', 1, 8, '', 1, '2020-08-30 03:15:49', 1, '2022-07-22 09:59:05', 0); +INSERT INTO `t_sys_dict` VALUES (173, 3, 'element_icon', 'ArrowRightBold', 'ArrowRightBold', 1, 9, '', 1, '2020-08-30 03:16:51', 1, '2022-07-22 09:59:13', 0); +INSERT INTO `t_sys_dict` VALUES (174, 3, 'element_icon', 'ArrowUp', 'ArrowUp', 1, 10, '', 1, '2020-08-30 03:18:32', 1, '2022-07-22 09:59:19', 0); +INSERT INTO `t_sys_dict` VALUES (180, 1, 'permission_btn', '设计表', 'design_table', 1, 31, NULL, 1, '2020-09-06 19:09:40', 0, '2021-08-28 00:03:02', 0); +INSERT INTO `t_sys_dict` VALUES (181, 6, 'oauth_type', 'gitee', '1', 1, 1, NULL, 1, '2020-12-06 13:16:39', 0, '2021-08-28 00:03:02', 0); +INSERT INTO `t_sys_dict` VALUES (182, 6, 'oauth_type', 'github', '2', 1, 2, NULL, 1, '2020-12-06 13:16:54', 0, '2021-08-28 00:03:02', 0); +INSERT INTO `t_sys_dict` VALUES (183, 6, 'oauth_type', 'qq', '3', 1, 3, NULL, 1, '2020-12-06 13:17:03', 0, '2021-08-28 00:03:02', 0); +INSERT INTO `t_sys_dict` VALUES (184, 3, 'element_icon', 'Monitor', 'Monitor', 1, 11, NULL, 0, '2023-02-01 16:42:06', 0, '2023-02-01 16:42:06', 0); +INSERT INTO `t_sys_dict` VALUES (185, 3, 'element_icon', 'Menu', 'Menu', 1, 12, NULL, 0, '2023-02-01 16:42:33', 0, '2023-02-01 16:42:33', 0); + +-- ---------------------------- +-- Table structure for t_sys_dict_type +-- ---------------------------- +DROP TABLE IF EXISTS `t_sys_dict_type`; +CREATE TABLE `t_sys_dict_type` ( + `id` int(11) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键ID', + `code` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '字典类型编码', + `name` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '字典类型名称(展示用)', + `status` tinyint(1) UNSIGNED NOT NULL DEFAULT 1 COMMENT '状态(0->停用 1->正常)', + `sort` int(10) UNSIGNED NOT NULL DEFAULT 1 COMMENT '排序', + `is_fixed` tinyint(1) UNSIGNED NOT NULL DEFAULT 0 COMMENT '是否固定(0->否 1->是)', + `create_by` int(11) UNSIGNED NOT NULL COMMENT '创建人', + `create_time` datetime NOT NULL COMMENT '创建时间', + `update_by` int(11) UNSIGNED NOT NULL COMMENT '修改人', + `update_time` datetime NOT NULL COMMENT '修改时间', + `is_deleted` tinyint(1) UNSIGNED NOT NULL DEFAULT 0 COMMENT '是否删除(1->是,0->否)', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 7 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '基础模块-数据字典类型' ROW_FORMAT = COMPACT; + +-- ---------------------------- +-- Records of t_sys_dict_type +-- ---------------------------- +INSERT INTO `t_sys_dict_type` VALUES (1, 'permission_btn', '权限按钮', 1, 1, 0, 1, '2020-08-22 15:01:51', 1, '2021-08-28 23:00:04', 0); +INSERT INTO `t_sys_dict_type` VALUES (2, 'file_suffix', '文件后缀名', 1, 4, 0, 1, '2020-08-22 15:01:51', 1, '2020-08-22 15:01:51', 0); +INSERT INTO `t_sys_dict_type` VALUES (3, 'element_icon', 'Element-Icon图标', 1, 2, 0, 1, '2020-08-30 02:52:36', 1, '2020-08-30 02:52:38', 0); +INSERT INTO `t_sys_dict_type` VALUES (6, 'oauth_type', '第三方帐号授权类型', 1, 3, 0, 1, '2020-12-06 13:11:27', 1, '2020-12-06 13:21:45', 0); + +-- ---------------------------- +-- Table structure for t_sys_menu +-- ---------------------------- +DROP TABLE IF EXISTS `t_sys_menu`; +CREATE TABLE `t_sys_menu` ( + `menu_id` int(11) NOT NULL AUTO_INCREMENT COMMENT '菜单ID', + `title` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '菜单名称', + `name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '菜单名称 - 英文', + `icon` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '菜单图标', + `path` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '菜单链接url', + `parent_id` int(11) NULL DEFAULT 0 COMMENT '父类菜单ID', + `sort` int(11) NULL DEFAULT NULL COMMENT '菜单排序', + `component` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '组件名', + `hidden` tinyint(1) NULL DEFAULT 1 COMMENT '是否隐藏 1:隐藏 0:显示', + `redirect` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '重定向路径', + `status` tinyint(1) NULL DEFAULT 1 COMMENT '菜单状态 1:启用 0:禁用', + `type` tinyint(4) NULL DEFAULT 0 COMMENT '菜单类型 0菜单 1按钮', + `always_show` tinyint(1) NULL DEFAULT 1 COMMENT '是否总是显示 0:不显示 1:显示', + `breadcrumb` tinyint(1) NULL DEFAULT 1 COMMENT '面包屑 0 false 1 true', + `create_by` int(11) NOT NULL COMMENT '创建人', + `create_time` datetime NOT NULL COMMENT '创建时间', + `update_by` int(11) NOT NULL COMMENT '修改人', + `update_time` datetime NOT NULL COMMENT '修改时间', + `is_deleted` tinyint(1) UNSIGNED NOT NULL DEFAULT 0 COMMENT '是否删除(1->是,0->否)', + PRIMARY KEY (`menu_id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 45 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '系统管理-菜单表' ROW_FORMAT = COMPACT; + +-- ---------------------------- +-- Records of t_sys_menu +-- ---------------------------- +INSERT INTO `t_sys_menu` VALUES (2, '系统管理', 'system', 'Setting', '/system', 0, 2, 'Layout', 0, NULL, 1, 0, 1, 1, 1, '2020-08-22 15:01:51', 1, '2020-08-30 03:17:44', 0); +INSERT INTO `t_sys_menu` VALUES (3, '菜单管理', 'menu', '', 'menu', 2, 9, 'system/menu/index', 0, NULL, 1, 0, 0, 1, 1, '2020-08-22 15:01:51', 1, '2020-08-22 15:01:51', 0); +INSERT INTO `t_sys_menu` VALUES (4, '用户管理', 'user', '', 'user', 2, 1, 'system/user/index', 0, '', 1, 0, 0, 1, 1, '2020-08-22 15:01:51', 1, '2020-08-22 15:01:51', 0); +INSERT INTO `t_sys_menu` VALUES (5, '角色管理', 'role', NULL, 'role', 2, 3, 'system/role/list', 0, NULL, 1, 0, 0, 1, 1, '2020-08-22 15:01:51', 1, '2020-08-22 15:01:51', 0); +INSERT INTO `t_sys_menu` VALUES (6, '角色权限', 'roleForm', NULL, 'roleForm', 2, 8, 'system/role/form', 1, NULL, 1, 0, 1, 1, 1, '2020-08-22 15:01:51', 1, '2020-08-22 15:01:51', 0); +INSERT INTO `t_sys_menu` VALUES (8, '个人中心', 'personal-center', NULL, 'personal-center', 2, 2, 'system/personal-center/index', 0, NULL, 1, 0, 0, 1, 1, '2020-08-22 15:01:51', 1, '2020-08-22 15:01:51', 0); +INSERT INTO `t_sys_menu` VALUES (10, '数据字典', 'dict', NULL, 'dict', 2, 10, 'system/dict/index', 0, NULL, 1, 0, 0, 1, 1, '2020-08-22 15:01:51', 1, '2020-08-22 15:01:51', 0); +INSERT INTO `t_sys_menu` VALUES (11, '首页', 'dashboard', 'Loading', '/', 0, 0, 'Layout', 0, '/dashboard', 1, 0, 0, 0, 1, '2020-08-22 15:01:51', 0, '2023-02-01 16:39:06', 0); +INSERT INTO `t_sys_menu` VALUES (12, '首页', 'Dashboard', '', '/dashboard', 11, 1, 'dashboard/index', 0, '', 1, 0, 0, 0, 1, '2020-08-22 15:01:51', 1, '2022-07-15 16:53:46', 0); + +-- ---------------------------- +-- Table structure for t_sys_oauth_client +-- ---------------------------- +DROP TABLE IF EXISTS `t_sys_oauth_client`; +CREATE TABLE `t_sys_oauth_client` ( + `client_id` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '客户端ID,唯一标识', + `client_secret` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '客户端访问秘钥,BCryptPasswordEncoder加密算法加密', + `resource_ids` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '可访问资源id(英文逗号分隔)', + `scope` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '授权范围(英文逗号分隔)', + `authorized_grant_types` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '授权类型(英文逗号分隔)', + `web_server_redirect_uri` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '重定向uri', + `authorities` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '@PreAuthorize(\"hasAuthority(\' admin \')\")可以在方法上标志 用户或者说client 需要说明样的权限\r\n\n\n指定客户端所拥有的Spring Security的权限值\r\n(英文逗号分隔)', + `access_token_validity` int(11) NOT NULL COMMENT '令牌有效期(单位:秒)', + `refresh_token_validity` int(11) NOT NULL COMMENT '刷新令牌有效期(单位:秒)', + `additional_information` varchar(4096) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '预留字段,在Oauth的流程中没有实际的使用(JSON格式数据)', + `autoapprove` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '设置用户是否自动Approval操作, 默认值为 \' false \'\r\n可选值包括 \' true \',\' false \', \' read \',\' write \'.\r\n该字段只适用于grant_type=\"authorization_code\"的情况,当用户登录成功后,若该值为\' true \'或支持的scope值,则会跳过用户Approve的页面, 直接授权', + `create_time` datetime NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `update_time` datetime NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + PRIMARY KEY (`client_id`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '系统管理-oauth2授权客户端' ROW_FORMAT = DYNAMIC; + +-- ---------------------------- +-- Records of t_sys_oauth_client +-- ---------------------------- +INSERT INTO `t_sys_oauth_client` VALUES ('app', '123456', '', 'all', 'password,refresh_token', '', NULL, 3600, 259200, NULL, 'true', '2022-04-02 09:31:10', '2022-06-16 12:25:03'); +INSERT INTO `t_sys_oauth_client` VALUES ('client', '123456', '', 'all', 'password,refresh_token', '', NULL, 3600, 259200, NULL, 'true', '2022-04-02 09:31:10', '2022-06-16 12:25:03'); +INSERT INTO `t_sys_oauth_client` VALUES ('mini', '123456', '', 'all', 'password,refresh_token', '', NULL, 3600, 259200, NULL, 'true', '2022-04-02 09:31:10', '2022-06-16 12:25:03'); +INSERT INTO `t_sys_oauth_client` VALUES ('web', '123456', '', 'all', 'password,refresh_token,captcha', '', NULL, 3600, 259200, NULL, NULL, '2022-04-02 09:31:10', '2022-06-16 12:39:37'); +INSERT INTO `t_sys_oauth_client` VALUES ('zq_app_id', '123456', 'res1', 'all', 'authorization_code,refresh_token', 'http://127.0.0.1:10020/index.html', NULL, 3600, 259200, NULL, 'true', '2022-04-02 09:31:10', '2022-06-16 12:25:03'); + +-- ---------------------------- +-- Table structure for t_sys_permission +-- ---------------------------- +DROP TABLE IF EXISTS `t_sys_permission`; +CREATE TABLE `t_sys_permission` ( + `id` int(11) NOT NULL AUTO_INCREMENT COMMENT 'id', + `name` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '名称', + `menu_id` int(11) NOT NULL COMMENT '菜单ID', + `btn_id` int(11) NULL DEFAULT NULL COMMENT '按钮ID', + `btn_perm` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '按钮权限标识', + `url_perm` varchar(200) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT 'URL权限标识', + `create_by` int(11) NOT NULL COMMENT '创建人', + `create_time` datetime NOT NULL COMMENT '创建时间', + `update_by` int(11) NOT NULL COMMENT '修改人', + `update_time` datetime NOT NULL COMMENT '修改时间', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 6 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '系统管理-菜单关联权限表' ROW_FORMAT = COMPACT; + +-- ---------------------------- +-- Records of t_sys_permission +-- ---------------------------- +INSERT INTO `t_sys_permission` VALUES (1, '查看用户', 4, 78, 'sys:user:view', 'GET:/web/api/user/*', 1, '2020-08-22 15:01:51', 1, '2020-08-22 15:01:51'); +INSERT INTO `t_sys_permission` VALUES (2, '编辑用户', 4, 75, 'sys:user:edit', 'PUT:/web/api/user/*', 1, '2020-08-22 15:01:51', 1, '2020-08-22 15:01:51'); +INSERT INTO `t_sys_permission` VALUES (3, '新增用户', 4, 76, 'sys:user:add', 'POST:/web/api/user', 1, '2020-08-22 15:01:51', 1, '2020-08-22 15:01:51'); +INSERT INTO `t_sys_permission` VALUES (4, '删除用户', 4, 77, 'sys:user:delete', 'DELETE:/web/api/user/*', 1, '2020-08-22 15:01:51', 1, '2020-08-22 15:01:51'); +INSERT INTO `t_sys_permission` VALUES (5, 'demo测试', 4, 77, 'sys:demo:test', 'GET:/demo/web/api/demo/demo/*', 1, '2020-08-22 15:01:51', 1, '2020-08-22 15:01:51'); + +-- ---------------------------- +-- Table structure for t_sys_property +-- ---------------------------- +DROP TABLE IF EXISTS `t_sys_property`; +CREATE TABLE `t_sys_property` ( + `id` int(11) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键ID', + `key` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '属性key', + `value` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '属性value', + `remark` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '备注', + `create_by` int(10) UNSIGNED NOT NULL COMMENT '创建人', + `create_time` datetime NOT NULL COMMENT '创建时间', + `update_by` int(10) UNSIGNED NOT NULL COMMENT '修改人', + `update_time` datetime NOT NULL COMMENT '修改时间', + `is_deleted` tinyint(1) UNSIGNED NOT NULL DEFAULT 0 COMMENT '是否删除(1->是,0->否)', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 11 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '系统管理-系统属性' ROW_FORMAT = COMPACT; + +-- ---------------------------- +-- Records of t_sys_property +-- ---------------------------- +INSERT INTO `t_sys_property` VALUES (9, 'test', '测试', 'this is test data.', 0, '2021-09-07 10:43:26', 0, '2021-09-07 10:43:26', 0); +INSERT INTO `t_sys_property` VALUES (10, 'hello', 'world', 'hello world !', 0, '2021-09-07 10:45:45', 0, '2021-09-07 10:45:45', 0); + +-- ---------------------------- +-- Table structure for t_sys_role +-- ---------------------------- +DROP TABLE IF EXISTS `t_sys_role`; +CREATE TABLE `t_sys_role` ( + `role_id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键ID', + `name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '角色名', + `code` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '角色编号', + `status` tinyint(4) NOT NULL DEFAULT 1 COMMENT '状态(1:开启(默认) 0:禁用)', + `create_by` int(11) NOT NULL COMMENT '创建人', + `create_time` datetime NOT NULL COMMENT '创建时间', + `update_by` int(11) NOT NULL COMMENT '修改人', + `update_time` datetime NOT NULL ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间', + PRIMARY KEY (`role_id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 11 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '系统管理-角色管理表' ROW_FORMAT = COMPACT; + +-- ---------------------------- +-- Records of t_sys_role +-- ---------------------------- +INSERT INTO `t_sys_role` VALUES (1, '普通用户', 'persion', 1, 1, '2020-08-22 15:01:51', 0, '2023-02-01 16:33:02'); +INSERT INTO `t_sys_role` VALUES (9, '超级管理员', 'super_admin', 1, 1, '2020-08-22 15:01:51', 1, '2022-07-15 11:36:42'); +INSERT INTO `t_sys_role` VALUES (10, '测试用户', 'test', 1, 1, '2022-08-05 20:00:36', 1, '2022-08-06 15:59:10'); + +-- ---------------------------- +-- Table structure for t_sys_role_menu +-- ---------------------------- +DROP TABLE IF EXISTS `t_sys_role_menu`; +CREATE TABLE `t_sys_role_menu` ( + `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键ID', + `role_id` int(11) NOT NULL COMMENT '角色ID', + `menu_id` int(11) NOT NULL COMMENT '菜单ID', + `create_by` int(11) NOT NULL COMMENT '创建人', + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `update_by` int(11) NOT NULL COMMENT '修改人', + `update_time` datetime NOT NULL ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 141 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '系统管理-角色菜单关联表' ROW_FORMAT = COMPACT; + +-- ---------------------------- +-- Records of t_sys_role_menu +-- ---------------------------- +INSERT INTO `t_sys_role_menu` VALUES (124, 1, 11, 0, '2023-02-01 16:39:27', 0, '2023-02-01 16:39:27'); +INSERT INTO `t_sys_role_menu` VALUES (125, 1, 12, 0, '2023-02-01 16:39:27', 0, '2023-02-01 16:39:27'); +INSERT INTO `t_sys_role_menu` VALUES (126, 1, 27, 0, '2023-02-01 16:39:27', 0, '2023-02-01 16:39:27'); +INSERT INTO `t_sys_role_menu` VALUES (127, 1, 41, 0, '2023-02-01 16:39:27', 0, '2023-02-01 16:39:27'); +INSERT INTO `t_sys_role_menu` VALUES (128, 1, 42, 0, '2023-02-01 16:39:27', 0, '2023-02-01 16:39:27'); +INSERT INTO `t_sys_role_menu` VALUES (129, 9, 11, 0, '2023-02-01 16:39:32', 0, '2023-02-01 16:39:32'); +INSERT INTO `t_sys_role_menu` VALUES (130, 9, 12, 0, '2023-02-01 16:39:32', 0, '2023-02-01 16:39:32'); +INSERT INTO `t_sys_role_menu` VALUES (131, 9, 2, 0, '2023-02-01 16:39:32', 0, '2023-02-01 16:39:32'); +INSERT INTO `t_sys_role_menu` VALUES (132, 9, 4, 0, '2023-02-01 16:39:32', 0, '2023-02-01 16:39:32'); +INSERT INTO `t_sys_role_menu` VALUES (133, 9, 8, 0, '2023-02-01 16:39:32', 0, '2023-02-01 16:39:32'); +INSERT INTO `t_sys_role_menu` VALUES (134, 9, 5, 0, '2023-02-01 16:39:32', 0, '2023-02-01 16:39:32'); +INSERT INTO `t_sys_role_menu` VALUES (135, 9, 6, 0, '2023-02-01 16:39:32', 0, '2023-02-01 16:39:32'); +INSERT INTO `t_sys_role_menu` VALUES (136, 9, 3, 0, '2023-02-01 16:39:32', 0, '2023-02-01 16:39:32'); +INSERT INTO `t_sys_role_menu` VALUES (137, 9, 10, 0, '2023-02-01 16:39:32', 0, '2023-02-01 16:39:32'); +INSERT INTO `t_sys_role_menu` VALUES (138, 9, 27, 0, '2023-02-01 16:39:32', 0, '2023-02-01 16:39:32'); +INSERT INTO `t_sys_role_menu` VALUES (139, 9, 41, 0, '2023-02-01 16:39:32', 0, '2023-02-01 16:39:32'); +INSERT INTO `t_sys_role_menu` VALUES (140, 9, 42, 0, '2023-02-01 16:39:32', 0, '2023-02-01 16:39:32'); + +-- ---------------------------- +-- Table structure for t_sys_role_permission +-- ---------------------------- +DROP TABLE IF EXISTS `t_sys_role_permission`; +CREATE TABLE `t_sys_role_permission` ( + `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键ID', + `role_id` int(11) NOT NULL COMMENT '角色ID', + `permission_id` int(11) NOT NULL COMMENT '权限ID', + `create_by` int(11) NOT NULL COMMENT '创建人', + `create_time` datetime NOT NULL COMMENT '创建时间', + `update_by` int(11) NOT NULL COMMENT '修改人', + `update_time` datetime NOT NULL COMMENT '修改时间', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 836 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '系统管理-角色关联权限表' ROW_FORMAT = COMPACT; + +-- ---------------------------- +-- Records of t_sys_role_permission +-- ---------------------------- +INSERT INTO `t_sys_role_permission` VALUES (4, 1, 4, 1, '2022-06-14 15:29:57', 1, '2022-06-14 15:30:04'); +INSERT INTO `t_sys_role_permission` VALUES (824, 1, 411, 1, '2022-07-21 18:07:56', 1, '2022-07-21 18:07:56'); +INSERT INTO `t_sys_role_permission` VALUES (825, 1, 412, 1, '2022-07-21 18:07:56', 1, '2022-07-21 18:07:56'); +INSERT INTO `t_sys_role_permission` VALUES (826, 9, 1, 1, '2022-07-21 18:08:26', 1, '2022-07-21 18:08:26'); +INSERT INTO `t_sys_role_permission` VALUES (827, 9, 2, 1, '2022-07-21 18:08:26', 1, '2022-07-21 18:08:26'); +INSERT INTO `t_sys_role_permission` VALUES (828, 9, 3, 1, '2022-07-21 18:08:26', 1, '2022-07-21 18:08:26'); +INSERT INTO `t_sys_role_permission` VALUES (829, 9, 4, 1, '2022-07-21 18:08:26', 1, '2022-07-21 18:08:26'); +INSERT INTO `t_sys_role_permission` VALUES (830, 9, 5, 1, '2022-07-21 18:08:26', 1, '2022-07-21 18:08:26'); +INSERT INTO `t_sys_role_permission` VALUES (834, 1, 421, 1, '2022-07-22 14:38:12', 1, '2022-07-22 14:38:12'); +INSERT INTO `t_sys_role_permission` VALUES (835, 1, 424, 1, '2022-07-22 14:38:12', 1, '2022-07-22 14:38:12'); + +-- ---------------------------- +-- Table structure for t_sys_user +-- ---------------------------- +DROP TABLE IF EXISTS `t_sys_user`; +CREATE TABLE `t_sys_user` ( + `user_id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键ID', + `username` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '账号', + `password` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '登录密码', + `nickname` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '昵称', + `sex` tinyint(2) NULL DEFAULT 0 COMMENT '性别(0:未知 1:男 2:女)', + `phone` varchar(11) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '手机号码', + `email` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '邮箱', + `avatar_url` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '头像', + `create_by` int(11) NOT NULL COMMENT '创建人', + `create_time` datetime NOT NULL COMMENT '创建时间', + `update_by` int(11) NOT NULL COMMENT '修改人', + `update_time` datetime NOT NULL COMMENT '修改时间', + `is_deleted` tinyint(1) UNSIGNED NOT NULL DEFAULT 0 COMMENT '是否删除(1->是,0->否)', + PRIMARY KEY (`user_id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 5 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '系统管理 - 用户基础信息表' ROW_FORMAT = DYNAMIC; + +-- ---------------------------- +-- Records of t_sys_user +-- ---------------------------- +INSERT INTO `t_sys_user` VALUES (1, 'admin', '3014dcb9ee3639535d5d9301b32c840c', '郑清', 1, '15188888888', 'zhengqingya@it.com', 'https://wpimg.wallstcn.com/f778738c-e4f8-4870-b634-56703b4acafe.gif?imageView2/1/w/80/h/80', 1, '2020-08-22 15:01:51', 0, '2023-02-10 14:30:54', 0); +INSERT INTO `t_sys_user` VALUES (2, 'test', '3014dcb9ee3639535d5d9301b32c840c', '测试号', 1, '', '', 'https://wpimg.wallstcn.com/f778738c-e4f8-4870-b634-56703b4acafe.gif?imageView2/1/w/80/h/80', 1, '2020-08-22 15:01:51', 0, '2023-02-10 15:04:56', 0); + +-- ---------------------------- +-- Table structure for t_sys_user_re_oauth +-- ---------------------------- +DROP TABLE IF EXISTS `t_sys_user_re_oauth`; +CREATE TABLE `t_sys_user_re_oauth` ( + `user_re_oauth_id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键ID', + `user_id` int(11) NOT NULL COMMENT '用户id(关联表`t_sys_user`字段`user_id`)', + `open_id` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '三方id', + `oauth_type` tinyint(4) NOT NULL COMMENT '第三方授权类型', + `create_by` int(11) NOT NULL COMMENT '创建人', + `create_time` datetime NOT NULL COMMENT '创建时间', + `update_by` int(11) NOT NULL COMMENT '修改人', + `update_time` datetime NOT NULL COMMENT '修改时间', + PRIMARY KEY (`user_re_oauth_id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '系统管理 - 用户三方授权表' ROW_FORMAT = DYNAMIC; + +-- ---------------------------- +-- Records of t_sys_user_re_oauth +-- ---------------------------- + +-- ---------------------------- +-- Table structure for t_sys_user_role +-- ---------------------------- +DROP TABLE IF EXISTS `t_sys_user_role`; +CREATE TABLE `t_sys_user_role` ( + `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键ID', + `user_id` int(11) NOT NULL COMMENT '用户ID', + `role_id` int(11) NOT NULL COMMENT '角色ID', + `create_by` int(11) NOT NULL COMMENT '创建人', + `create_time` datetime NOT NULL COMMENT '创建时间', + `update_by` int(11) NOT NULL COMMENT '修改人', + `update_time` datetime NOT NULL COMMENT '修改时间', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 15 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '系统管理-用户角色关联表' ROW_FORMAT = COMPACT; + +-- ---------------------------- +-- Records of t_sys_user_role +-- ---------------------------- +INSERT INTO `t_sys_user_role` VALUES (1, 1, 1, 1, '2020-08-22 15:01:51', 1, '2020-08-22 15:01:51'); +INSERT INTO `t_sys_user_role` VALUES (4, 1, 9, 1, '2022-06-14 09:53:59', 1, '2022-06-14 09:54:01'); +INSERT INTO `t_sys_user_role` VALUES (12, 2, 1, 1, '2022-07-15 17:34:06', 1, '2022-07-15 17:34:06'); + +-- ---------------------------- +-- Table structure for undo_log +-- ---------------------------- +DROP TABLE IF EXISTS `undo_log`; +CREATE TABLE `undo_log` ( + `id` bigint(20) NOT NULL AUTO_INCREMENT, + `branch_id` bigint(20) NOT NULL, + `xid` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL, + `context` varchar(128) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL, + `rollback_info` longblob NOT NULL, + `log_status` int(11) NOT NULL, + `log_created` datetime NOT NULL, + `log_modified` datetime NOT NULL, + PRIMARY KEY (`id`) USING BTREE, + UNIQUE INDEX `ux_undo_log`(`xid`, `branch_id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = DYNAMIC; + +-- ---------------------------- +-- Records of undo_log +-- ---------------------------- + +SET FOREIGN_KEY_CHECKS = 1; diff --git a/smallboot-api/doc/部署/docker-compose.yml b/smallboot-api/doc/部署/docker-compose.yml new file mode 100644 index 0000000..28d7979 --- /dev/null +++ b/smallboot-api/doc/部署/docker-compose.yml @@ -0,0 +1,115 @@ +version: '3' + +# 网桥 -> 方便相互通讯 +networks: + smallboot: + ipam: + driver: default + config: + - subnet: "172.28.0.0/24" + +services: + mysql: + image: registry.cn-hangzhou.aliyuncs.com/zhengqing/mysql:5.7 # 原镜像`mysql:5.7` + container_name: mysql5.7 # 容器名 + restart: unless-stopped # 指定容器退出后的重启策略为始终重启,但是不考虑在Docker守护进程启动时就已经停止了的容器 + volumes: # 数据卷挂载路径设置,将本机目录映射到容器目录 + - "./mysql5.7/my.cnf:/etc/mysql/my.cnf" + - "./mysql5.7/data:/var/lib/mysql" + # - "./mysql5.7/conf.d:/etc/mysql/conf.d" + - "./mysql5.7/log/mysql/error.log:/var/log/mysql/error.log" + - "./mysql5.7/docker-entrypoint-initdb.d:/docker-entrypoint-initdb.d" # 可执行初始化sql脚本的目录 -- tips:`/var/lib/mysql`目录下无数据的时候才会执行(即第一次启动的时候才会执行) + environment: # 设置环境变量,相当于docker run命令中的-e + TZ: Asia/Shanghai + LANG: en_US.UTF-8 + MYSQL_ROOT_PASSWORD: root # 设置root用户密码 + MYSQL_DATABASE: demo # 初始化的数据库名称 + ports: # 映射端口 + - "3306:3306" + hostname: mysql + networks: + smallboot: + ipv4_address: 172.28.0.10 + + # ---------------------------------------------------------------------------------------------------------------------------- + redis: + image: registry.cn-hangzhou.aliyuncs.com/zhengqing/redis:7.0.5 # 镜像'redis:7.0.5' + container_name: redis # 容器名为'redis' + restart: unless-stopped # 指定容器退出后的重启策略为始终重启,但是不考虑在Docker守护进程启动时就已经停止了的容器 + command: redis-server /etc/redis/redis.conf --requirepass 123456 --appendonly no # 启动redis服务并添加密码为:123456,默认不开启redis-aof方式持久化配置 + # command: redis-server --requirepass 123456 --appendonly yes # 启动redis服务并添加密码为:123456,并开启redis持久化配置 + environment: # 设置环境变量,相当于docker run命令中的-e + TZ: Asia/Shanghai + LANG: en_US.UTF-8 + volumes: # 数据卷挂载路径设置,将本机目录映射到容器目录 + - "./redis/data:/data" + - "./redis/config/redis.conf:/etc/redis/redis.conf" # `redis.conf`文件内容`http://download.redis.io/redis-stable/redis.conf` + ports: # 映射端口 + - "9379:6379" + networks: + smallboot: + ipv4_address: 172.28.0.11 + + # ---------------------------------------------------------------------------------------------------------------------------- + minio: + image: registry.cn-hangzhou.aliyuncs.com/zhengqing/minio:RELEASE.2023-01-02T09-40-09Z # 原镜像`minio/minio:RELEASE.2023-01-02T09-40-09Z` + container_name: minio # 容器名为'minio' + restart: unless-stopped # 指定容器退出后的重启策略为始终重启,但是不考虑在Docker守护进程启动时就已经停止了的容器 + volumes: # 数据卷挂载路径设置,将本机目录映射到容器目录 + - "./minio/data:/mnt/data" # 数据 + - "./minio/config/config.env:/etc/config.env" # 配置文件 + environment: # 设置环境变量,相当于docker run命令中的-e + TZ: Asia/Shanghai + LANG: en_US.UTF-8 + MINIO_CONFIG_ENV_FILE: "/etc/config.env" + command: server --console-address ":9090" + ports: # 映射端口 + - "9001:9000" + - "9090:9090" + networks: + smallboot: + ipv4_address: 172.28.0.12 + + # ---------------------------------------------------------------------------------------------------------------------------- + api: + image: registry.cn-hangzhou.aliyuncs.com/zhengqingya/smallboot-api:prod + container_name: api + restart: unless-stopped + volumes: + - "./smallboot/logs:/logs" + environment: + TZ: Asia/Shanghai + LANG: en_US.UTF-8 + server.port: 888 + JAVA_OPTS: -XX:+UseG1GC + spring.profiles.active: prod + # smallboot.ip: 127.0.0.1 + ports: + - "888:888" + depends_on: + - mysql + - redis + - minio + networks: + smallboot: + ipv4_address: 172.28.0.13 + mem_limit: 3000m + + # ---------------------------------------------------------------------------------------------------------------------------- + web: + image: registry.cn-hangzhou.aliyuncs.com/zhengqingya/smallboot-web:prod + container_name: web + restart: always + environment: + TZ: Asia/Shanghai + LANG: en_US.UTF-8 + ports: + - "80:80" + depends_on: + - mysql + - redis + - minio + - api + networks: + smallboot: + ipv4_address: 172.28.0.15 \ No newline at end of file diff --git a/smallboot-api/doc/部署/minio/config/config.env b/smallboot-api/doc/部署/minio/config/config.env new file mode 100644 index 0000000..164eb14 --- /dev/null +++ b/smallboot-api/doc/部署/minio/config/config.env @@ -0,0 +1,18 @@ +# MINIO_ROOT_USER and MINIO_ROOT_PASSWORD sets the root account for the MinIO server. +# This user has unrestricted permissions to perform S3 and administrative API operations on any resource in the deployment. +# Omit to use the default values 'minioadmin:minioadmin'. +# MinIO recommends setting non-default values as a best practice, regardless of environment + +MINIO_ROOT_USER=admin +MINIO_ROOT_PASSWORD=admin123 + +# MINIO_VOLUMES sets the storage volume or path to use for the MinIO server. + +MINIO_VOLUMES="/mnt/data" + +# MINIO_SERVER_URL sets the hostname of the local machine for use with the MinIO Server +# MinIO assumes your network control plane can correctly resolve this hostname to the local machine + +# Uncomment the following line and replace the value with the correct hostname for the local machine. + +#MINIO_SERVER_URL="http://minio.example.net" \ No newline at end of file diff --git a/smallboot-api/doc/部署/mysql5.7/log/mysql/error.log b/smallboot-api/doc/部署/mysql5.7/log/mysql/error.log new file mode 100644 index 0000000..e69de29 diff --git a/smallboot-api/doc/部署/mysql5.7/my.cnf b/smallboot-api/doc/部署/mysql5.7/my.cnf new file mode 100644 index 0000000..4fe5515 --- /dev/null +++ b/smallboot-api/doc/部署/mysql5.7/my.cnf @@ -0,0 +1,43 @@ +[mysqld] +user=mysql # MySQL启动用户 +default-storage-engine=INNODB # 创建新表时将使用的默认存储引擎 +character-set-server=utf8mb4 # 设置mysql服务端默认字符集 +pid-file = /var/run/mysqld/mysqld.pid # pid文件所在目录 +socket = /var/run/mysqld/mysqld.sock # 用于本地连接的socket套接字 +datadir = /var/lib/mysql # 数据文件存放的目录 +#log-error = /var/log/mysql/error.log +#bind-address = 127.0.0.1 # MySQL绑定IP +# Disabling symbolic-links is recommended to prevent assorted security risks +symbolic-links=0 +sql_mode=STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION # 定义mysql应该支持的sql语法,数据校验等! + +# 允许最大连接数 +max_connections=200 + +# ================= ↓↓↓ mysql主从同步配置start ↓↓↓ ================= + +# 同一局域网内注意要唯一 +server-id=3306 +# 开启二进制日志功能 & 日志位置存放位置`/var/lib/mysql` +#log-bin=mysql-bin +log-bin=/var/lib/mysql/mysql-bin +# binlog格式 +# 1. STATEMENT:基于SQL语句的模式,binlog 数据量小,但是某些语句和函数在复制过程可能导致数据不一致甚至出错; +# 2. MIXED:混合模式,根据语句来选用是 STATEMENT 还是 ROW 模式; +# 3. ROW:基于行的模式,记录的是行的完整变化。安全,但 binlog 会比其他两种模式大很多; +binlog_format=ROW +# FULL:binlog记录每一行的完整变更 MINIMAL:只记录影响后的行 +binlog_row_image=FULL +# 日志文件大小 +# max_binlog_size=1G +max_binlog_size=100M +# 定义清除过期日志的时间(这里设置为7天) +expire_logs_days=7 + +# ================= ↑↑↑ mysql主从同步配置end ↑↑↑ ================= + +[mysql] +default-character-set=utf8mb4 + +[client] +default-character-set=utf8mb4 # 设置mysql客户端默认字符集 diff --git a/smallboot-api/doc/部署/redis/config/redis.conf b/smallboot-api/doc/部署/redis/config/redis.conf new file mode 100644 index 0000000..b0ee47c --- /dev/null +++ b/smallboot-api/doc/部署/redis/config/redis.conf @@ -0,0 +1,2277 @@ +# Redis configuration file example. +# +# Note that in order to read the configuration file, Redis must be +# started with the file path as first argument: +# +# ./redis-server /path/to/redis.conf + +# Note on units: when memory size is needed, it is possible to specify +# it in the usual form of 1k 5GB 4M and so forth: +# +# 1k => 1000 bytes +# 1kb => 1024 bytes +# 1m => 1000000 bytes +# 1mb => 1024*1024 bytes +# 1g => 1000000000 bytes +# 1gb => 1024*1024*1024 bytes +# +# units are case insensitive so 1GB 1Gb 1gB are all the same. + +################################## INCLUDES ################################### + +# Include one or more other config files here. This is useful if you +# have a standard template that goes to all Redis servers but also need +# to customize a few per-server settings. Include files can include +# other files, so use this wisely. +# +# Note that option "include" won't be rewritten by command "CONFIG REWRITE" +# from admin or Redis Sentinel. Since Redis always uses the last processed +# line as value of a configuration directive, you'd better put includes +# at the beginning of this file to avoid overwriting config change at runtime. +# +# If instead you are interested in using includes to override configuration +# options, it is better to use include as the last line. +# +# Included paths may contain wildcards. All files matching the wildcards will +# be included in alphabetical order. +# Note that if an include path contains a wildcards but no files match it when +# the server is started, the include statement will be ignored and no error will +# be emitted. It is safe, therefore, to include wildcard files from empty +# directories. +# +# include /path/to/local.conf +# include /path/to/other.conf +# include /path/to/fragments/*.conf +# + +################################## MODULES ##################################### + +# Load modules at startup. If the server is not able to load modules +# it will abort. It is possible to use multiple loadmodule directives. +# +# loadmodule /path/to/my_module.so +# loadmodule /path/to/other_module.so + +################################## NETWORK ##################################### + +# By default, if no "bind" configuration directive is specified, Redis listens +# for connections from all available network interfaces on the host machine. +# It is possible to listen to just one or multiple selected interfaces using +# the "bind" configuration directive, followed by one or more IP addresses. +# Each address can be prefixed by "-", which means that redis will not fail to +# start if the address is not available. Being not available only refers to +# addresses that does not correspond to any network interface. Addresses that +# are already in use will always fail, and unsupported protocols will always BE +# silently skipped. +# +# Examples: +# +# bind 192.168.1.100 10.0.0.1 # listens on two specific IPv4 addresses +# bind 127.0.0.1 ::1 # listens on loopback IPv4 and IPv6 +# bind * -::* # like the default, all available interfaces +# +# ~~~ WARNING ~~~ If the computer running Redis is directly exposed to the +# internet, binding to all the interfaces is dangerous and will expose the +# instance to everybody on the internet. So by default we uncomment the +# following bind directive, that will force Redis to listen only on the +# IPv4 and IPv6 (if available) loopback interface addresses (this means Redis +# will only be able to accept client connections from the same host that it is +# running on). +# +# IF YOU ARE SURE YOU WANT YOUR INSTANCE TO LISTEN TO ALL THE INTERFACES +# COMMENT OUT THE FOLLOWING LINE. +# +# You will also need to set a password unless you explicitly disable protected +# mode. +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# 注释允许外部访问redis +# bind 127.0.0.1 -::1 + +# By default, outgoing connections (from replica to master, from Sentinel to +# instances, cluster bus, etc.) are not bound to a specific local address. In +# most cases, this means the operating system will handle that based on routing +# and the interface through which the connection goes out. +# +# Using bind-source-addr it is possible to configure a specific address to bind +# to, which may also affect how the connection gets routed. +# +# Example: +# +# bind-source-addr 10.0.0.1 + +# Protected mode is a layer of security protection, in order to avoid that +# Redis instances left open on the internet are accessed and exploited. +# +# When protected mode is on and the default user has no password, the server +# only accepts local connections from the IPv4 address (127.0.0.1), IPv6 address +# (::1) or Unix domain sockets. +# +# By default protected mode is enabled. You should disable it only if +# you are sure you want clients from other hosts to connect to Redis +# even if no authentication is configured. +protected-mode yes + +# Redis uses default hardened security configuration directives to reduce the +# attack surface on innocent users. Therefore, several sensitive configuration +# directives are immutable, and some potentially-dangerous commands are blocked. +# +# Configuration directives that control files that Redis writes to (e.g., 'dir' +# and 'dbfilename') and that aren't usually modified during runtime +# are protected by making them immutable. +# +# Commands that can increase the attack surface of Redis and that aren't usually +# called by users are blocked by default. +# +# These can be exposed to either all connections or just local ones by setting +# each of the configs listed below to either of these values: +# +# no - Block for any connection (remain immutable) +# yes - Allow for any connection (no protection) +# local - Allow only for local connections. Ones originating from the +# IPv4 address (127.0.0.1), IPv6 address (::1) or Unix domain sockets. +# +# enable-protected-configs no +# enable-debug-command no +# enable-module-command no + +# Accept connections on the specified port, default is 6379 (IANA #815344). +# If port 0 is specified Redis will not listen on a TCP socket. +port 6379 + +# TCP listen() backlog. +# +# In high requests-per-second environments you need a high backlog in order +# to avoid slow clients connection issues. Note that the Linux kernel +# will silently truncate it to the value of /proc/sys/net/core/somaxconn so +# make sure to raise both the value of somaxconn and tcp_max_syn_backlog +# in order to get the desired effect. +tcp-backlog 511 + +# Unix socket. +# +# Specify the path for the Unix socket that will be used to listen for +# incoming connections. There is no default, so Redis will not listen +# on a unix socket when not specified. +# +# unixsocket /run/redis.sock +# unixsocketperm 700 + +# Close the connection after a client is idle for N seconds (0 to disable) +timeout 0 + +# TCP keepalive. +# +# If non-zero, use SO_KEEPALIVE to send TCP ACKs to clients in absence +# of communication. This is useful for two reasons: +# +# 1) Detect dead peers. +# 2) Force network equipment in the middle to consider the connection to be +# alive. +# +# On Linux, the specified value (in seconds) is the period used to send ACKs. +# Note that to close the connection the double of the time is needed. +# On other kernels the period depends on the kernel configuration. +# +# A reasonable value for this option is 300 seconds, which is the new +# Redis default starting with Redis 3.2.1. +tcp-keepalive 300 + +# Apply OS-specific mechanism to mark the listening socket with the specified +# ID, to support advanced routing and filtering capabilities. +# +# On Linux, the ID represents a connection mark. +# On FreeBSD, the ID represents a socket cookie ID. +# On OpenBSD, the ID represents a route table ID. +# +# The default value is 0, which implies no marking is required. +# socket-mark-id 0 + +################################# TLS/SSL ##################################### + +# By default, TLS/SSL is disabled. To enable it, the "tls-port" configuration +# directive can be used to define TLS-listening ports. To enable TLS on the +# default port, use: +# +# port 0 +# tls-port 6379 + +# Configure a X.509 certificate and private key to use for authenticating the +# server to connected clients, masters or cluster peers. These files should be +# PEM formatted. +# +# tls-cert-file redis.crt +# tls-key-file redis.key +# +# If the key file is encrypted using a passphrase, it can be included here +# as well. +# +# tls-key-file-pass secret + +# Normally Redis uses the same certificate for both server functions (accepting +# connections) and client functions (replicating from a master, establishing +# cluster bus connections, etc.). +# +# Sometimes certificates are issued with attributes that designate them as +# client-only or server-only certificates. In that case it may be desired to use +# different certificates for incoming (server) and outgoing (client) +# connections. To do that, use the following directives: +# +# tls-client-cert-file client.crt +# tls-client-key-file client.key +# +# If the key file is encrypted using a passphrase, it can be included here +# as well. +# +# tls-client-key-file-pass secret + +# Configure a DH parameters file to enable Diffie-Hellman (DH) key exchange, +# required by older versions of OpenSSL (<3.0). Newer versions do not require +# this configuration and recommend against it. +# +# tls-dh-params-file redis.dh + +# Configure a CA certificate(s) bundle or directory to authenticate TLS/SSL +# clients and peers. Redis requires an explicit configuration of at least one +# of these, and will not implicitly use the system wide configuration. +# +# tls-ca-cert-file ca.crt +# tls-ca-cert-dir /etc/ssl/certs + +# By default, clients (including replica servers) on a TLS port are required +# to authenticate using valid client side certificates. +# +# If "no" is specified, client certificates are not required and not accepted. +# If "optional" is specified, client certificates are accepted and must be +# valid if provided, but are not required. +# +# tls-auth-clients no +# tls-auth-clients optional + +# By default, a Redis replica does not attempt to establish a TLS connection +# with its master. +# +# Use the following directive to enable TLS on replication links. +# +# tls-replication yes + +# By default, the Redis Cluster bus uses a plain TCP connection. To enable +# TLS for the bus protocol, use the following directive: +# +# tls-cluster yes + +# By default, only TLSv1.2 and TLSv1.3 are enabled and it is highly recommended +# that older formally deprecated versions are kept disabled to reduce the attack surface. +# You can explicitly specify TLS versions to support. +# Allowed values are case insensitive and include "TLSv1", "TLSv1.1", "TLSv1.2", +# "TLSv1.3" (OpenSSL >= 1.1.1) or any combination. +# To enable only TLSv1.2 and TLSv1.3, use: +# +# tls-protocols "TLSv1.2 TLSv1.3" + +# Configure allowed ciphers. See the ciphers(1ssl) manpage for more information +# about the syntax of this string. +# +# Note: this configuration applies only to <= TLSv1.2. +# +# tls-ciphers DEFAULT:!MEDIUM + +# Configure allowed TLSv1.3 ciphersuites. See the ciphers(1ssl) manpage for more +# information about the syntax of this string, and specifically for TLSv1.3 +# ciphersuites. +# +# tls-ciphersuites TLS_CHACHA20_POLY1305_SHA256 + +# When choosing a cipher, use the server's preference instead of the client +# preference. By default, the server follows the client's preference. +# +# tls-prefer-server-ciphers yes + +# By default, TLS session caching is enabled to allow faster and less expensive +# reconnections by clients that support it. Use the following directive to disable +# caching. +# +# tls-session-caching no + +# Change the default number of TLS sessions cached. A zero value sets the cache +# to unlimited size. The default size is 20480. +# +# tls-session-cache-size 5000 + +# Change the default timeout of cached TLS sessions. The default timeout is 300 +# seconds. +# +# tls-session-cache-timeout 60 + +################################# GENERAL ##################################### + +# By default Redis does not run as a daemon. Use 'yes' if you need it. +# Note that Redis will write a pid file in /var/run/redis.pid when daemonized. +# When Redis is supervised by upstart or systemd, this parameter has no impact. +daemonize no + +# If you run Redis from upstart or systemd, Redis can interact with your +# supervision tree. Options: +# supervised no - no supervision interaction +# supervised upstart - signal upstart by putting Redis into SIGSTOP mode +# requires "expect stop" in your upstart job config +# supervised systemd - signal systemd by writing READY=1 to $NOTIFY_SOCKET +# on startup, and updating Redis status on a regular +# basis. +# supervised auto - detect upstart or systemd method based on +# UPSTART_JOB or NOTIFY_SOCKET environment variables +# Note: these supervision methods only signal "process is ready." +# They do not enable continuous pings back to your supervisor. +# +# The default is "no". To run under upstart/systemd, you can simply uncomment +# the line below: +# +# supervised auto + +# If a pid file is specified, Redis writes it where specified at startup +# and removes it at exit. +# +# When the server runs non daemonized, no pid file is created if none is +# specified in the configuration. When the server is daemonized, the pid file +# is used even if not specified, defaulting to "/var/run/redis.pid". +# +# Creating a pid file is best effort: if Redis is not able to create it +# nothing bad happens, the server will start and run normally. +# +# Note that on modern Linux systems "/run/redis.pid" is more conforming +# and should be used instead. +pidfile /var/run/redis_6379.pid + +# Specify the server verbosity level. +# This can be one of: +# debug (a lot of information, useful for development/testing) +# verbose (many rarely useful info, but not a mess like the debug level) +# notice (moderately verbose, what you want in production probably) +# warning (only very important / critical messages are logged) +loglevel notice + +# Specify the log file name. Also the empty string can be used to force +# Redis to log on the standard output. Note that if you use standard +# output for logging but daemonize, logs will be sent to /dev/null +logfile "" + +# To enable logging to the system logger, just set 'syslog-enabled' to yes, +# and optionally update the other syslog parameters to suit your needs. +# syslog-enabled no + +# Specify the syslog identity. +# syslog-ident redis + +# Specify the syslog facility. Must be USER or between LOCAL0-LOCAL7. +# syslog-facility local0 + +# To disable the built in crash log, which will possibly produce cleaner core +# dumps when they are needed, uncomment the following: +# +# crash-log-enabled no + +# To disable the fast memory check that's run as part of the crash log, which +# will possibly let redis terminate sooner, uncomment the following: +# +# crash-memcheck-enabled no + +# Set the number of databases. The default database is DB 0, you can select +# a different one on a per-connection basis using SELECT where +# dbid is a number between 0 and 'databases'-1 +databases 16 + +# By default Redis shows an ASCII art logo only when started to log to the +# standard output and if the standard output is a TTY and syslog logging is +# disabled. Basically this means that normally a logo is displayed only in +# interactive sessions. +# +# However it is possible to force the pre-4.0 behavior and always show a +# ASCII art logo in startup logs by setting the following option to yes. +always-show-logo no + +# By default, Redis modifies the process title (as seen in 'top' and 'ps') to +# provide some runtime information. It is possible to disable this and leave +# the process name as executed by setting the following to no. +set-proc-title yes + +# When changing the process title, Redis uses the following template to construct +# the modified title. +# +# Template variables are specified in curly brackets. The following variables are +# supported: +# +# {title} Name of process as executed if parent, or type of child process. +# {listen-addr} Bind address or '*' followed by TCP or TLS port listening on, or +# Unix socket if only that's available. +# {server-mode} Special mode, i.e. "[sentinel]" or "[cluster]". +# {port} TCP port listening on, or 0. +# {tls-port} TLS port listening on, or 0. +# {unixsocket} Unix domain socket listening on, or "". +# {config-file} Name of configuration file used. +# +proc-title-template "{title} {listen-addr} {server-mode}" + +################################ SNAPSHOTTING ################################ + +# Save the DB to disk. +# +# save [ ...] +# +# Redis will save the DB if the given number of seconds elapsed and it +# surpassed the given number of write operations against the DB. +# +# Snapshotting can be completely disabled with a single empty string argument +# as in following example: +# +# save "" +# +# Unless specified otherwise, by default Redis will save the DB: +# * After 3600 seconds (an hour) if at least 1 change was performed +# * After 300 seconds (5 minutes) if at least 100 changes were performed +# * After 60 seconds if at least 10000 changes were performed +# +# You can set these explicitly by uncommenting the following line. +# +# save 3600 1 300 100 60 10000 + +# By default Redis will stop accepting writes if RDB snapshots are enabled +# (at least one save point) and the latest background save failed. +# This will make the user aware (in a hard way) that data is not persisting +# on disk properly, otherwise chances are that no one will notice and some +# disaster will happen. +# +# If the background saving process will start working again Redis will +# automatically allow writes again. +# +# However if you have setup your proper monitoring of the Redis server +# and persistence, you may want to disable this feature so that Redis will +# continue to work as usual even if there are problems with disk, +# permissions, and so forth. +stop-writes-on-bgsave-error yes + +# Compress string objects using LZF when dump .rdb databases? +# By default compression is enabled as it's almost always a win. +# If you want to save some CPU in the saving child set it to 'no' but +# the dataset will likely be bigger if you have compressible values or keys. +rdbcompression yes + +# Since version 5 of RDB a CRC64 checksum is placed at the end of the file. +# This makes the format more resistant to corruption but there is a performance +# hit to pay (around 10%) when saving and loading RDB files, so you can disable it +# for maximum performances. +# +# RDB files created with checksum disabled have a checksum of zero that will +# tell the loading code to skip the check. +rdbchecksum yes + +# Enables or disables full sanitization checks for ziplist and listpack etc when +# loading an RDB or RESTORE payload. This reduces the chances of a assertion or +# crash later on while processing commands. +# Options: +# no - Never perform full sanitization +# yes - Always perform full sanitization +# clients - Perform full sanitization only for user connections. +# Excludes: RDB files, RESTORE commands received from the master +# connection, and client connections which have the +# skip-sanitize-payload ACL flag. +# The default should be 'clients' but since it currently affects cluster +# resharding via MIGRATE, it is temporarily set to 'no' by default. +# +# sanitize-dump-payload no + +# The filename where to dump the DB +dbfilename dump.rdb + +# Remove RDB files used by replication in instances without persistence +# enabled. By default this option is disabled, however there are environments +# where for regulations or other security concerns, RDB files persisted on +# disk by masters in order to feed replicas, or stored on disk by replicas +# in order to load them for the initial synchronization, should be deleted +# ASAP. Note that this option ONLY WORKS in instances that have both AOF +# and RDB persistence disabled, otherwise is completely ignored. +# +# An alternative (and sometimes better) way to obtain the same effect is +# to use diskless replication on both master and replicas instances. However +# in the case of replicas, diskless is not always an option. +rdb-del-sync-files no + +# The working directory. +# +# The DB will be written inside this directory, with the filename specified +# above using the 'dbfilename' configuration directive. +# +# The Append Only File will also be created inside this directory. +# +# Note that you must specify a directory here, not a file name. +dir ./ + +################################# REPLICATION ################################# + +# Master-Replica replication. Use replicaof to make a Redis instance a copy of +# another Redis server. A few things to understand ASAP about Redis replication. +# +# +------------------+ +---------------+ +# | Master | ---> | Replica | +# | (receive writes) | | (exact copy) | +# +------------------+ +---------------+ +# +# 1) Redis replication is asynchronous, but you can configure a master to +# stop accepting writes if it appears to be not connected with at least +# a given number of replicas. +# 2) Redis replicas are able to perform a partial resynchronization with the +# master if the replication link is lost for a relatively small amount of +# time. You may want to configure the replication backlog size (see the next +# sections of this file) with a sensible value depending on your needs. +# 3) Replication is automatic and does not need user intervention. After a +# network partition replicas automatically try to reconnect to masters +# and resynchronize with them. +# +# replicaof + +# If the master is password protected (using the "requirepass" configuration +# directive below) it is possible to tell the replica to authenticate before +# starting the replication synchronization process, otherwise the master will +# refuse the replica request. +# +# masterauth +# +# However this is not enough if you are using Redis ACLs (for Redis version +# 6 or greater), and the default user is not capable of running the PSYNC +# command and/or other commands needed for replication. In this case it's +# better to configure a special user to use with replication, and specify the +# masteruser configuration as such: +# +# masteruser +# +# When masteruser is specified, the replica will authenticate against its +# master using the new AUTH form: AUTH . + +# When a replica loses its connection with the master, or when the replication +# is still in progress, the replica can act in two different ways: +# +# 1) if replica-serve-stale-data is set to 'yes' (the default) the replica will +# still reply to client requests, possibly with out of date data, or the +# data set may just be empty if this is the first synchronization. +# +# 2) If replica-serve-stale-data is set to 'no' the replica will reply with error +# "MASTERDOWN Link with MASTER is down and replica-serve-stale-data is set to 'no'" +# to all data access commands, excluding commands such as: +# INFO, REPLICAOF, AUTH, SHUTDOWN, REPLCONF, ROLE, CONFIG, SUBSCRIBE, +# UNSUBSCRIBE, PSUBSCRIBE, PUNSUBSCRIBE, PUBLISH, PUBSUB, COMMAND, POST, +# HOST and LATENCY. +# +replica-serve-stale-data yes + +# You can configure a replica instance to accept writes or not. Writing against +# a replica instance may be useful to store some ephemeral data (because data +# written on a replica will be easily deleted after resync with the master) but +# may also cause problems if clients are writing to it because of a +# misconfiguration. +# +# Since Redis 2.6 by default replicas are read-only. +# +# Note: read only replicas are not designed to be exposed to untrusted clients +# on the internet. It's just a protection layer against misuse of the instance. +# Still a read only replica exports by default all the administrative commands +# such as CONFIG, DEBUG, and so forth. To a limited extent you can improve +# security of read only replicas using 'rename-command' to shadow all the +# administrative / dangerous commands. +replica-read-only yes + +# Replication SYNC strategy: disk or socket. +# +# New replicas and reconnecting replicas that are not able to continue the +# replication process just receiving differences, need to do what is called a +# "full synchronization". An RDB file is transmitted from the master to the +# replicas. +# +# The transmission can happen in two different ways: +# +# 1) Disk-backed: The Redis master creates a new process that writes the RDB +# file on disk. Later the file is transferred by the parent +# process to the replicas incrementally. +# 2) Diskless: The Redis master creates a new process that directly writes the +# RDB file to replica sockets, without touching the disk at all. +# +# With disk-backed replication, while the RDB file is generated, more replicas +# can be queued and served with the RDB file as soon as the current child +# producing the RDB file finishes its work. With diskless replication instead +# once the transfer starts, new replicas arriving will be queued and a new +# transfer will start when the current one terminates. +# +# When diskless replication is used, the master waits a configurable amount of +# time (in seconds) before starting the transfer in the hope that multiple +# replicas will arrive and the transfer can be parallelized. +# +# With slow disks and fast (large bandwidth) networks, diskless replication +# works better. +repl-diskless-sync yes + +# When diskless replication is enabled, it is possible to configure the delay +# the server waits in order to spawn the child that transfers the RDB via socket +# to the replicas. +# +# This is important since once the transfer starts, it is not possible to serve +# new replicas arriving, that will be queued for the next RDB transfer, so the +# server waits a delay in order to let more replicas arrive. +# +# The delay is specified in seconds, and by default is 5 seconds. To disable +# it entirely just set it to 0 seconds and the transfer will start ASAP. +repl-diskless-sync-delay 5 + +# When diskless replication is enabled with a delay, it is possible to let +# the replication start before the maximum delay is reached if the maximum +# number of replicas expected have connected. Default of 0 means that the +# maximum is not defined and Redis will wait the full delay. +repl-diskless-sync-max-replicas 0 + +# ----------------------------------------------------------------------------- +# WARNING: RDB diskless load is experimental. Since in this setup the replica +# does not immediately store an RDB on disk, it may cause data loss during +# failovers. RDB diskless load + Redis modules not handling I/O reads may also +# cause Redis to abort in case of I/O errors during the initial synchronization +# stage with the master. Use only if you know what you are doing. +# ----------------------------------------------------------------------------- +# +# Replica can load the RDB it reads from the replication link directly from the +# socket, or store the RDB to a file and read that file after it was completely +# received from the master. +# +# In many cases the disk is slower than the network, and storing and loading +# the RDB file may increase replication time (and even increase the master's +# Copy on Write memory and replica buffers). +# However, parsing the RDB file directly from the socket may mean that we have +# to flush the contents of the current database before the full rdb was +# received. For this reason we have the following options: +# +# "disabled" - Don't use diskless load (store the rdb file to the disk first) +# "on-empty-db" - Use diskless load only when it is completely safe. +# "swapdb" - Keep current db contents in RAM while parsing the data directly +# from the socket. Replicas in this mode can keep serving current +# data set while replication is in progress, except for cases where +# they can't recognize master as having a data set from same +# replication history. +# Note that this requires sufficient memory, if you don't have it, +# you risk an OOM kill. +repl-diskless-load disabled + +# Master send PINGs to its replicas in a predefined interval. It's possible to +# change this interval with the repl_ping_replica_period option. The default +# value is 10 seconds. +# +# repl-ping-replica-period 10 + +# The following option sets the replication timeout for: +# +# 1) Bulk transfer I/O during SYNC, from the point of view of replica. +# 2) Master timeout from the point of view of replicas (data, pings). +# 3) Replica timeout from the point of view of masters (REPLCONF ACK pings). +# +# It is important to make sure that this value is greater than the value +# specified for repl-ping-replica-period otherwise a timeout will be detected +# every time there is low traffic between the master and the replica. The default +# value is 60 seconds. +# +# repl-timeout 60 + +# Disable TCP_NODELAY on the replica socket after SYNC? +# +# If you select "yes" Redis will use a smaller number of TCP packets and +# less bandwidth to send data to replicas. But this can add a delay for +# the data to appear on the replica side, up to 40 milliseconds with +# Linux kernels using a default configuration. +# +# If you select "no" the delay for data to appear on the replica side will +# be reduced but more bandwidth will be used for replication. +# +# By default we optimize for low latency, but in very high traffic conditions +# or when the master and replicas are many hops away, turning this to "yes" may +# be a good idea. +repl-disable-tcp-nodelay no + +# Set the replication backlog size. The backlog is a buffer that accumulates +# replica data when replicas are disconnected for some time, so that when a +# replica wants to reconnect again, often a full resync is not needed, but a +# partial resync is enough, just passing the portion of data the replica +# missed while disconnected. +# +# The bigger the replication backlog, the longer the replica can endure the +# disconnect and later be able to perform a partial resynchronization. +# +# The backlog is only allocated if there is at least one replica connected. +# +# repl-backlog-size 1mb + +# After a master has no connected replicas for some time, the backlog will be +# freed. The following option configures the amount of seconds that need to +# elapse, starting from the time the last replica disconnected, for the backlog +# buffer to be freed. +# +# Note that replicas never free the backlog for timeout, since they may be +# promoted to masters later, and should be able to correctly "partially +# resynchronize" with other replicas: hence they should always accumulate backlog. +# +# A value of 0 means to never release the backlog. +# +# repl-backlog-ttl 3600 + +# The replica priority is an integer number published by Redis in the INFO +# output. It is used by Redis Sentinel in order to select a replica to promote +# into a master if the master is no longer working correctly. +# +# A replica with a low priority number is considered better for promotion, so +# for instance if there are three replicas with priority 10, 100, 25 Sentinel +# will pick the one with priority 10, that is the lowest. +# +# However a special priority of 0 marks the replica as not able to perform the +# role of master, so a replica with priority of 0 will never be selected by +# Redis Sentinel for promotion. +# +# By default the priority is 100. +replica-priority 100 + +# The propagation error behavior controls how Redis will behave when it is +# unable to handle a command being processed in the replication stream from a master +# or processed while reading from an AOF file. Errors that occur during propagation +# are unexpected, and can cause data inconsistency. However, there are edge cases +# in earlier versions of Redis where it was possible for the server to replicate or persist +# commands that would fail on future versions. For this reason the default behavior +# is to ignore such errors and continue processing commands. +# +# If an application wants to ensure there is no data divergence, this configuration +# should be set to 'panic' instead. The value can also be set to 'panic-on-replicas' +# to only panic when a replica encounters an error on the replication stream. One of +# these two panic values will become the default value in the future once there are +# sufficient safety mechanisms in place to prevent false positive crashes. +# +# propagation-error-behavior ignore + +# Replica ignore disk write errors controls the behavior of a replica when it is +# unable to persist a write command received from its master to disk. By default, +# this configuration is set to 'no' and will crash the replica in this condition. +# It is not recommended to change this default, however in order to be compatible +# with older versions of Redis this config can be toggled to 'yes' which will just +# log a warning and execute the write command it got from the master. +# +# replica-ignore-disk-write-errors no + +# ----------------------------------------------------------------------------- +# By default, Redis Sentinel includes all replicas in its reports. A replica +# can be excluded from Redis Sentinel's announcements. An unannounced replica +# will be ignored by the 'sentinel replicas ' command and won't be +# exposed to Redis Sentinel's clients. +# +# This option does not change the behavior of replica-priority. Even with +# replica-announced set to 'no', the replica can be promoted to master. To +# prevent this behavior, set replica-priority to 0. +# +# replica-announced yes + +# It is possible for a master to stop accepting writes if there are less than +# N replicas connected, having a lag less or equal than M seconds. +# +# The N replicas need to be in "online" state. +# +# The lag in seconds, that must be <= the specified value, is calculated from +# the last ping received from the replica, that is usually sent every second. +# +# This option does not GUARANTEE that N replicas will accept the write, but +# will limit the window of exposure for lost writes in case not enough replicas +# are available, to the specified number of seconds. +# +# For example to require at least 3 replicas with a lag <= 10 seconds use: +# +# min-replicas-to-write 3 +# min-replicas-max-lag 10 +# +# Setting one or the other to 0 disables the feature. +# +# By default min-replicas-to-write is set to 0 (feature disabled) and +# min-replicas-max-lag is set to 10. + +# A Redis master is able to list the address and port of the attached +# replicas in different ways. For example the "INFO replication" section +# offers this information, which is used, among other tools, by +# Redis Sentinel in order to discover replica instances. +# Another place where this info is available is in the output of the +# "ROLE" command of a master. +# +# The listed IP address and port normally reported by a replica is +# obtained in the following way: +# +# IP: The address is auto detected by checking the peer address +# of the socket used by the replica to connect with the master. +# +# Port: The port is communicated by the replica during the replication +# handshake, and is normally the port that the replica is using to +# listen for connections. +# +# However when port forwarding or Network Address Translation (NAT) is +# used, the replica may actually be reachable via different IP and port +# pairs. The following two options can be used by a replica in order to +# report to its master a specific set of IP and port, so that both INFO +# and ROLE will report those values. +# +# There is no need to use both the options if you need to override just +# the port or the IP address. +# +# replica-announce-ip 5.5.5.5 +# replica-announce-port 1234 + +############################### KEYS TRACKING ################################# + +# Redis implements server assisted support for client side caching of values. +# This is implemented using an invalidation table that remembers, using +# a radix key indexed by key name, what clients have which keys. In turn +# this is used in order to send invalidation messages to clients. Please +# check this page to understand more about the feature: +# +# https://redis.io/topics/client-side-caching +# +# When tracking is enabled for a client, all the read only queries are assumed +# to be cached: this will force Redis to store information in the invalidation +# table. When keys are modified, such information is flushed away, and +# invalidation messages are sent to the clients. However if the workload is +# heavily dominated by reads, Redis could use more and more memory in order +# to track the keys fetched by many clients. +# +# For this reason it is possible to configure a maximum fill value for the +# invalidation table. By default it is set to 1M of keys, and once this limit +# is reached, Redis will start to evict keys in the invalidation table +# even if they were not modified, just to reclaim memory: this will in turn +# force the clients to invalidate the cached values. Basically the table +# maximum size is a trade off between the memory you want to spend server +# side to track information about who cached what, and the ability of clients +# to retain cached objects in memory. +# +# If you set the value to 0, it means there are no limits, and Redis will +# retain as many keys as needed in the invalidation table. +# In the "stats" INFO section, you can find information about the number of +# keys in the invalidation table at every given moment. +# +# Note: when key tracking is used in broadcasting mode, no memory is used +# in the server side so this setting is useless. +# +# tracking-table-max-keys 1000000 + +################################## SECURITY ################################### + +# Warning: since Redis is pretty fast, an outside user can try up to +# 1 million passwords per second against a modern box. This means that you +# should use very strong passwords, otherwise they will be very easy to break. +# Note that because the password is really a shared secret between the client +# and the server, and should not be memorized by any human, the password +# can be easily a long string from /dev/urandom or whatever, so by using a +# long and unguessable password no brute force attack will be possible. + +# Redis ACL users are defined in the following format: +# +# user ... acl rules ... +# +# For example: +# +# user worker +@list +@connection ~jobs:* on >ffa9203c493aa99 +# +# The special username "default" is used for new connections. If this user +# has the "nopass" rule, then new connections will be immediately authenticated +# as the "default" user without the need of any password provided via the +# AUTH command. Otherwise if the "default" user is not flagged with "nopass" +# the connections will start in not authenticated state, and will require +# AUTH (or the HELLO command AUTH option) in order to be authenticated and +# start to work. +# +# The ACL rules that describe what a user can do are the following: +# +# on Enable the user: it is possible to authenticate as this user. +# off Disable the user: it's no longer possible to authenticate +# with this user, however the already authenticated connections +# will still work. +# skip-sanitize-payload RESTORE dump-payload sanitization is skipped. +# sanitize-payload RESTORE dump-payload is sanitized (default). +# + Allow the execution of that command. +# May be used with `|` for allowing subcommands (e.g "+config|get") +# - Disallow the execution of that command. +# May be used with `|` for blocking subcommands (e.g "-config|set") +# +@ Allow the execution of all the commands in such category +# with valid categories are like @admin, @set, @sortedset, ... +# and so forth, see the full list in the server.c file where +# the Redis command table is described and defined. +# The special category @all means all the commands, but currently +# present in the server, and that will be loaded in the future +# via modules. +# +|first-arg Allow a specific first argument of an otherwise +# disabled command. It is only supported on commands with +# no sub-commands, and is not allowed as negative form +# like -SELECT|1, only additive starting with "+". This +# feature is deprecated and may be removed in the future. +# allcommands Alias for +@all. Note that it implies the ability to execute +# all the future commands loaded via the modules system. +# nocommands Alias for -@all. +# ~ Add a pattern of keys that can be mentioned as part of +# commands. For instance ~* allows all the keys. The pattern +# is a glob-style pattern like the one of KEYS. +# It is possible to specify multiple patterns. +# %R~ Add key read pattern that specifies which keys can be read +# from. +# %W~ Add key write pattern that specifies which keys can be +# written to. +# allkeys Alias for ~* +# resetkeys Flush the list of allowed keys patterns. +# & Add a glob-style pattern of Pub/Sub channels that can be +# accessed by the user. It is possible to specify multiple channel +# patterns. +# allchannels Alias for &* +# resetchannels Flush the list of allowed channel patterns. +# > Add this password to the list of valid password for the user. +# For example >mypass will add "mypass" to the list. +# This directive clears the "nopass" flag (see later). +# < Remove this password from the list of valid passwords. +# nopass All the set passwords of the user are removed, and the user +# is flagged as requiring no password: it means that every +# password will work against this user. If this directive is +# used for the default user, every new connection will be +# immediately authenticated with the default user without +# any explicit AUTH command required. Note that the "resetpass" +# directive will clear this condition. +# resetpass Flush the list of allowed passwords. Moreover removes the +# "nopass" status. After "resetpass" the user has no associated +# passwords and there is no way to authenticate without adding +# some password (or setting it as "nopass" later). +# reset Performs the following actions: resetpass, resetkeys, off, +# -@all. The user returns to the same state it has immediately +# after its creation. +# () Create a new selector with the options specified within the +# parentheses and attach it to the user. Each option should be +# space separated. The first character must be ( and the last +# character must be ). +# clearselectors Remove all of the currently attached selectors. +# Note this does not change the "root" user permissions, +# which are the permissions directly applied onto the +# user (outside the parentheses). +# +# ACL rules can be specified in any order: for instance you can start with +# passwords, then flags, or key patterns. However note that the additive +# and subtractive rules will CHANGE MEANING depending on the ordering. +# For instance see the following example: +# +# user alice on +@all -DEBUG ~* >somepassword +# +# This will allow "alice" to use all the commands with the exception of the +# DEBUG command, since +@all added all the commands to the set of the commands +# alice can use, and later DEBUG was removed. However if we invert the order +# of two ACL rules the result will be different: +# +# user alice on -DEBUG +@all ~* >somepassword +# +# Now DEBUG was removed when alice had yet no commands in the set of allowed +# commands, later all the commands are added, so the user will be able to +# execute everything. +# +# Basically ACL rules are processed left-to-right. +# +# The following is a list of command categories and their meanings: +# * keyspace - Writing or reading from keys, databases, or their metadata +# in a type agnostic way. Includes DEL, RESTORE, DUMP, RENAME, EXISTS, DBSIZE, +# KEYS, EXPIRE, TTL, FLUSHALL, etc. Commands that may modify the keyspace, +# key or metadata will also have `write` category. Commands that only read +# the keyspace, key or metadata will have the `read` category. +# * read - Reading from keys (values or metadata). Note that commands that don't +# interact with keys, will not have either `read` or `write`. +# * write - Writing to keys (values or metadata) +# * admin - Administrative commands. Normal applications will never need to use +# these. Includes REPLICAOF, CONFIG, DEBUG, SAVE, MONITOR, ACL, SHUTDOWN, etc. +# * dangerous - Potentially dangerous (each should be considered with care for +# various reasons). This includes FLUSHALL, MIGRATE, RESTORE, SORT, KEYS, +# CLIENT, DEBUG, INFO, CONFIG, SAVE, REPLICAOF, etc. +# * connection - Commands affecting the connection or other connections. +# This includes AUTH, SELECT, COMMAND, CLIENT, ECHO, PING, etc. +# * blocking - Potentially blocking the connection until released by another +# command. +# * fast - Fast O(1) commands. May loop on the number of arguments, but not the +# number of elements in the key. +# * slow - All commands that are not Fast. +# * pubsub - PUBLISH / SUBSCRIBE related +# * transaction - WATCH / MULTI / EXEC related commands. +# * scripting - Scripting related. +# * set - Data type: sets related. +# * sortedset - Data type: zsets related. +# * list - Data type: lists related. +# * hash - Data type: hashes related. +# * string - Data type: strings related. +# * bitmap - Data type: bitmaps related. +# * hyperloglog - Data type: hyperloglog related. +# * geo - Data type: geo related. +# * stream - Data type: streams related. +# +# For more information about ACL configuration please refer to +# the Redis web site at https://redis.io/topics/acl + +# ACL LOG +# +# The ACL Log tracks failed commands and authentication events associated +# with ACLs. The ACL Log is useful to troubleshoot failed commands blocked +# by ACLs. The ACL Log is stored in memory. You can reclaim memory with +# ACL LOG RESET. Define the maximum entry length of the ACL Log below. +acllog-max-len 128 + +# Using an external ACL file +# +# Instead of configuring users here in this file, it is possible to use +# a stand-alone file just listing users. The two methods cannot be mixed: +# if you configure users here and at the same time you activate the external +# ACL file, the server will refuse to start. +# +# The format of the external ACL user file is exactly the same as the +# format that is used inside redis.conf to describe users. +# +# aclfile /etc/redis/users.acl + +# IMPORTANT NOTE: starting with Redis 6 "requirepass" is just a compatibility +# layer on top of the new ACL system. The option effect will be just setting +# the password for the default user. Clients will still authenticate using +# AUTH as usually, or more explicitly with AUTH default +# if they follow the new protocol: both will work. +# +# The requirepass is not compatible with aclfile option and the ACL LOAD +# command, these will cause requirepass to be ignored. +# +# requirepass foobared + +# New users are initialized with restrictive permissions by default, via the +# equivalent of this ACL rule 'off resetkeys -@all'. Starting with Redis 6.2, it +# is possible to manage access to Pub/Sub channels with ACL rules as well. The +# default Pub/Sub channels permission if new users is controlled by the +# acl-pubsub-default configuration directive, which accepts one of these values: +# +# allchannels: grants access to all Pub/Sub channels +# resetchannels: revokes access to all Pub/Sub channels +# +# From Redis 7.0, acl-pubsub-default defaults to 'resetchannels' permission. +# +# acl-pubsub-default resetchannels + +# Command renaming (DEPRECATED). +# +# ------------------------------------------------------------------------ +# WARNING: avoid using this option if possible. Instead use ACLs to remove +# commands from the default user, and put them only in some admin user you +# create for administrative purposes. +# ------------------------------------------------------------------------ +# +# It is possible to change the name of dangerous commands in a shared +# environment. For instance the CONFIG command may be renamed into something +# hard to guess so that it will still be available for internal-use tools +# but not available for general clients. +# +# Example: +# +# rename-command CONFIG b840fc02d524045429941cc15f59e41cb7be6c52 +# +# It is also possible to completely kill a command by renaming it into +# an empty string: +# +# rename-command CONFIG "" +# +# Please note that changing the name of commands that are logged into the +# AOF file or transmitted to replicas may cause problems. + +################################### CLIENTS #################################### + +# Set the max number of connected clients at the same time. By default +# this limit is set to 10000 clients, however if the Redis server is not +# able to configure the process file limit to allow for the specified limit +# the max number of allowed clients is set to the current file limit +# minus 32 (as Redis reserves a few file descriptors for internal uses). +# +# Once the limit is reached Redis will close all the new connections sending +# an error 'max number of clients reached'. +# +# IMPORTANT: When Redis Cluster is used, the max number of connections is also +# shared with the cluster bus: every node in the cluster will use two +# connections, one incoming and another outgoing. It is important to size the +# limit accordingly in case of very large clusters. +# +# maxclients 10000 + +############################## MEMORY MANAGEMENT ################################ + +# Set a memory usage limit to the specified amount of bytes. +# When the memory limit is reached Redis will try to remove keys +# according to the eviction policy selected (see maxmemory-policy). +# +# If Redis can't remove keys according to the policy, or if the policy is +# set to 'noeviction', Redis will start to reply with errors to commands +# that would use more memory, like SET, LPUSH, and so on, and will continue +# to reply to read-only commands like GET. +# +# This option is usually useful when using Redis as an LRU or LFU cache, or to +# set a hard memory limit for an instance (using the 'noeviction' policy). +# +# WARNING: If you have replicas attached to an instance with maxmemory on, +# the size of the output buffers needed to feed the replicas are subtracted +# from the used memory count, so that network problems / resyncs will +# not trigger a loop where keys are evicted, and in turn the output +# buffer of replicas is full with DELs of keys evicted triggering the deletion +# of more keys, and so forth until the database is completely emptied. +# +# In short... if you have replicas attached it is suggested that you set a lower +# limit for maxmemory so that there is some free RAM on the system for replica +# output buffers (but this is not needed if the policy is 'noeviction'). +# +# maxmemory + +# MAXMEMORY POLICY: how Redis will select what to remove when maxmemory +# is reached. You can select one from the following behaviors: +# +# volatile-lru -> Evict using approximated LRU, only keys with an expire set. +# allkeys-lru -> Evict any key using approximated LRU. +# volatile-lfu -> Evict using approximated LFU, only keys with an expire set. +# allkeys-lfu -> Evict any key using approximated LFU. +# volatile-random -> Remove a random key having an expire set. +# allkeys-random -> Remove a random key, any key. +# volatile-ttl -> Remove the key with the nearest expire time (minor TTL) +# noeviction -> Don't evict anything, just return an error on write operations. +# +# LRU means Least Recently Used +# LFU means Least Frequently Used +# +# Both LRU, LFU and volatile-ttl are implemented using approximated +# randomized algorithms. +# +# Note: with any of the above policies, when there are no suitable keys for +# eviction, Redis will return an error on write operations that require +# more memory. These are usually commands that create new keys, add data or +# modify existing keys. A few examples are: SET, INCR, HSET, LPUSH, SUNIONSTORE, +# SORT (due to the STORE argument), and EXEC (if the transaction includes any +# command that requires memory). +# +# The default is: +# +# maxmemory-policy noeviction + +# LRU, LFU and minimal TTL algorithms are not precise algorithms but approximated +# algorithms (in order to save memory), so you can tune it for speed or +# accuracy. By default Redis will check five keys and pick the one that was +# used least recently, you can change the sample size using the following +# configuration directive. +# +# The default of 5 produces good enough results. 10 Approximates very closely +# true LRU but costs more CPU. 3 is faster but not very accurate. +# +# maxmemory-samples 5 + +# Eviction processing is designed to function well with the default setting. +# If there is an unusually large amount of write traffic, this value may need to +# be increased. Decreasing this value may reduce latency at the risk of +# eviction processing effectiveness +# 0 = minimum latency, 10 = default, 100 = process without regard to latency +# +# maxmemory-eviction-tenacity 10 + +# Starting from Redis 5, by default a replica will ignore its maxmemory setting +# (unless it is promoted to master after a failover or manually). It means +# that the eviction of keys will be just handled by the master, sending the +# DEL commands to the replica as keys evict in the master side. +# +# This behavior ensures that masters and replicas stay consistent, and is usually +# what you want, however if your replica is writable, or you want the replica +# to have a different memory setting, and you are sure all the writes performed +# to the replica are idempotent, then you may change this default (but be sure +# to understand what you are doing). +# +# Note that since the replica by default does not evict, it may end using more +# memory than the one set via maxmemory (there are certain buffers that may +# be larger on the replica, or data structures may sometimes take more memory +# and so forth). So make sure you monitor your replicas and make sure they +# have enough memory to never hit a real out-of-memory condition before the +# master hits the configured maxmemory setting. +# +# replica-ignore-maxmemory yes + +# Redis reclaims expired keys in two ways: upon access when those keys are +# found to be expired, and also in background, in what is called the +# "active expire key". The key space is slowly and interactively scanned +# looking for expired keys to reclaim, so that it is possible to free memory +# of keys that are expired and will never be accessed again in a short time. +# +# The default effort of the expire cycle will try to avoid having more than +# ten percent of expired keys still in memory, and will try to avoid consuming +# more than 25% of total memory and to add latency to the system. However +# it is possible to increase the expire "effort" that is normally set to +# "1", to a greater value, up to the value "10". At its maximum value the +# system will use more CPU, longer cycles (and technically may introduce +# more latency), and will tolerate less already expired keys still present +# in the system. It's a tradeoff between memory, CPU and latency. +# +# active-expire-effort 1 + +############################# LAZY FREEING #################################### + +# Redis has two primitives to delete keys. One is called DEL and is a blocking +# deletion of the object. It means that the server stops processing new commands +# in order to reclaim all the memory associated with an object in a synchronous +# way. If the key deleted is associated with a small object, the time needed +# in order to execute the DEL command is very small and comparable to most other +# O(1) or O(log_N) commands in Redis. However if the key is associated with an +# aggregated value containing millions of elements, the server can block for +# a long time (even seconds) in order to complete the operation. +# +# For the above reasons Redis also offers non blocking deletion primitives +# such as UNLINK (non blocking DEL) and the ASYNC option of FLUSHALL and +# FLUSHDB commands, in order to reclaim memory in background. Those commands +# are executed in constant time. Another thread will incrementally free the +# object in the background as fast as possible. +# +# DEL, UNLINK and ASYNC option of FLUSHALL and FLUSHDB are user-controlled. +# It's up to the design of the application to understand when it is a good +# idea to use one or the other. However the Redis server sometimes has to +# delete keys or flush the whole database as a side effect of other operations. +# Specifically Redis deletes objects independently of a user call in the +# following scenarios: +# +# 1) On eviction, because of the maxmemory and maxmemory policy configurations, +# in order to make room for new data, without going over the specified +# memory limit. +# 2) Because of expire: when a key with an associated time to live (see the +# EXPIRE command) must be deleted from memory. +# 3) Because of a side effect of a command that stores data on a key that may +# already exist. For example the RENAME command may delete the old key +# content when it is replaced with another one. Similarly SUNIONSTORE +# or SORT with STORE option may delete existing keys. The SET command +# itself removes any old content of the specified key in order to replace +# it with the specified string. +# 4) During replication, when a replica performs a full resynchronization with +# its master, the content of the whole database is removed in order to +# load the RDB file just transferred. +# +# In all the above cases the default is to delete objects in a blocking way, +# like if DEL was called. However you can configure each case specifically +# in order to instead release memory in a non-blocking way like if UNLINK +# was called, using the following configuration directives. + +lazyfree-lazy-eviction no +lazyfree-lazy-expire no +lazyfree-lazy-server-del no +replica-lazy-flush no + +# It is also possible, for the case when to replace the user code DEL calls +# with UNLINK calls is not easy, to modify the default behavior of the DEL +# command to act exactly like UNLINK, using the following configuration +# directive: + +lazyfree-lazy-user-del no + +# FLUSHDB, FLUSHALL, SCRIPT FLUSH and FUNCTION FLUSH support both asynchronous and synchronous +# deletion, which can be controlled by passing the [SYNC|ASYNC] flags into the +# commands. When neither flag is passed, this directive will be used to determine +# if the data should be deleted asynchronously. + +lazyfree-lazy-user-flush no + +################################ THREADED I/O ################################# + +# Redis is mostly single threaded, however there are certain threaded +# operations such as UNLINK, slow I/O accesses and other things that are +# performed on side threads. +# +# Now it is also possible to handle Redis clients socket reads and writes +# in different I/O threads. Since especially writing is so slow, normally +# Redis users use pipelining in order to speed up the Redis performances per +# core, and spawn multiple instances in order to scale more. Using I/O +# threads it is possible to easily speedup two times Redis without resorting +# to pipelining nor sharding of the instance. +# +# By default threading is disabled, we suggest enabling it only in machines +# that have at least 4 or more cores, leaving at least one spare core. +# Using more than 8 threads is unlikely to help much. We also recommend using +# threaded I/O only if you actually have performance problems, with Redis +# instances being able to use a quite big percentage of CPU time, otherwise +# there is no point in using this feature. +# +# So for instance if you have a four cores boxes, try to use 2 or 3 I/O +# threads, if you have a 8 cores, try to use 6 threads. In order to +# enable I/O threads use the following configuration directive: +# +# io-threads 4 +# +# Setting io-threads to 1 will just use the main thread as usual. +# When I/O threads are enabled, we only use threads for writes, that is +# to thread the write(2) syscall and transfer the client buffers to the +# socket. However it is also possible to enable threading of reads and +# protocol parsing using the following configuration directive, by setting +# it to yes: +# +# io-threads-do-reads no +# +# Usually threading reads doesn't help much. +# +# NOTE 1: This configuration directive cannot be changed at runtime via +# CONFIG SET. Also, this feature currently does not work when SSL is +# enabled. +# +# NOTE 2: If you want to test the Redis speedup using redis-benchmark, make +# sure you also run the benchmark itself in threaded mode, using the +# --threads option to match the number of Redis threads, otherwise you'll not +# be able to notice the improvements. + +############################ KERNEL OOM CONTROL ############################## + +# On Linux, it is possible to hint the kernel OOM killer on what processes +# should be killed first when out of memory. +# +# Enabling this feature makes Redis actively control the oom_score_adj value +# for all its processes, depending on their role. The default scores will +# attempt to have background child processes killed before all others, and +# replicas killed before masters. +# +# Redis supports these options: +# +# no: Don't make changes to oom-score-adj (default). +# yes: Alias to "relative" see below. +# absolute: Values in oom-score-adj-values are written as is to the kernel. +# relative: Values are used relative to the initial value of oom_score_adj when +# the server starts and are then clamped to a range of -1000 to 1000. +# Because typically the initial value is 0, they will often match the +# absolute values. +oom-score-adj no + +# When oom-score-adj is used, this directive controls the specific values used +# for master, replica and background child processes. Values range -2000 to +# 2000 (higher means more likely to be killed). +# +# Unprivileged processes (not root, and without CAP_SYS_RESOURCE capabilities) +# can freely increase their value, but not decrease it below its initial +# settings. This means that setting oom-score-adj to "relative" and setting the +# oom-score-adj-values to positive values will always succeed. +oom-score-adj-values 0 200 800 + + +#################### KERNEL transparent hugepage CONTROL ###################### + +# Usually the kernel Transparent Huge Pages control is set to "madvise" or +# or "never" by default (/sys/kernel/mm/transparent_hugepage/enabled), in which +# case this config has no effect. On systems in which it is set to "always", +# redis will attempt to disable it specifically for the redis process in order +# to avoid latency problems specifically with fork(2) and CoW. +# If for some reason you prefer to keep it enabled, you can set this config to +# "no" and the kernel global to "always". + +disable-thp yes + +############################## APPEND ONLY MODE ############################### + +# By default Redis asynchronously dumps the dataset on disk. This mode is +# good enough in many applications, but an issue with the Redis process or +# a power outage may result into a few minutes of writes lost (depending on +# the configured save points). +# +# The Append Only File is an alternative persistence mode that provides +# much better durability. For instance using the default data fsync policy +# (see later in the config file) Redis can lose just one second of writes in a +# dramatic event like a server power outage, or a single write if something +# wrong with the Redis process itself happens, but the operating system is +# still running correctly. +# +# AOF and RDB persistence can be enabled at the same time without problems. +# If the AOF is enabled on startup Redis will load the AOF, that is the file +# with the better durability guarantees. +# +# Please check https://redis.io/topics/persistence for more information. + +appendonly no + +# The base name of the append only file. +# +# Redis 7 and newer use a set of append-only files to persist the dataset +# and changes applied to it. There are two basic types of files in use: +# +# - Base files, which are a snapshot representing the complete state of the +# dataset at the time the file was created. Base files can be either in +# the form of RDB (binary serialized) or AOF (textual commands). +# - Incremental files, which contain additional commands that were applied +# to the dataset following the previous file. +# +# In addition, manifest files are used to track the files and the order in +# which they were created and should be applied. +# +# Append-only file names are created by Redis following a specific pattern. +# The file name's prefix is based on the 'appendfilename' configuration +# parameter, followed by additional information about the sequence and type. +# +# For example, if appendfilename is set to appendonly.aof, the following file +# names could be derived: +# +# - appendonly.aof.1.base.rdb as a base file. +# - appendonly.aof.1.incr.aof, appendonly.aof.2.incr.aof as incremental files. +# - appendonly.aof.manifest as a manifest file. + +appendfilename "appendonly.aof" + +# For convenience, Redis stores all persistent append-only files in a dedicated +# directory. The name of the directory is determined by the appenddirname +# configuration parameter. + +appenddirname "appendonlydir" + +# The fsync() call tells the Operating System to actually write data on disk +# instead of waiting for more data in the output buffer. Some OS will really flush +# data on disk, some other OS will just try to do it ASAP. +# +# Redis supports three different modes: +# +# no: don't fsync, just let the OS flush the data when it wants. Faster. +# always: fsync after every write to the append only log. Slow, Safest. +# everysec: fsync only one time every second. Compromise. +# +# The default is "everysec", as that's usually the right compromise between +# speed and data safety. It's up to you to understand if you can relax this to +# "no" that will let the operating system flush the output buffer when +# it wants, for better performances (but if you can live with the idea of +# some data loss consider the default persistence mode that's snapshotting), +# or on the contrary, use "always" that's very slow but a bit safer than +# everysec. +# +# More details please check the following article: +# http://antirez.com/post/redis-persistence-demystified.html +# +# If unsure, use "everysec". + +# appendfsync always +appendfsync everysec +# appendfsync no + +# When the AOF fsync policy is set to always or everysec, and a background +# saving process (a background save or AOF log background rewriting) is +# performing a lot of I/O against the disk, in some Linux configurations +# Redis may block too long on the fsync() call. Note that there is no fix for +# this currently, as even performing fsync in a different thread will block +# our synchronous write(2) call. +# +# In order to mitigate this problem it's possible to use the following option +# that will prevent fsync() from being called in the main process while a +# BGSAVE or BGREWRITEAOF is in progress. +# +# This means that while another child is saving, the durability of Redis is +# the same as "appendfsync no". In practical terms, this means that it is +# possible to lose up to 30 seconds of log in the worst scenario (with the +# default Linux settings). +# +# If you have latency problems turn this to "yes". Otherwise leave it as +# "no" that is the safest pick from the point of view of durability. + +no-appendfsync-on-rewrite no + +# Automatic rewrite of the append only file. +# Redis is able to automatically rewrite the log file implicitly calling +# BGREWRITEAOF when the AOF log size grows by the specified percentage. +# +# This is how it works: Redis remembers the size of the AOF file after the +# latest rewrite (if no rewrite has happened since the restart, the size of +# the AOF at startup is used). +# +# This base size is compared to the current size. If the current size is +# bigger than the specified percentage, the rewrite is triggered. Also +# you need to specify a minimal size for the AOF file to be rewritten, this +# is useful to avoid rewriting the AOF file even if the percentage increase +# is reached but it is still pretty small. +# +# Specify a percentage of zero in order to disable the automatic AOF +# rewrite feature. + +auto-aof-rewrite-percentage 100 +auto-aof-rewrite-min-size 64mb + +# An AOF file may be found to be truncated at the end during the Redis +# startup process, when the AOF data gets loaded back into memory. +# This may happen when the system where Redis is running +# crashes, especially when an ext4 filesystem is mounted without the +# data=ordered option (however this can't happen when Redis itself +# crashes or aborts but the operating system still works correctly). +# +# Redis can either exit with an error when this happens, or load as much +# data as possible (the default now) and start if the AOF file is found +# to be truncated at the end. The following option controls this behavior. +# +# If aof-load-truncated is set to yes, a truncated AOF file is loaded and +# the Redis server starts emitting a log to inform the user of the event. +# Otherwise if the option is set to no, the server aborts with an error +# and refuses to start. When the option is set to no, the user requires +# to fix the AOF file using the "redis-check-aof" utility before to restart +# the server. +# +# Note that if the AOF file will be found to be corrupted in the middle +# the server will still exit with an error. This option only applies when +# Redis will try to read more data from the AOF file but not enough bytes +# will be found. +aof-load-truncated yes + +# Redis can create append-only base files in either RDB or AOF formats. Using +# the RDB format is always faster and more efficient, and disabling it is only +# supported for backward compatibility purposes. +aof-use-rdb-preamble yes + +# Redis supports recording timestamp annotations in the AOF to support restoring +# the data from a specific point-in-time. However, using this capability changes +# the AOF format in a way that may not be compatible with existing AOF parsers. +aof-timestamp-enabled no + +################################ SHUTDOWN ##################################### + +# Maximum time to wait for replicas when shutting down, in seconds. +# +# During shut down, a grace period allows any lagging replicas to catch up with +# the latest replication offset before the master exists. This period can +# prevent data loss, especially for deployments without configured disk backups. +# +# The 'shutdown-timeout' value is the grace period's duration in seconds. It is +# only applicable when the instance has replicas. To disable the feature, set +# the value to 0. +# +# shutdown-timeout 10 + +# When Redis receives a SIGINT or SIGTERM, shutdown is initiated and by default +# an RDB snapshot is written to disk in a blocking operation if save points are configured. +# The options used on signaled shutdown can include the following values: +# default: Saves RDB snapshot only if save points are configured. +# Waits for lagging replicas to catch up. +# save: Forces a DB saving operation even if no save points are configured. +# nosave: Prevents DB saving operation even if one or more save points are configured. +# now: Skips waiting for lagging replicas. +# force: Ignores any errors that would normally prevent the server from exiting. +# +# Any combination of values is allowed as long as "save" and "nosave" are not set simultaneously. +# Example: "nosave force now" +# +# shutdown-on-sigint default +# shutdown-on-sigterm default + +################ NON-DETERMINISTIC LONG BLOCKING COMMANDS ##################### + +# Maximum time in milliseconds for EVAL scripts, functions and in some cases +# modules' commands before Redis can start processing or rejecting other clients. +# +# If the maximum execution time is reached Redis will start to reply to most +# commands with a BUSY error. +# +# In this state Redis will only allow a handful of commands to be executed. +# For instance, SCRIPT KILL, FUNCTION KILL, SHUTDOWN NOSAVE and possibly some +# module specific 'allow-busy' commands. +# +# SCRIPT KILL and FUNCTION KILL will only be able to stop a script that did not +# yet call any write commands, so SHUTDOWN NOSAVE may be the only way to stop +# the server in the case a write command was already issued by the script when +# the user doesn't want to wait for the natural termination of the script. +# +# The default is 5 seconds. It is possible to set it to 0 or a negative value +# to disable this mechanism (uninterrupted execution). Note that in the past +# this config had a different name, which is now an alias, so both of these do +# the same: +# lua-time-limit 5000 +# busy-reply-threshold 5000 + +################################ REDIS CLUSTER ############################### + +# Normal Redis instances can't be part of a Redis Cluster; only nodes that are +# started as cluster nodes can. In order to start a Redis instance as a +# cluster node enable the cluster support uncommenting the following: +# +# cluster-enabled yes + +# Every cluster node has a cluster configuration file. This file is not +# intended to be edited by hand. It is created and updated by Redis nodes. +# Every Redis Cluster node requires a different cluster configuration file. +# Make sure that instances running in the same system do not have +# overlapping cluster configuration file names. +# +# cluster-config-file nodes-6379.conf + +# Cluster node timeout is the amount of milliseconds a node must be unreachable +# for it to be considered in failure state. +# Most other internal time limits are a multiple of the node timeout. +# +# cluster-node-timeout 15000 + +# The cluster port is the port that the cluster bus will listen for inbound connections on. When set +# to the default value, 0, it will be bound to the command port + 10000. Setting this value requires +# you to specify the cluster bus port when executing cluster meet. +# cluster-port 0 + +# A replica of a failing master will avoid to start a failover if its data +# looks too old. +# +# There is no simple way for a replica to actually have an exact measure of +# its "data age", so the following two checks are performed: +# +# 1) If there are multiple replicas able to failover, they exchange messages +# in order to try to give an advantage to the replica with the best +# replication offset (more data from the master processed). +# Replicas will try to get their rank by offset, and apply to the start +# of the failover a delay proportional to their rank. +# +# 2) Every single replica computes the time of the last interaction with +# its master. This can be the last ping or command received (if the master +# is still in the "connected" state), or the time that elapsed since the +# disconnection with the master (if the replication link is currently down). +# If the last interaction is too old, the replica will not try to failover +# at all. +# +# The point "2" can be tuned by user. Specifically a replica will not perform +# the failover if, since the last interaction with the master, the time +# elapsed is greater than: +# +# (node-timeout * cluster-replica-validity-factor) + repl-ping-replica-period +# +# So for example if node-timeout is 30 seconds, and the cluster-replica-validity-factor +# is 10, and assuming a default repl-ping-replica-period of 10 seconds, the +# replica will not try to failover if it was not able to talk with the master +# for longer than 310 seconds. +# +# A large cluster-replica-validity-factor may allow replicas with too old data to failover +# a master, while a too small value may prevent the cluster from being able to +# elect a replica at all. +# +# For maximum availability, it is possible to set the cluster-replica-validity-factor +# to a value of 0, which means, that replicas will always try to failover the +# master regardless of the last time they interacted with the master. +# (However they'll always try to apply a delay proportional to their +# offset rank). +# +# Zero is the only value able to guarantee that when all the partitions heal +# the cluster will always be able to continue. +# +# cluster-replica-validity-factor 10 + +# Cluster replicas are able to migrate to orphaned masters, that are masters +# that are left without working replicas. This improves the cluster ability +# to resist to failures as otherwise an orphaned master can't be failed over +# in case of failure if it has no working replicas. +# +# Replicas migrate to orphaned masters only if there are still at least a +# given number of other working replicas for their old master. This number +# is the "migration barrier". A migration barrier of 1 means that a replica +# will migrate only if there is at least 1 other working replica for its master +# and so forth. It usually reflects the number of replicas you want for every +# master in your cluster. +# +# Default is 1 (replicas migrate only if their masters remain with at least +# one replica). To disable migration just set it to a very large value or +# set cluster-allow-replica-migration to 'no'. +# A value of 0 can be set but is useful only for debugging and dangerous +# in production. +# +# cluster-migration-barrier 1 + +# Turning off this option allows to use less automatic cluster configuration. +# It both disables migration to orphaned masters and migration from masters +# that became empty. +# +# Default is 'yes' (allow automatic migrations). +# +# cluster-allow-replica-migration yes + +# By default Redis Cluster nodes stop accepting queries if they detect there +# is at least a hash slot uncovered (no available node is serving it). +# This way if the cluster is partially down (for example a range of hash slots +# are no longer covered) all the cluster becomes, eventually, unavailable. +# It automatically returns available as soon as all the slots are covered again. +# +# However sometimes you want the subset of the cluster which is working, +# to continue to accept queries for the part of the key space that is still +# covered. In order to do so, just set the cluster-require-full-coverage +# option to no. +# +# cluster-require-full-coverage yes + +# This option, when set to yes, prevents replicas from trying to failover its +# master during master failures. However the replica can still perform a +# manual failover, if forced to do so. +# +# This is useful in different scenarios, especially in the case of multiple +# data center operations, where we want one side to never be promoted if not +# in the case of a total DC failure. +# +# cluster-replica-no-failover no + +# This option, when set to yes, allows nodes to serve read traffic while the +# cluster is in a down state, as long as it believes it owns the slots. +# +# This is useful for two cases. The first case is for when an application +# doesn't require consistency of data during node failures or network partitions. +# One example of this is a cache, where as long as the node has the data it +# should be able to serve it. +# +# The second use case is for configurations that don't meet the recommended +# three shards but want to enable cluster mode and scale later. A +# master outage in a 1 or 2 shard configuration causes a read/write outage to the +# entire cluster without this option set, with it set there is only a write outage. +# Without a quorum of masters, slot ownership will not change automatically. +# +# cluster-allow-reads-when-down no + +# This option, when set to yes, allows nodes to serve pubsub shard traffic while +# the cluster is in a down state, as long as it believes it owns the slots. +# +# This is useful if the application would like to use the pubsub feature even when +# the cluster global stable state is not OK. If the application wants to make sure only +# one shard is serving a given channel, this feature should be kept as yes. +# +# cluster-allow-pubsubshard-when-down yes + +# Cluster link send buffer limit is the limit on the memory usage of an individual +# cluster bus link's send buffer in bytes. Cluster links would be freed if they exceed +# this limit. This is to primarily prevent send buffers from growing unbounded on links +# toward slow peers (E.g. PubSub messages being piled up). +# This limit is disabled by default. Enable this limit when 'mem_cluster_links' INFO field +# and/or 'send-buffer-allocated' entries in the 'CLUSTER LINKS` command output continuously increase. +# Minimum limit of 1gb is recommended so that cluster link buffer can fit in at least a single +# PubSub message by default. (client-query-buffer-limit default value is 1gb) +# +# cluster-link-sendbuf-limit 0 + +# Clusters can configure their announced hostname using this config. This is a common use case for +# applications that need to use TLS Server Name Indication (SNI) or dealing with DNS based +# routing. By default this value is only shown as additional metadata in the CLUSTER SLOTS +# command, but can be changed using 'cluster-preferred-endpoint-type' config. This value is +# communicated along the clusterbus to all nodes, setting it to an empty string will remove +# the hostname and also propagate the removal. +# +# cluster-announce-hostname "" + +# Clusters can advertise how clients should connect to them using either their IP address, +# a user defined hostname, or by declaring they have no endpoint. Which endpoint is +# shown as the preferred endpoint is set by using the cluster-preferred-endpoint-type +# config with values 'ip', 'hostname', or 'unknown-endpoint'. This value controls how +# the endpoint returned for MOVED/ASKING requests as well as the first field of CLUSTER SLOTS. +# If the preferred endpoint type is set to hostname, but no announced hostname is set, a '?' +# will be returned instead. +# +# When a cluster advertises itself as having an unknown endpoint, it's indicating that +# the server doesn't know how clients can reach the cluster. This can happen in certain +# networking situations where there are multiple possible routes to the node, and the +# server doesn't know which one the client took. In this case, the server is expecting +# the client to reach out on the same endpoint it used for making the last request, but use +# the port provided in the response. +# +# cluster-preferred-endpoint-type ip + +# In order to setup your cluster make sure to read the documentation +# available at https://redis.io web site. + +########################## CLUSTER DOCKER/NAT support ######################## + +# In certain deployments, Redis Cluster nodes address discovery fails, because +# addresses are NAT-ted or because ports are forwarded (the typical case is +# Docker and other containers). +# +# In order to make Redis Cluster working in such environments, a static +# configuration where each node knows its public address is needed. The +# following four options are used for this scope, and are: +# +# * cluster-announce-ip +# * cluster-announce-port +# * cluster-announce-tls-port +# * cluster-announce-bus-port +# +# Each instructs the node about its address, client ports (for connections +# without and with TLS) and cluster message bus port. The information is then +# published in the header of the bus packets so that other nodes will be able to +# correctly map the address of the node publishing the information. +# +# If cluster-tls is set to yes and cluster-announce-tls-port is omitted or set +# to zero, then cluster-announce-port refers to the TLS port. Note also that +# cluster-announce-tls-port has no effect if cluster-tls is set to no. +# +# If the above options are not used, the normal Redis Cluster auto-detection +# will be used instead. +# +# Note that when remapped, the bus port may not be at the fixed offset of +# clients port + 10000, so you can specify any port and bus-port depending +# on how they get remapped. If the bus-port is not set, a fixed offset of +# 10000 will be used as usual. +# +# Example: +# +# cluster-announce-ip 10.1.1.5 +# cluster-announce-tls-port 6379 +# cluster-announce-port 0 +# cluster-announce-bus-port 6380 + +################################## SLOW LOG ################################### + +# The Redis Slow Log is a system to log queries that exceeded a specified +# execution time. The execution time does not include the I/O operations +# like talking with the client, sending the reply and so forth, +# but just the time needed to actually execute the command (this is the only +# stage of command execution where the thread is blocked and can not serve +# other requests in the meantime). +# +# You can configure the slow log with two parameters: one tells Redis +# what is the execution time, in microseconds, to exceed in order for the +# command to get logged, and the other parameter is the length of the +# slow log. When a new command is logged the oldest one is removed from the +# queue of logged commands. + +# The following time is expressed in microseconds, so 1000000 is equivalent +# to one second. Note that a negative number disables the slow log, while +# a value of zero forces the logging of every command. +slowlog-log-slower-than 10000 + +# There is no limit to this length. Just be aware that it will consume memory. +# You can reclaim memory used by the slow log with SLOWLOG RESET. +slowlog-max-len 128 + +################################ LATENCY MONITOR ############################## + +# The Redis latency monitoring subsystem samples different operations +# at runtime in order to collect data related to possible sources of +# latency of a Redis instance. +# +# Via the LATENCY command this information is available to the user that can +# print graphs and obtain reports. +# +# The system only logs operations that were performed in a time equal or +# greater than the amount of milliseconds specified via the +# latency-monitor-threshold configuration directive. When its value is set +# to zero, the latency monitor is turned off. +# +# By default latency monitoring is disabled since it is mostly not needed +# if you don't have latency issues, and collecting data has a performance +# impact, that while very small, can be measured under big load. Latency +# monitoring can easily be enabled at runtime using the command +# "CONFIG SET latency-monitor-threshold " if needed. +latency-monitor-threshold 0 + +################################ LATENCY TRACKING ############################## + +# The Redis extended latency monitoring tracks the per command latencies and enables +# exporting the percentile distribution via the INFO latencystats command, +# and cumulative latency distributions (histograms) via the LATENCY command. +# +# By default, the extended latency monitoring is enabled since the overhead +# of keeping track of the command latency is very small. +# latency-tracking yes + +# By default the exported latency percentiles via the INFO latencystats command +# are the p50, p99, and p999. +# latency-tracking-info-percentiles 50 99 99.9 + +############################# EVENT NOTIFICATION ############################## + +# Redis can notify Pub/Sub clients about events happening in the key space. +# This feature is documented at https://redis.io/topics/notifications +# +# For instance if keyspace events notification is enabled, and a client +# performs a DEL operation on key "foo" stored in the Database 0, two +# messages will be published via Pub/Sub: +# +# PUBLISH __keyspace@0__:foo del +# PUBLISH __keyevent@0__:del foo +# +# It is possible to select the events that Redis will notify among a set +# of classes. Every class is identified by a single character: +# +# K Keyspace events, published with __keyspace@__ prefix. +# E Keyevent events, published with __keyevent@__ prefix. +# g Generic commands (non-type specific) like DEL, EXPIRE, RENAME, ... +# $ String commands +# l List commands +# s Set commands +# h Hash commands +# z Sorted set commands +# x Expired events (events generated every time a key expires) +# e Evicted events (events generated when a key is evicted for maxmemory) +# n New key events (Note: not included in the 'A' class) +# t Stream commands +# d Module key type events +# m Key-miss events (Note: It is not included in the 'A' class) +# A Alias for g$lshzxetd, so that the "AKE" string means all the events +# (Except key-miss events which are excluded from 'A' due to their +# unique nature). +# +# The "notify-keyspace-events" takes as argument a string that is composed +# of zero or multiple characters. The empty string means that notifications +# are disabled. +# +# Example: to enable list and generic events, from the point of view of the +# event name, use: +# +# notify-keyspace-events Elg +# +# Example 2: to get the stream of the expired keys subscribing to channel +# name __keyevent@0__:expired use: +# +# notify-keyspace-events Ex +# +# By default all notifications are disabled because most users don't need +# this feature and the feature has some overhead. Note that if you don't +# specify at least one of K or E, no events will be delivered. +notify-keyspace-events "" + +############################### ADVANCED CONFIG ############################### + +# Hashes are encoded using a memory efficient data structure when they have a +# small number of entries, and the biggest entry does not exceed a given +# threshold. These thresholds can be configured using the following directives. +hash-max-listpack-entries 512 +hash-max-listpack-value 64 + +# Lists are also encoded in a special way to save a lot of space. +# The number of entries allowed per internal list node can be specified +# as a fixed maximum size or a maximum number of elements. +# For a fixed maximum size, use -5 through -1, meaning: +# -5: max size: 64 Kb <-- not recommended for normal workloads +# -4: max size: 32 Kb <-- not recommended +# -3: max size: 16 Kb <-- probably not recommended +# -2: max size: 8 Kb <-- good +# -1: max size: 4 Kb <-- good +# Positive numbers mean store up to _exactly_ that number of elements +# per list node. +# The highest performing option is usually -2 (8 Kb size) or -1 (4 Kb size), +# but if your use case is unique, adjust the settings as necessary. +list-max-listpack-size -2 + +# Lists may also be compressed. +# Compress depth is the number of quicklist ziplist nodes from *each* side of +# the list to *exclude* from compression. The head and tail of the list +# are always uncompressed for fast push/pop operations. Settings are: +# 0: disable all list compression +# 1: depth 1 means "don't start compressing until after 1 node into the list, +# going from either the head or tail" +# So: [head]->node->node->...->node->[tail] +# [head], [tail] will always be uncompressed; inner nodes will compress. +# 2: [head]->[next]->node->node->...->node->[prev]->[tail] +# 2 here means: don't compress head or head->next or tail->prev or tail, +# but compress all nodes between them. +# 3: [head]->[next]->[next]->node->node->...->node->[prev]->[prev]->[tail] +# etc. +list-compress-depth 0 + +# Sets have a special encoding in just one case: when a set is composed +# of just strings that happen to be integers in radix 10 in the range +# of 64 bit signed integers. +# The following configuration setting sets the limit in the size of the +# set in order to use this special memory saving encoding. +set-max-intset-entries 512 + +# Similarly to hashes and lists, sorted sets are also specially encoded in +# order to save a lot of space. This encoding is only used when the length and +# elements of a sorted set are below the following limits: +zset-max-listpack-entries 128 +zset-max-listpack-value 64 + +# HyperLogLog sparse representation bytes limit. The limit includes the +# 16 bytes header. When an HyperLogLog using the sparse representation crosses +# this limit, it is converted into the dense representation. +# +# A value greater than 16000 is totally useless, since at that point the +# dense representation is more memory efficient. +# +# The suggested value is ~ 3000 in order to have the benefits of +# the space efficient encoding without slowing down too much PFADD, +# which is O(N) with the sparse encoding. The value can be raised to +# ~ 10000 when CPU is not a concern, but space is, and the data set is +# composed of many HyperLogLogs with cardinality in the 0 - 15000 range. +hll-sparse-max-bytes 3000 + +# Streams macro node max size / items. The stream data structure is a radix +# tree of big nodes that encode multiple items inside. Using this configuration +# it is possible to configure how big a single node can be in bytes, and the +# maximum number of items it may contain before switching to a new node when +# appending new stream entries. If any of the following settings are set to +# zero, the limit is ignored, so for instance it is possible to set just a +# max entries limit by setting max-bytes to 0 and max-entries to the desired +# value. +stream-node-max-bytes 4096 +stream-node-max-entries 100 + +# Active rehashing uses 1 millisecond every 100 milliseconds of CPU time in +# order to help rehashing the main Redis hash table (the one mapping top-level +# keys to values). The hash table implementation Redis uses (see dict.c) +# performs a lazy rehashing: the more operation you run into a hash table +# that is rehashing, the more rehashing "steps" are performed, so if the +# server is idle the rehashing is never complete and some more memory is used +# by the hash table. +# +# The default is to use this millisecond 10 times every second in order to +# actively rehash the main dictionaries, freeing memory when possible. +# +# If unsure: +# use "activerehashing no" if you have hard latency requirements and it is +# not a good thing in your environment that Redis can reply from time to time +# to queries with 2 milliseconds delay. +# +# use "activerehashing yes" if you don't have such hard requirements but +# want to free memory asap when possible. +activerehashing yes + +# The client output buffer limits can be used to force disconnection of clients +# that are not reading data from the server fast enough for some reason (a +# common reason is that a Pub/Sub client can't consume messages as fast as the +# publisher can produce them). +# +# The limit can be set differently for the three different classes of clients: +# +# normal -> normal clients including MONITOR clients +# replica -> replica clients +# pubsub -> clients subscribed to at least one pubsub channel or pattern +# +# The syntax of every client-output-buffer-limit directive is the following: +# +# client-output-buffer-limit +# +# A client is immediately disconnected once the hard limit is reached, or if +# the soft limit is reached and remains reached for the specified number of +# seconds (continuously). +# So for instance if the hard limit is 32 megabytes and the soft limit is +# 16 megabytes / 10 seconds, the client will get disconnected immediately +# if the size of the output buffers reach 32 megabytes, but will also get +# disconnected if the client reaches 16 megabytes and continuously overcomes +# the limit for 10 seconds. +# +# By default normal clients are not limited because they don't receive data +# without asking (in a push way), but just after a request, so only +# asynchronous clients may create a scenario where data is requested faster +# than it can read. +# +# Instead there is a default limit for pubsub and replica clients, since +# subscribers and replicas receive data in a push fashion. +# +# Note that it doesn't make sense to set the replica clients output buffer +# limit lower than the repl-backlog-size config (partial sync will succeed +# and then replica will get disconnected). +# Such a configuration is ignored (the size of repl-backlog-size will be used). +# This doesn't have memory consumption implications since the replica client +# will share the backlog buffers memory. +# +# Both the hard or the soft limit can be disabled by setting them to zero. +client-output-buffer-limit normal 0 0 0 +client-output-buffer-limit replica 256mb 64mb 60 +client-output-buffer-limit pubsub 32mb 8mb 60 + +# Client query buffers accumulate new commands. They are limited to a fixed +# amount by default in order to avoid that a protocol desynchronization (for +# instance due to a bug in the client) will lead to unbound memory usage in +# the query buffer. However you can configure it here if you have very special +# needs, such us huge multi/exec requests or alike. +# +# client-query-buffer-limit 1gb + +# In some scenarios client connections can hog up memory leading to OOM +# errors or data eviction. To avoid this we can cap the accumulated memory +# used by all client connections (all pubsub and normal clients). Once we +# reach that limit connections will be dropped by the server freeing up +# memory. The server will attempt to drop the connections using the most +# memory first. We call this mechanism "client eviction". +# +# Client eviction is configured using the maxmemory-clients setting as follows: +# 0 - client eviction is disabled (default) +# +# A memory value can be used for the client eviction threshold, +# for example: +# maxmemory-clients 1g +# +# A percentage value (between 1% and 100%) means the client eviction threshold +# is based on a percentage of the maxmemory setting. For example to set client +# eviction at 5% of maxmemory: +# maxmemory-clients 5% + +# In the Redis protocol, bulk requests, that are, elements representing single +# strings, are normally limited to 512 mb. However you can change this limit +# here, but must be 1mb or greater +# +# proto-max-bulk-len 512mb + +# Redis calls an internal function to perform many background tasks, like +# closing connections of clients in timeout, purging expired keys that are +# never requested, and so forth. +# +# Not all tasks are performed with the same frequency, but Redis checks for +# tasks to perform according to the specified "hz" value. +# +# By default "hz" is set to 10. Raising the value will use more CPU when +# Redis is idle, but at the same time will make Redis more responsive when +# there are many keys expiring at the same time, and timeouts may be +# handled with more precision. +# +# The range is between 1 and 500, however a value over 100 is usually not +# a good idea. Most users should use the default of 10 and raise this up to +# 100 only in environments where very low latency is required. +hz 10 + +# Normally it is useful to have an HZ value which is proportional to the +# number of clients connected. This is useful in order, for instance, to +# avoid too many clients are processed for each background task invocation +# in order to avoid latency spikes. +# +# Since the default HZ value by default is conservatively set to 10, Redis +# offers, and enables by default, the ability to use an adaptive HZ value +# which will temporarily raise when there are many connected clients. +# +# When dynamic HZ is enabled, the actual configured HZ will be used +# as a baseline, but multiples of the configured HZ value will be actually +# used as needed once more clients are connected. In this way an idle +# instance will use very little CPU time while a busy instance will be +# more responsive. +dynamic-hz yes + +# When a child rewrites the AOF file, if the following option is enabled +# the file will be fsync-ed every 4 MB of data generated. This is useful +# in order to commit the file to the disk more incrementally and avoid +# big latency spikes. +aof-rewrite-incremental-fsync yes + +# When redis saves RDB file, if the following option is enabled +# the file will be fsync-ed every 4 MB of data generated. This is useful +# in order to commit the file to the disk more incrementally and avoid +# big latency spikes. +rdb-save-incremental-fsync yes + +# Redis LFU eviction (see maxmemory setting) can be tuned. However it is a good +# idea to start with the default settings and only change them after investigating +# how to improve the performances and how the keys LFU change over time, which +# is possible to inspect via the OBJECT FREQ command. +# +# There are two tunable parameters in the Redis LFU implementation: the +# counter logarithm factor and the counter decay time. It is important to +# understand what the two parameters mean before changing them. +# +# The LFU counter is just 8 bits per key, it's maximum value is 255, so Redis +# uses a probabilistic increment with logarithmic behavior. Given the value +# of the old counter, when a key is accessed, the counter is incremented in +# this way: +# +# 1. A random number R between 0 and 1 is extracted. +# 2. A probability P is calculated as 1/(old_value*lfu_log_factor+1). +# 3. The counter is incremented only if R < P. +# +# The default lfu-log-factor is 10. This is a table of how the frequency +# counter changes with a different number of accesses with different +# logarithmic factors: +# +# +--------+------------+------------+------------+------------+------------+ +# | factor | 100 hits | 1000 hits | 100K hits | 1M hits | 10M hits | +# +--------+------------+------------+------------+------------+------------+ +# | 0 | 104 | 255 | 255 | 255 | 255 | +# +--------+------------+------------+------------+------------+------------+ +# | 1 | 18 | 49 | 255 | 255 | 255 | +# +--------+------------+------------+------------+------------+------------+ +# | 10 | 10 | 18 | 142 | 255 | 255 | +# +--------+------------+------------+------------+------------+------------+ +# | 100 | 8 | 11 | 49 | 143 | 255 | +# +--------+------------+------------+------------+------------+------------+ +# +# NOTE: The above table was obtained by running the following commands: +# +# redis-benchmark -n 1000000 incr foo +# redis-cli object freq foo +# +# NOTE 2: The counter initial value is 5 in order to give new objects a chance +# to accumulate hits. +# +# The counter decay time is the time, in minutes, that must elapse in order +# for the key counter to be divided by two (or decremented if it has a value +# less <= 10). +# +# The default value for the lfu-decay-time is 1. A special value of 0 means to +# decay the counter every time it happens to be scanned. +# +# lfu-log-factor 10 +# lfu-decay-time 1 + +########################### ACTIVE DEFRAGMENTATION ####################### +# +# What is active defragmentation? +# ------------------------------- +# +# Active (online) defragmentation allows a Redis server to compact the +# spaces left between small allocations and deallocations of data in memory, +# thus allowing to reclaim back memory. +# +# Fragmentation is a natural process that happens with every allocator (but +# less so with Jemalloc, fortunately) and certain workloads. Normally a server +# restart is needed in order to lower the fragmentation, or at least to flush +# away all the data and create it again. However thanks to this feature +# implemented by Oran Agra for Redis 4.0 this process can happen at runtime +# in a "hot" way, while the server is running. +# +# Basically when the fragmentation is over a certain level (see the +# configuration options below) Redis will start to create new copies of the +# values in contiguous memory regions by exploiting certain specific Jemalloc +# features (in order to understand if an allocation is causing fragmentation +# and to allocate it in a better place), and at the same time, will release the +# old copies of the data. This process, repeated incrementally for all the keys +# will cause the fragmentation to drop back to normal values. +# +# Important things to understand: +# +# 1. This feature is disabled by default, and only works if you compiled Redis +# to use the copy of Jemalloc we ship with the source code of Redis. +# This is the default with Linux builds. +# +# 2. You never need to enable this feature if you don't have fragmentation +# issues. +# +# 3. Once you experience fragmentation, you can enable this feature when +# needed with the command "CONFIG SET activedefrag yes". +# +# The configuration parameters are able to fine tune the behavior of the +# defragmentation process. If you are not sure about what they mean it is +# a good idea to leave the defaults untouched. + +# Active defragmentation is disabled by default +# activedefrag no + +# Minimum amount of fragmentation waste to start active defrag +# active-defrag-ignore-bytes 100mb + +# Minimum percentage of fragmentation to start active defrag +# active-defrag-threshold-lower 10 + +# Maximum percentage of fragmentation at which we use maximum effort +# active-defrag-threshold-upper 100 + +# Minimal effort for defrag in CPU percentage, to be used when the lower +# threshold is reached +# active-defrag-cycle-min 1 + +# Maximal effort for defrag in CPU percentage, to be used when the upper +# threshold is reached +# active-defrag-cycle-max 25 + +# Maximum number of set/hash/zset/list fields that will be processed from +# the main dictionary scan +# active-defrag-max-scan-fields 1000 + +# Jemalloc background thread for purging will be enabled by default +jemalloc-bg-thread yes + +# It is possible to pin different threads and processes of Redis to specific +# CPUs in your system, in order to maximize the performances of the server. +# This is useful both in order to pin different Redis threads in different +# CPUs, but also in order to make sure that multiple Redis instances running +# in the same host will be pinned to different CPUs. +# +# Normally you can do this using the "taskset" command, however it is also +# possible to this via Redis configuration directly, both in Linux and FreeBSD. +# +# You can pin the server/IO threads, bio threads, aof rewrite child process, and +# the bgsave child process. The syntax to specify the cpu list is the same as +# the taskset command: +# +# Set redis server/io threads to cpu affinity 0,2,4,6: +# server_cpulist 0-7:2 +# +# Set bio threads to cpu affinity 1,3: +# bio_cpulist 1,3 +# +# Set aof rewrite child process to cpu affinity 8,9,10,11: +# aof_rewrite_cpulist 8-11 +# +# Set bgsave child process to cpu affinity 1,10,11 +# bgsave_cpulist 1,10-11 + +# In some cases redis will emit warnings and even refuse to start if it detects +# that the system is in bad state, it is possible to suppress these warnings +# by setting the following config which takes a space delimited list of warnings +# to suppress +# +# ignore-warnings ARM64-COW-BUG diff --git a/smallboot-api/doc/部署/run.md b/smallboot-api/doc/部署/run.md new file mode 100644 index 0000000..2a47fe1 --- /dev/null +++ b/smallboot-api/doc/部署/run.md @@ -0,0 +1,28 @@ +# 环境部署 + +> 组件环境部署可参考 https://gitee.com/zhengqingya/docker-compose + +```shell +# portainer +docker run -d -p 9000:9000 --restart=always --name portainer -v /var/run/docker.sock:/var/run/docker.sock registry.cn-hangzhou.aliyuncs.com/zhengqing/portainer-ce:2.16.2 + + +# 当前目录下所有文件赋予权限(读、写、执行) +chmod -R 777 ./redis + +# 组件部署 +docker-compose -f ./docker-compose.yml -p smallboot up -d mysql redis minio + +# 服务部署 -- 根据自己的条件去启动 +docker-compose -p smallboot up -d api +docker-compose -p smallboot up -d web +``` + +--- + +### Redis + +```shell +# 连接redis +docker exec -it redis redis-cli -a 123456 +``` diff --git a/smallboot-api/doc/部署/run.sh b/smallboot-api/doc/部署/run.sh new file mode 100644 index 0000000..d3df0fd --- /dev/null +++ b/smallboot-api/doc/部署/run.sh @@ -0,0 +1,23 @@ +#################################### +# @description 运行docker镜像 +# @params $? => 代表上一个命令执行后的退出状态: 0->成功,1->失败 +# ${1} => 脚本第1个参数: 要运行的名称 +# @example => sh run.sh +# @author zhengqingya +# @date 2023/2/4 12:49 PM +#################################### + +# 在执行过程中若遇到使用了未定义的变量或命令返回值为非零,将直接报错退出 +set -eu + +# 获取脚本第一个参数 +APP=${1} + +# 删除旧容器 +docker ps -a | grep ${APP} | awk '{print $1}' | xargs -i docker stop {} | xargs -i docker rm {} + +# 删除旧镜像 +docker images | grep -E ${APP} | awk '{print $3}' | uniq | xargs -I {} docker rmi --force {} + +# 运行 +docker-compose -p smallboot up -d ${APP} diff --git a/smallboot-api/docker/Dockerfile b/smallboot-api/docker/Dockerfile new file mode 100644 index 0000000..bb3b329 --- /dev/null +++ b/smallboot-api/docker/Dockerfile @@ -0,0 +1,19 @@ +# jre基础环境 +FROM openjdk:8-jre-alpine + +# 维护者信息 +MAINTAINER zhengqingya + +# 设置环境变量-运行时也可传参进来耍哈 +ENV JAVA_OPTS "" + +# 添加jar包到容器中 -- tips: xx.jar 和 Dockerfile 在同一级 +ADD *.jar /home/app.jar + +# 对外暴漏的端口号 +# [注:EXPOSE指令只是声明容器运行时提供的服务端口,给读者看有哪些端口,在运行时只会开启程序自身的端口!!] +EXPOSE 8080 + +# 以exec格式的CMD指令 -- 可实现优雅停止容器服务 +# "sh", "-c" : 可通过exec模式执行shell =》 获得环境变量 +CMD ["sh", "-c", "echo \"****** 运行命令:java -jar ${JAVA_OPTS} /home/app.jar\" & java -jar ${JAVA_OPTS} /home/app.jar"] diff --git a/smallboot-api/docker/README.md b/smallboot-api/docker/README.md new file mode 100644 index 0000000..884b316 --- /dev/null +++ b/smallboot-api/docker/README.md @@ -0,0 +1,23 @@ +### 使用示例命令 + +此版本提供出一个`JAVA_OPTS`去设置jar的运行参数 + +```shell +# 打包镜像 -f:指定Dockerfile文件路径 --no-cache:构建镜像时不使用缓存 +docker build -f Dockerfile --build-arg JAVA_OPTS="-XX:+UseG1GC" -t "registry.cn-hangzhou.aliyuncs.com/zhengqingya/smallboot-api:prod" . --no-cache + +# 推送镜像 +docker push registry.cn-hangzhou.aliyuncs.com/zhengqingya/smallboot-api:prod + +# 拉取镜像 +docker pull registry.cn-hangzhou.aliyuncs.com/zhengqingya/smallboot-api:prod + +# 运行 +docker run -d -p 888:888 --name smallboot-api registry.cn-hangzhou.aliyuncs.com/zhengqingya/smallboot-api:prod + +# 删除旧容器 +docker ps -a | grep smallboot-api | grep dev | awk '{print $1}' | xargs -i docker stop {} | xargs -I docker rm {} + +# 删除旧镜像 +docker images | grep -E smallboot-api | grep dev | awk '{print $3}' | uniq | xargs -I {} docker rmi --force {} +``` diff --git a/smallboot-api/pom.xml b/smallboot-api/pom.xml new file mode 100644 index 0000000..b0cc1d8 --- /dev/null +++ b/smallboot-api/pom.xml @@ -0,0 +1,257 @@ + + + 4.0.0 + + com.zhengqing + smallboot-api + ${revision} + pom + + smallboot-api + https://gitee.com/zhengqingya + Small project for Spring Boot + + + UTF-8 + UTF-8 + 1.8 + 1.0.1 + + + + common + system + app + + + + + + + org.springframework.boot + spring-boot-dependencies + 2.7.0 + pom + import + + + + + com.zhengqing + auth + ${revision} + + + com.zhengqing + base + ${revision} + + + com.zhengqing + core + ${revision} + + + com.zhengqing + db + ${revision} + + + com.zhengqing + file + ${revision} + + + com.zhengqing + log + ${revision} + + + com.zhengqing + redis + ${revision} + + + com.zhengqing + swagger + ${revision} + + + com.zhengqing + web + ${revision} + + + + + com.zhengqing + system + ${revision} + + + + + + + org.springframework.boot + spring-boot-starter + + + + org.springframework.boot + spring-boot-starter-web + + + + org.springframework.boot + spring-boot-starter-test + test + + + + junit + junit + test + + + + + org.projectlombok + lombok + true + + + + + + cn.hutool + hutool-all + 5.8.3 + + + + + + com.google.guava + guava + 31.1-jre + + + + + commons-beanutils + commons-beanutils + 1.9.4 + + + + + + com.github.xiaoymin + knife4j-spring-boot-starter + 3.0.3 + + + + + + + ${project.name} + + + + + src/main/java + + **/*.xml + + false + + + src/main/resources + + + + + src/main/java + + **/*.xml + + false + + + + + + + + + org.springframework.boot + spring-boot-maven-plugin + 2.7.0 + + ${project.build.finalName} + + true + + + + + + repackage + + + + + + + + + + + org.apache.maven.plugins + maven-surefire-plugin + 2.22.1 + + true + + + + + + + + + + aliyun-repos + aliyun-repos + https://maven.aliyun.com/nexus/content/groups/public/ + + + true + + + + false + + + + + + + + aliyun-plugin + aliyun-plugin + https://maven.aliyun.com/nexus/content/groups/public/ + + true + + + false + + + + + diff --git a/smallboot-api/system/pom.xml b/smallboot-api/system/pom.xml new file mode 100644 index 0000000..d91bb8a --- /dev/null +++ b/smallboot-api/system/pom.xml @@ -0,0 +1,49 @@ + + + + 4.0.0 + + + smallboot-api + com.zhengqing + ${revision} + + + system + + ${project.artifactId} + ${revision} + jar + + 系统模块 + + + + + + + + com.zhengqing + core + + + + + + me.zhyd.oauth + JustAuth + 1.15.8 + + + + com.squareup.okhttp3 + okhttp + 4.4.1 + + + + + + diff --git a/smallboot-api/system/src/main/java/com/zhengqing/system/api/AuthController.java b/smallboot-api/system/src/main/java/com/zhengqing/system/api/AuthController.java new file mode 100644 index 0000000..14245f7 --- /dev/null +++ b/smallboot-api/system/src/main/java/com/zhengqing/system/api/AuthController.java @@ -0,0 +1,43 @@ +package com.zhengqing.common.auth.api; + + +import cn.dev33.satoken.stp.StpUtil; +import com.zhengqing.common.auth.model.dto.AuthLoginDTO; +import com.zhengqing.common.auth.model.vo.AuthLoginVO; +import com.zhengqing.common.auth.service.IAuthService; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +/** + *

授权认证

+ * + * @author zhengqingya + * @description + * @date 2021/10/5 2:36 下午 + */ +@Slf4j +@RestController +@RequestMapping("/auth") +@Api(tags = "授权认证api") +@RequiredArgsConstructor +public class AuthController { + + private final IAuthService authService; + + @PostMapping("login") + @ApiOperation("登录") + public AuthLoginVO login(@Validated @RequestBody AuthLoginDTO params) { + return this.authService.login(params); + } + + @DeleteMapping("logout") + @ApiOperation("注销登录") + public void logout() { + StpUtil.logout(); + } + +} diff --git a/smallboot-api/system/src/main/java/com/zhengqing/system/api/SysAdminController.java b/smallboot-api/system/src/main/java/com/zhengqing/system/api/SysAdminController.java new file mode 100644 index 0000000..1b6e173 --- /dev/null +++ b/smallboot-api/system/src/main/java/com/zhengqing/system/api/SysAdminController.java @@ -0,0 +1,66 @@ +package com.zhengqing.system.api; + +import com.zhengqing.common.base.constant.ServiceConstant; +import com.zhengqing.common.core.api.BaseController; +import com.zhengqing.common.db.mapper.MyBaseMapper; +import com.zhengqing.system.model.vo.SysRoleRePermListVO; +import com.zhengqing.system.service.ISysDictService; +import com.zhengqing.system.service.ISysPermissionBusinessService; +import com.zhengqing.system.service.ISysPermissionService; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.util.List; + +/** + *

+ * 管理员api + *

+ * + * @author zhengqingya + * @description + * @date 2019/8/19 13:49 + */ +@RestController +@RequestMapping(ServiceConstant.SERVICE_API_PREFIX_WEB_SYSTEM + "/admin") +@Api(tags = "管理员api") +@RequiredArgsConstructor +public class SysAdminController extends BaseController { + + private final ISysPermissionService sysPermissionService; + private final ISysPermissionBusinessService sysPermissionBusinessService; + private final MyBaseMapper myBaseMapper; + private final ISysDictService sysDictService; + + @ApiOperation("获取角色权限映射数据") + @GetMapping("listRoleRePerm") + public List listRoleRePerm() { + return this.sysPermissionService.listRoleRePerm(); + } + + @ApiOperation("刷新Redis缓存中的角色菜单权限") + @GetMapping("refreshRedisPerm") + public void refreshRedisPerm() { + this.sysPermissionBusinessService.refreshRedisPerm(); + } + + /** + * http://127.0.0.1:20010/initDb + */ + @ApiOperation("初始化DB(谨慎操作)") + @GetMapping("initDb") + public String initDb() { +// String initDbSql = "DROP DATABASE IF EXISTS `smallboot`; " + +// "CREATE DATABASE `smallboot` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; "; +// String initSql = "USE `smallboot`; " + MyFileUtil.readFileContent(ProjectConstant.PROJECT_ROOT_DIRECTORY + "/doc/sql/smallboot.sql"); +// this.myBaseMapper.execSql(initDbSql); +// this.myBaseMapper.execSql(initSql); + return "OK"; + } + + +} diff --git a/smallboot-api/system/src/main/java/com/zhengqing/system/api/SysDictController.java b/smallboot-api/system/src/main/java/com/zhengqing/system/api/SysDictController.java new file mode 100644 index 0000000..979951a --- /dev/null +++ b/smallboot-api/system/src/main/java/com/zhengqing/system/api/SysDictController.java @@ -0,0 +1,95 @@ +package com.zhengqing.system.api; + +import com.google.common.collect.Lists; +import com.zhengqing.common.base.constant.ServiceConstant; +import com.zhengqing.common.core.api.BaseController; +import com.zhengqing.common.core.custom.validator.common.UpdateGroup; +import com.zhengqing.common.core.custom.validator.common.ValidList; +import com.zhengqing.system.model.dto.SysDictSaveBatchDTO; +import com.zhengqing.system.model.dto.SysDictSaveDTO; +import com.zhengqing.system.model.vo.SysDictVO; +import com.zhengqing.system.service.ISysDictService; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import lombok.extern.slf4j.Slf4j; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import java.util.List; +import java.util.Map; + +/** + *

+ * 基础模块 - 数据字典接口 + *

+ * + * @author zhengqingya + * @description + * @date 2020/4/15 20:50 + */ +@Slf4j +@RestController +@RequestMapping(ServiceConstant.SERVICE_API_PREFIX_WEB_SYSTEM + "/dict") +@Api(tags = "基础模块 - 数据字典接口") +public class SysDictController extends BaseController { + + @Resource + private ISysDictService sysDictService; + + @PostMapping("initCache") + @ApiOperation("初始化缓存数据") + public void initCache() { + this.sysDictService.initCache(); + } + + @GetMapping("listByCode") + @ApiOperation("通过编码获取数据字典列表信息(启用+禁用数据)") + public List listByCode(@RequestParam String code) { + return this.sysDictService.listByCode(code); + } + + @GetMapping("listByOpenCode") + @ApiOperation("通过编码获取数据字典列表(只含启用数据)") + public Map> listByOpenCode(@RequestParam List codeList) { + return this.sysDictService.listByOpenCode(codeList); + } + + @GetMapping("listFromDbByCode") + @ApiOperation("通过编码获取数据字典列表信息 - 数据库方式(只含启用数据)") + public List listFromDbByCode(@RequestParam String code) { + return this.sysDictService.listFromDbByOpenCode(Lists.newArrayList(code)).get(code); + } + + @GetMapping("listFromCacheByCode") + @ApiOperation("通过编码获取数据字典列表信息 - 缓存方式(只含启用数据)") + public List listFromCacheByCode(@RequestParam String code) { + return this.sysDictService.listFromCacheByCode(Lists.newArrayList(code)).get(code); + } + + @PostMapping("") + @ApiOperation("新增") + public Integer add(@Validated @RequestBody SysDictSaveDTO params) { + params.setId(null); + return this.sysDictService.addOrUpdateData(params); + } + + @PutMapping("") + @ApiOperation("更新") + public Integer update(@Validated(UpdateGroup.class) @RequestBody SysDictSaveDTO params) { + return this.sysDictService.addOrUpdateData(params); + } + + @PutMapping("updateBatch") + @ApiOperation("批量更新") + public void updateBatch(@Validated @RequestBody Map> dictDataMap) { + this.sysDictService.addOrUpdateBatch(dictDataMap, false); + } + + @DeleteMapping("") + @ApiOperation("删除") + public void delete(@RequestParam Integer id) { + this.sysDictService.deleteDictById(id); + } + +} diff --git a/smallboot-api/system/src/main/java/com/zhengqing/system/api/SysDictTypeController.java b/smallboot-api/system/src/main/java/com/zhengqing/system/api/SysDictTypeController.java new file mode 100644 index 0000000..f054f43 --- /dev/null +++ b/smallboot-api/system/src/main/java/com/zhengqing/system/api/SysDictTypeController.java @@ -0,0 +1,64 @@ +package com.zhengqing.system.api; + +import com.zhengqing.common.base.constant.ServiceConstant; +import com.zhengqing.common.core.api.BaseController; +import com.zhengqing.common.core.custom.validator.common.CreateGroup; +import com.zhengqing.common.core.custom.validator.common.UpdateGroup; +import com.zhengqing.system.entity.SysDictType; +import com.zhengqing.system.model.dto.SysDictTypeSaveDTO; +import com.zhengqing.system.service.ISysDictTypeService; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import lombok.extern.slf4j.Slf4j; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import java.util.Comparator; +import java.util.List; +import java.util.stream.Collectors; + +/** + *

+ * 基础模块 - 数据字典类型接口 + *

+ * + * @author zhengqingya + * @description + * @date 2020/4/15 20:50 + */ +@Slf4j +@RestController +@RequestMapping(ServiceConstant.SERVICE_API_PREFIX_WEB_SYSTEM + "/dict/type") +@Api(tags = "基础模块 - 数据字典类型接口") +public class SysDictTypeController extends BaseController { + + @Resource + private ISysDictTypeService dictTypeService; + + @GetMapping("list") + @ApiOperation("列表") + public List list() { + return this.dictTypeService.list().stream().sorted(Comparator.comparing(SysDictType::getSort)).collect(Collectors.toList()); + } + + @PostMapping("") + @ApiOperation("新增") + public Integer add(@Validated(CreateGroup.class) @RequestBody SysDictTypeSaveDTO params) { + params.setId(null); + return this.dictTypeService.addOrUpdateData(params); + } + + @PutMapping("") + @ApiOperation("更新") + public Integer update(@Validated(UpdateGroup.class) @RequestBody SysDictTypeSaveDTO params) { + return this.dictTypeService.addOrUpdateData(params); + } + + @DeleteMapping("") + @ApiOperation("删除") + public void delete(@RequestParam Integer id) { + this.dictTypeService.deleteType(id); + } + +} diff --git a/smallboot-api/system/src/main/java/com/zhengqing/system/api/SysIndexController.java b/smallboot-api/system/src/main/java/com/zhengqing/system/api/SysIndexController.java new file mode 100644 index 0000000..b1eb8be --- /dev/null +++ b/smallboot-api/system/src/main/java/com/zhengqing/system/api/SysIndexController.java @@ -0,0 +1,42 @@ +package com.zhengqing.system.api; + +import com.zhengqing.common.base.constant.ServiceConstant; +import com.zhengqing.common.core.api.BaseController; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +/** + *

+ * 首页 + *

+ * + * @author zhengqingya + * @description + * @date 2019/8/19 13:49 + */ +@RestController +@RequestMapping(ServiceConstant.SERVICE_API_PREFIX_WEB_SYSTEM + "") +@Api(tags = "首页 - 接口") +public class SysIndexController extends BaseController { + + @Value(value = "${spring.application.name}") + private String applicationName; + + @GetMapping("/index") + @ApiOperation(value = "首页") + public String index() { + return "您好,欢迎访问【" + this.applicationName + "】"; + } + + @GetMapping("/hello") + public String hello() throws InterruptedException { + // 模拟业务耗时处理流程 + Thread.sleep(2 * 1000L); + return "hello"; + } + +} diff --git a/smallboot-api/system/src/main/java/com/zhengqing/system/api/SysMenuController.java b/smallboot-api/system/src/main/java/com/zhengqing/system/api/SysMenuController.java new file mode 100644 index 0000000..a2a19e9 --- /dev/null +++ b/smallboot-api/system/src/main/java/com/zhengqing/system/api/SysMenuController.java @@ -0,0 +1,110 @@ +package com.zhengqing.system.api; + +import com.zhengqing.common.base.constant.ServiceConstant; +import com.zhengqing.common.core.api.BaseController; +import com.zhengqing.common.core.custom.repeatsubmit.NoRepeatSubmit; +import com.zhengqing.common.core.custom.validator.common.UpdateGroup; +import com.zhengqing.system.model.dto.SysMenuReBtnPermSaveDTO; +import com.zhengqing.system.model.dto.SysMenuSaveDTO; +import com.zhengqing.system.model.vo.SysMenuReBtnPermListVO; +import com.zhengqing.system.model.vo.SysMenuTreeVO; +import com.zhengqing.system.service.ISysMenuService; +import com.zhengqing.system.service.ISysPermissionService; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import lombok.extern.slf4j.Slf4j; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import java.util.List; + +/** + *

+ * 系统管理 - 菜单表接口 + *

+ * + * @author zhengqingya + * @description + * @date 2020/4/15 18:52 + */ +@Slf4j +@RestController +@RequestMapping(ServiceConstant.SERVICE_API_PREFIX_WEB_SYSTEM + "/menu") +@Api(tags = "系统管理 - 菜单表接口") +public class SysMenuController extends BaseController { + + @Resource + private ISysMenuService menuService; + + @Resource + private ISysPermissionService sysPermissionService; + + // @GetMapping("/listPage") + // @ApiOperation("列表分页") + // public IPage listPage(@ModelAttribute SysMenuDTO params) { + // return menuService.listPage(params); + // } + // + // @GetMapping("/list") + // @ApiOperation("列表") + // public List list(@ModelAttribute SysMenuDTO params) { + // return menuService.list(params); + // } + + @GetMapping("menuTree") + @ApiOperation("菜单树") + public List menuTree() { + return this.menuService.tree(); + } + + @NoRepeatSubmit + @PostMapping("") + @ApiOperation("新增") + public Integer add(@Validated @RequestBody SysMenuSaveDTO params) { + return this.menuService.addOrUpdateData(params); + } + + @NoRepeatSubmit + @PutMapping("") + @ApiOperation("更新") + public Integer update(@Validated(UpdateGroup.class) @RequestBody SysMenuSaveDTO params) { + return this.menuService.addOrUpdateData(params); + } + + @DeleteMapping("") + @ApiOperation("删除") + public void delete(@RequestParam Integer menuId) { + this.menuService.removeById(menuId); + } + + // 下:菜单按钮权限(菜单页面中配置页面所属按钮使用) + + @GetMapping("getPermListByMenuId") + @ApiOperation("菜单关联按钮权限-列表") + public List getPermListByMenuId(@RequestParam Integer menuId) { + return this.sysPermissionService.getPermListByMenuId(menuId); + } + + @DeleteMapping("deleteMenuReBtnPerm") + @ApiOperation("菜单关联按钮权限-删除") + public void deleteMenuReBtnPerm(@RequestParam Integer id) { + this.sysPermissionService.removeById(id); + } + + @NoRepeatSubmit + @PostMapping("addMenuReBtnPerm") + @ApiOperation("菜单关联按钮权限-新增") + public void addMenuReBtnPerm(@Validated @RequestBody SysMenuReBtnPermSaveDTO params) { + params.setId(null); + this.sysPermissionService.addOrUpdateData(params); + } + + @NoRepeatSubmit + @PutMapping("updateMenuReBtnPerm") + @ApiOperation("菜单关联按钮权限-更新") + public void updateMenuReBtnPerm(@Validated(UpdateGroup.class) @RequestBody SysMenuReBtnPermSaveDTO params) { + this.sysPermissionService.addOrUpdateData(params); + } + +} diff --git a/smallboot-api/system/src/main/java/com/zhengqing/system/api/SysOauthController.java b/smallboot-api/system/src/main/java/com/zhengqing/system/api/SysOauthController.java new file mode 100644 index 0000000..13ef72b --- /dev/null +++ b/smallboot-api/system/src/main/java/com/zhengqing/system/api/SysOauthController.java @@ -0,0 +1,68 @@ +package com.zhengqing.system.api; + +import com.zhengqing.common.base.constant.ServiceConstant; +import com.zhengqing.common.core.api.BaseController; +import com.zhengqing.system.model.dto.SysOauthRemoveBindDTO; +import com.zhengqing.system.model.dto.SysOauthSaveDTO; +import com.zhengqing.system.model.vo.SysOauthDataListVO; +import com.zhengqing.system.service.ISysOauthService; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import lombok.extern.slf4j.Slf4j; +import me.zhyd.oauth.model.AuthCallback; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import javax.servlet.http.HttpServletResponse; +import java.util.List; + +/** + *

+ * 系统管理 - 用户三方授权 Controller + *

+ * + * @author zhengqingya + * @description 可参考: https://justauth.wiki + * @date 2020/6/21 21:18 + */ +@Slf4j +@RestController +@RequestMapping(ServiceConstant.SERVICE_API_PREFIX_WEB_SYSTEM + "/oauth") +@Api(tags = "系统管理 - 三方登录接口") +public class SysOauthController extends BaseController { + + @Resource + private ISysOauthService sysOauthService; + + @RequestMapping("{oauthType}") + @ApiOperation("第三方账号授权") + public String handleOauth(@PathVariable String oauthType, HttpServletResponse response) { + return this.sysOauthService.handleOauth(oauthType, response); + } + + @RequestMapping("{oauthType}/callback") + @ApiOperation("第三方账号授权回调处理") + public void handleCallback(@PathVariable String oauthType, AuthCallback callback, HttpServletResponse response) { + this.sysOauthService.handleCallback(oauthType, callback, response); + } + + @PostMapping("bindThirdPart") + @ApiOperation("第三方账号绑定") + public Integer handleBindThirdPartData(@Validated @RequestBody SysOauthSaveDTO params) { + return this.sysOauthService.addOrUpdateData(params); + } + + @GetMapping("getOauthDataList") + @ApiOperation("获取第三方账号绑定授权数据") + public List getOauthDataList(@RequestParam Integer userId) { + return this.sysOauthService.getOauthDataList(userId); + } + + @PostMapping("removeBind") + @ApiOperation("解除第三方账号绑定") + public void removeBind(@RequestBody SysOauthRemoveBindDTO params) { + this.sysOauthService.removeBind(params); + } + +} diff --git a/smallboot-api/system/src/main/java/com/zhengqing/system/api/SysPropertyController.java b/smallboot-api/system/src/main/java/com/zhengqing/system/api/SysPropertyController.java new file mode 100644 index 0000000..5627de2 --- /dev/null +++ b/smallboot-api/system/src/main/java/com/zhengqing/system/api/SysPropertyController.java @@ -0,0 +1,60 @@ +package com.zhengqing.system.api; + +import com.zhengqing.common.base.constant.ServiceConstant; +import com.zhengqing.common.core.api.BaseController; +import com.zhengqing.common.core.custom.repeatsubmit.NoRepeatSubmit; +import com.zhengqing.common.core.custom.validator.common.ValidList; +import com.zhengqing.system.model.dto.SysPropertySaveDTO; +import com.zhengqing.system.model.vo.SysPropertyVO; +import com.zhengqing.system.service.ISysPropertyService; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import java.util.List; +import java.util.Map; + + +/** + *

系统管理-系统属性 接口

+ * + * @author zhengqingya + * @description + * @date 2021/09/06 22:57 + */ +@RestController +@RequestMapping(ServiceConstant.SERVICE_API_PREFIX_WEB_SYSTEM + "/property") +@Api(tags = {"系统管理 - 系统属性接口"}) +public class SysPropertyController extends BaseController { + + @Resource + private ISysPropertyService sysPropertyService; + + @GetMapping("listByKey") + @ApiOperation("根据属性key查询") + public Map listByKey(@RequestParam List keyList) { + return this.sysPropertyService.mapByKey(keyList); + } + + @GetMapping("list") + @ApiOperation("列表") + public List list(@RequestParam List keyList) { + return this.sysPropertyService.listByKey(keyList); + } + + @NoRepeatSubmit + @PostMapping("saveBatch") + @ApiOperation("批量保存") + public void saveBatch(@Validated @RequestBody ValidList dataList) { + this.sysPropertyService.saveBatch(dataList); + } + + @DeleteMapping("deleteByKey") + @ApiOperation("根据属性key删除数据") + public void deleteByKey(@RequestParam String key) { + this.sysPropertyService.deleteByKey(key); + } + +} diff --git a/smallboot-api/system/src/main/java/com/zhengqing/system/api/SysRoleController.java b/smallboot-api/system/src/main/java/com/zhengqing/system/api/SysRoleController.java new file mode 100644 index 0000000..55c0a76 --- /dev/null +++ b/smallboot-api/system/src/main/java/com/zhengqing/system/api/SysRoleController.java @@ -0,0 +1,126 @@ +package com.zhengqing.system.api; + +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.zhengqing.common.base.constant.ServiceConstant; +import com.zhengqing.common.core.api.BaseController; +import com.zhengqing.common.core.custom.repeatsubmit.NoRepeatSubmit; +import com.zhengqing.common.core.custom.validator.common.UpdateGroup; +import com.zhengqing.system.model.dto.*; +import com.zhengqing.system.model.vo.SysRoleAllPermissionDetailVO; +import com.zhengqing.system.model.vo.SysRoleListVO; +import com.zhengqing.system.model.vo.SysRolePermissionDetailVO; +import com.zhengqing.system.service.ISysPermissionService; +import com.zhengqing.system.service.ISysRoleMenuService; +import com.zhengqing.system.service.ISysRolePermissionService; +import com.zhengqing.system.service.ISysRoleService; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import lombok.extern.slf4j.Slf4j; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import java.util.List; + +/** + *

+ * 系统管理 - 角色管理接口 + *

+ * + * @author zhengqingya + * @description + * @date 2020/9/10 18:33 + */ +@Slf4j +@RestController +@RequestMapping(ServiceConstant.SERVICE_API_PREFIX_WEB_SYSTEM + "/role") +@Api(tags = "系统管理 - 角色管理接口") +public class SysRoleController extends BaseController { + + @Resource + private ISysRoleService roleService; + + @Resource + private ISysPermissionService sysMenuBtnService; + + @Resource + private ISysRoleMenuService sysRoleMenuService; + + @Resource + private ISysRolePermissionService sysRoleMenuBtnService; + + @GetMapping("listPage") + @ApiOperation("列表分页") + public IPage listPage(@ModelAttribute SysRoleListDTO params) { + return this.roleService.listPage(params); + } + + @GetMapping("list") + @ApiOperation("列表") + public List list(@ModelAttribute SysRoleListDTO params) { + return this.roleService.list(params); + } + + @NoRepeatSubmit + @PostMapping("") + @ApiOperation("新增") + public Integer add(@Validated @RequestBody SysRoleSaveDTO params) { + return this.roleService.addOrUpdateData(params); + } + + @NoRepeatSubmit + @PutMapping("") + @ApiOperation("更新") + public Integer update(@Validated(UpdateGroup.class) @RequestBody SysRoleSaveDTO params) { + return this.roleService.addOrUpdateData(params); + } + + @GetMapping("detail") + @ApiOperation("详情(角色基本信息+菜单ids)") + public SysRolePermissionDetailVO detail(@RequestParam Integer roleId) { + return this.roleService.detail(roleId); + } + + @GetMapping("permissionDetail") + @ApiOperation("详情(带树+按钮+所拥有的权限)") + public SysRoleAllPermissionDetailVO permissionDetail(@RequestParam Integer roleId) { + return this.roleService.permissionDetail(roleId); + } + + @DeleteMapping("") + @ApiOperation("删除") + public void delete(@RequestParam Integer roleId) { + this.roleService.deleteRoleAndRoleMenu(roleId); + } + + // ======================== ↓↓↓↓↓↓ 角色关联菜单按钮权限 ↓↓↓↓↓↓ ========================== + + @GetMapping("getPermissionBtnsByRoleIdAndMenuId") + @ApiOperation("通过角色id和菜单id查询该菜单所拥有的所有按钮") + public List getPermissionBtnsByRoleIdAndMenuId(@RequestParam Integer roleId, + @RequestParam Integer menuId) { + return this.sysRoleMenuBtnService.getPermissionBtnsByRoleIdAndMenuId(roleId, menuId); + } + + @NoRepeatSubmit + @PostMapping("saveRoleMenuIds") + @ApiOperation("保存角色关联菜单ids") + public void saveRoleMenuIds(@Validated @RequestBody SysRoleMenuSaveDTO params) { + this.sysRoleMenuService.saveRoleMenuIds(params); + } + + @NoRepeatSubmit + @PostMapping("saveRoleMenuBtnIds") + @ApiOperation("保存角色关联菜单按钮ids") + public void saveRoleMenuBtnIds(@Validated @RequestBody SysRoleMenuBtnSaveDTO params) { + this.sysRoleMenuBtnService.saveRoleMenuBtnIds(params); + } + + @NoRepeatSubmit + @PostMapping("saveRolePermission") + @ApiOperation("保存角色权限(菜单权限+按钮权限)") + public void saveRolePermission(@Validated @RequestBody SysRolePermissionSaveDTO params) { + this.sysRoleMenuService.saveRolePermission(params); + } + +} diff --git a/smallboot-api/system/src/main/java/com/zhengqing/system/api/SysUserController.java b/smallboot-api/system/src/main/java/com/zhengqing/system/api/SysUserController.java new file mode 100644 index 0000000..09e4f11 --- /dev/null +++ b/smallboot-api/system/src/main/java/com/zhengqing/system/api/SysUserController.java @@ -0,0 +1,106 @@ +package com.zhengqing.system.api; + +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.zhengqing.common.base.constant.ServiceConstant; +import com.zhengqing.common.base.context.SysUserContext; +import com.zhengqing.common.core.api.BaseController; +import com.zhengqing.common.core.custom.repeatsubmit.NoRepeatSubmit; +import com.zhengqing.common.core.custom.validator.common.UpdateGroup; +import com.zhengqing.system.model.dto.*; +import com.zhengqing.system.model.vo.SysUserDetailVO; +import com.zhengqing.system.model.vo.SysUserListVO; +import com.zhengqing.system.model.vo.SysUserPermVO; +import com.zhengqing.system.service.ISysUserRoleService; +import com.zhengqing.system.service.ISysUserService; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import javax.validation.Valid; + +/** + *

+ * 系统管理 - 用户管理接口 + *

+ * + * @author zhengqingya + * @description + * @date 2020/4/15 11:43 + */ +@Slf4j +@RestController +@RequiredArgsConstructor +@RequestMapping(ServiceConstant.SERVICE_API_PREFIX_WEB_SYSTEM + "/user") +@Api(tags = "系统管理 - 用户管理接口") +public class SysUserController extends BaseController { + + private final ISysUserService sysUserService; + + private final ISysUserRoleService sysUserRoleService; + + @GetMapping("listPage") + @ApiOperation("列表分页") + public IPage listPage(@ModelAttribute SysUserListDTO params) { + return this.sysUserService.listPage(params); + } + + @NoRepeatSubmit + @PostMapping("") + @ApiOperation("新增") + public Integer add(@Validated @RequestBody SysUserSaveDTO params) { + return this.sysUserService.addOrUpdateData(params); + } + + @NoRepeatSubmit + @PutMapping("") + @ApiOperation("更新") + public Integer update(@Validated(UpdateGroup.class) @RequestBody SysUserSaveDTO params) { + return this.sysUserService.addOrUpdateData(params); + } + + @DeleteMapping("") + @ApiOperation("删除") + public void delete(@RequestParam Integer userId) { + this.sysUserService.deleteUser(userId); + } + + @GetMapping("") + @ApiOperation("详情") + public SysUserDetailVO detail(@RequestParam Integer userId) { + return this.sysUserService.detail(userId); + } + + @PutMapping("updatePassword") + @ApiOperation("修改用户密码") + public void updatePassword(@RequestBody @Valid SysUserUpdatePasswordDTO params) { + this.sysUserService.updatePassword(params); + } + + @GetMapping("resetPassword") + @ApiOperation("重置用户密码") + public void resetPassword(@RequestParam Integer userId, @RequestParam(required = false) String password) { + this.sysUserService.resetPassword(userId, password); + } + + @GetMapping("getUserPerm") + @ApiOperation("获取当前登录用户权限信息") + public SysUserPermVO getUserPerm(@RequestParam(required = false) Integer userId) { + SysUserPermVO userPerm = this.sysUserService.getUserPerm( + SysUserPermDTO.builder() + .userId(userId == null ? SysUserContext.getUserId() : userId) + .build() + ); + userPerm.setPassword(null); + return userPerm; + } + + @PostMapping("saveRoleIds") + @ApiOperation("保存用户角色ids") + public void saveRoleIds(@Validated @RequestBody SysUserRoleSaveDTO params) { + this.sysUserRoleService.addOrUpdateData(params); + } + +} diff --git a/smallboot-api/system/src/main/java/com/zhengqing/system/config/SystemProperty.java b/smallboot-api/system/src/main/java/com/zhengqing/system/config/SystemProperty.java new file mode 100644 index 0000000..36d535a --- /dev/null +++ b/smallboot-api/system/src/main/java/com/zhengqing/system/config/SystemProperty.java @@ -0,0 +1,42 @@ +package com.zhengqing.system.config; + +import com.zhengqing.common.core.config.CommonProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; +import me.zhyd.oauth.config.AuthConfig; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Configuration; + +/** + *

+ * 配置信息 + *

+ * + * @author zhengqingya + * @description + * @date 2019/8/19 9:07 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@Configuration +@ConfigurationProperties(prefix = "smallboot") +public class SystemProperty extends CommonProperty { + + /** + * 第三方授权参数 + */ + private final ThirdpartOauth thirdpartOauth = new ThirdpartOauth(); + + @Data + public static class ThirdpartOauth { + private String redirectUrlPrefix; + private String webRedirectUrl; + private String webBindRedirectUrl; + private AuthConfig gitee; + private AuthConfig github; + private AuthConfig qq; + private AuthConfig giteeBind; + private AuthConfig githubBind; + } + +} diff --git a/smallboot-api/system/src/main/java/com/zhengqing/system/config/SystemRunner.java b/smallboot-api/system/src/main/java/com/zhengqing/system/config/SystemRunner.java new file mode 100644 index 0000000..da634ad --- /dev/null +++ b/smallboot-api/system/src/main/java/com/zhengqing/system/config/SystemRunner.java @@ -0,0 +1,43 @@ +package com.zhengqing.system.config; + +import com.zhengqing.common.core.config.AppCommonRunner; +import com.zhengqing.system.service.ISysDictService; +import com.zhengqing.system.service.ISysPermissionBusinessService; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +/** + *

+ * 服务初始化之后,执行方法 + *

+ * + * @author zhengqingya + * @description + * @date 2020/5/22 19:29 + */ +@Slf4j +@Component +@RequiredArgsConstructor +public class SystemRunner extends AppCommonRunner { + + private final ISysDictService dictService; + + private final ISysPermissionBusinessService sysPermissionBusinessService; + + @Override + public void run(String... args) throws Exception { + super.appRun(); + + log.info("服务初始化之后,执行方法 start..."); + + // 数据字典 + this.dictService.initCache(); + + // 权限缓存 + this.sysPermissionBusinessService.refreshRedisPerm(); + + log.info("服务初始化之后,执行方法 end..."); + } + +} diff --git a/smallboot-api/system/src/main/java/com/zhengqing/system/constant/CanalConstant.java b/smallboot-api/system/src/main/java/com/zhengqing/system/constant/CanalConstant.java new file mode 100644 index 0000000..4e86ffe --- /dev/null +++ b/smallboot-api/system/src/main/java/com/zhengqing/system/constant/CanalConstant.java @@ -0,0 +1,16 @@ +package com.zhengqing.system.constant; + +/** + *

全局常用变量 - canal

+ * + * @author zhengqingya + * @description + * @date 2021/8/9 11:12 + */ +public interface CanalConstant { + + String CANAL_EXCHANGE = "canal.exchange"; + String CANAL_QUEUE = "canal_queue"; + String CANAL_ROUTING_KEY = "canal_routing_key"; + +} diff --git a/smallboot-api/system/src/main/java/com/zhengqing/system/constant/SystemConstant.java b/smallboot-api/system/src/main/java/com/zhengqing/system/constant/SystemConstant.java new file mode 100644 index 0000000..0ee9341 --- /dev/null +++ b/smallboot-api/system/src/main/java/com/zhengqing/system/constant/SystemConstant.java @@ -0,0 +1,37 @@ +package com.zhengqing.system.constant; + +/** + *

+ * 全局常用变量 - system + *

+ * + * @author zhengqingya + * @description + * @date 2021/6/15 14:38 + */ +public interface SystemConstant { + + // =============================================================================== + // ============================ ↓↓↓↓↓↓ redis缓存系列 ↓↓↓↓↓↓ ============================ + // =============================================================================== + + /** + * 数据字典缓存 + */ + String CACHE_SYS_DICT_PREFIX = "smallboot:system:dict:"; + /** + * 系统属性缓存 + */ + String CACHE_SYS_PROPERTY_PREFIX = "smallboot:system:property:"; + /** + * 系统缓存 + */ + String CACHE_SYS_MENU_TREE = "smallboot:system:sys_menu_tree"; + /** + * 个人缓存 + */ + String CACHE_SYS_USER_INFO_PREFIX = "smallboot:system:sys_user_info_"; + String CACHE_SYS_PERMISSION_PREFIX = "smallboot:system:sys_permission_"; + + +} diff --git a/smallboot-api/system/src/main/java/com/zhengqing/system/entity/SysDict.java b/smallboot-api/system/src/main/java/com/zhengqing/system/entity/SysDict.java new file mode 100644 index 0000000..3c4fd40 --- /dev/null +++ b/smallboot-api/system/src/main/java/com/zhengqing/system/entity/SysDict.java @@ -0,0 +1,54 @@ +package com.zhengqing.system.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import com.zhengqing.common.db.entity.BaseEntity; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.*; + +/** + *

+ * 数据字典 + *

+ * + * @author zhengqingya + * @description + * @date 2020/4/15 20:57 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@Builder +@AllArgsConstructor +@NoArgsConstructor +@ApiModel("数据字典") +@TableName("t_sys_dict") +public class SysDict extends BaseEntity { + + @ApiModelProperty(value = "主键ID") + @TableId(value = "id", type = IdType.AUTO) + private Integer id; + + @ApiModelProperty(value = "字典类型id") + private Integer dictTypeId; + + @ApiModelProperty(value = "字典类型编码") + private String code; + + @ApiModelProperty(value = "字典名") + private String name; + + @ApiModelProperty(value = "字典值") + private String value; + + @ApiModelProperty(value = "状态(0->停用 1->正常)") + private Integer status; + + @ApiModelProperty(value = "排序") + private Integer sort; + + @ApiModelProperty(value = "备注") + private String remark; + +} diff --git a/smallboot-api/system/src/main/java/com/zhengqing/system/entity/SysDictType.java b/smallboot-api/system/src/main/java/com/zhengqing/system/entity/SysDictType.java new file mode 100644 index 0000000..ea7d71a --- /dev/null +++ b/smallboot-api/system/src/main/java/com/zhengqing/system/entity/SysDictType.java @@ -0,0 +1,48 @@ +package com.zhengqing.system.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import com.zhengqing.common.db.entity.BaseEntity; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.*; + +/** + *

+ * 数据字典类型表 + *

+ * + * @author zhengqingya + * @description + * @date 2020/4/15 20:57 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@Builder +@AllArgsConstructor +@NoArgsConstructor +@ApiModel("数据字典类型表") +@TableName("t_sys_dict_type") +public class SysDictType extends BaseEntity { + + @ApiModelProperty(value = "主键ID") + @TableId(value = "ID", type = IdType.AUTO) + private Integer id; + + @ApiModelProperty(value = "字典类型编码") + private String code; + + @ApiModelProperty(value = "字典类型名称(展示用)") + private String name; + + @ApiModelProperty(value = "状态(0->停用 1->正常)") + private Integer status; + + @ApiModelProperty(value = "排序") + private Integer sort; + + @ApiModelProperty(value = "是否固定(false->否 true->是)") + private Boolean isFixed; + +} diff --git a/smallboot-api/system/src/main/java/com/zhengqing/system/entity/SysMenu.java b/smallboot-api/system/src/main/java/com/zhengqing/system/entity/SysMenu.java new file mode 100644 index 0000000..83768ed --- /dev/null +++ b/smallboot-api/system/src/main/java/com/zhengqing/system/entity/SysMenu.java @@ -0,0 +1,72 @@ +package com.zhengqing.system.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import com.zhengqing.common.db.entity.IsDeletedBaseEntity; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.*; + +/** + *

+ * 系统管理 - 菜单表 + *

+ * + * @author zhengqingya + * @description + * @date 2020/4/15 18:44 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@Builder +@AllArgsConstructor +@NoArgsConstructor +@ApiModel("系统管理 - 菜单表") +@TableName("t_sys_menu") +public class SysMenu extends IsDeletedBaseEntity { + + @ApiModelProperty(value = "菜单ID") + @TableId(type = IdType.AUTO) + private Integer menuId; + + @ApiModelProperty(value = "菜单名称") + private String title; + + @ApiModelProperty(value = "菜单名称 - 英文") + private String name; + + @ApiModelProperty(value = "菜单图标") + private String icon; + + @ApiModelProperty(value = "菜单链接url") + private String path; + + @ApiModelProperty(value = "父类菜单ID") + private Integer parentId; + + @ApiModelProperty(value = "菜单排序") + private Integer sort; + + @ApiModelProperty(value = "组件名") + private String component; + + @ApiModelProperty(value = "是否隐藏 true:隐藏 false:显示") + private Boolean hidden; + + @ApiModelProperty(value = "重定向路径") + private String redirect; + + @ApiModelProperty(value = "菜单状态 1:启用 0:禁用") + private Integer status; + + @ApiModelProperty(value = "菜单类型 0菜单 1按钮") + private Integer type; + + @ApiModelProperty(value = "是否总是显示 0:不显示 1:显示") + private Integer alwaysShow; + + @ApiModelProperty(value = "面包屑") + private Boolean breadcrumb; + +} diff --git a/smallboot-api/system/src/main/java/com/zhengqing/system/entity/SysOauth.java b/smallboot-api/system/src/main/java/com/zhengqing/system/entity/SysOauth.java new file mode 100644 index 0000000..e42085c --- /dev/null +++ b/smallboot-api/system/src/main/java/com/zhengqing/system/entity/SysOauth.java @@ -0,0 +1,42 @@ +package com.zhengqing.system.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import com.zhengqing.common.db.entity.BaseEntity; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.*; + +/** + *

+ * 系统管理 - 用户三方授权表 + *

+ * + * @author zhengqingya + * @description + * @date 2020/11/28 22:14 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@Builder +@AllArgsConstructor +@NoArgsConstructor +@TableName("t_sys_user_re_oauth") +@ApiModel("系统管理 - 用户三方授权表") +public class SysOauth extends BaseEntity { + + @ApiModelProperty(value = "主键ID") + @TableId(value = "user_re_oauth_id", type = IdType.AUTO) + private Integer userReOauthId; + + @ApiModelProperty(value = "用户id(关联表`t_sys_user`字段`user_id`)") + private Integer userId; + + @ApiModelProperty(value = "三方id") + private String openId; + + @ApiModelProperty(value = "第三方授权类型") + private Integer oauthType; + +} diff --git a/smallboot-api/system/src/main/java/com/zhengqing/system/entity/SysOauthClient.java b/smallboot-api/system/src/main/java/com/zhengqing/system/entity/SysOauthClient.java new file mode 100644 index 0000000..9353238 --- /dev/null +++ b/smallboot-api/system/src/main/java/com/zhengqing/system/entity/SysOauthClient.java @@ -0,0 +1,74 @@ +package com.zhengqing.system.entity; + +import com.baomidou.mybatisplus.annotation.*; +import com.baomidou.mybatisplus.extension.activerecord.Model; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.*; + +import javax.validation.constraints.Past; +import java.util.Date; + +/** + *

oauth客户端

+ * + * @author zhengqingya + * @description + * @date 2022/06/10 16:25 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@Builder +@AllArgsConstructor +@NoArgsConstructor +@TableName("t_sys_oauth_client") +@ApiModel +public class SysOauthClient extends Model { + + @ApiModelProperty("客户端ID,唯一标识") + @TableId(value = "client_id", type = IdType.INPUT) + private String clientId; + + @ApiModelProperty("客户端访问秘钥,BCryptPasswordEncoder加密算法加密") + private String clientSecret; + + @ApiModelProperty("可访问资源id(英文逗号分隔)") + private String resourceIds; + + @ApiModelProperty("授权范围(英文逗号分隔)") + private String scope; + + @ApiModelProperty("授权类型(英文逗号分隔)") + private String authorizedGrantTypes; + + @ApiModelProperty("重定向uri") + private String webServerRedirectUri; + + @ApiModelProperty("@PreAuthorize(\"hasAuthority('admin')\")可以在方法上标志 用户或者说client 需要说明样的权限\n" + + "指定客户端所拥有的Spring Security的权限值\n" + + "(英文逗号分隔)") + private String authorities; + + @ApiModelProperty("令牌有效期(单位:秒)") + private Integer accessTokenValidity; + + @ApiModelProperty("刷新令牌有效期(单位:秒)") + private Integer refreshTokenValidity; + + @ApiModelProperty("预留字段,在Oauth的流程中没有实际的使用(JSON格式数据)") + private String additionalInformation; + + @ApiModelProperty("设置用户是否自动Approval操作, 默认值为 'false' 可选值包括 'true','false', 'read','write'.\n" + + "该字段只适用于grant_type=\"authorization_code\"的情况,当用户登录成功后,若该值为'true'或支持的scope值,则会跳过用户Approve的页面, 直接授权") + private String autoapprove; + + @ApiModelProperty(value = "创建人id") + @TableField(value = "create_by", fill = FieldFill.INSERT) + private Long createBy; + + @ApiModelProperty(value = "创建时间") + @TableField(value = "create_time", fill = FieldFill.INSERT) + @Past(message = "创建时间必须是过去时间") + private Date createTime; + +} diff --git a/smallboot-api/system/src/main/java/com/zhengqing/system/entity/SysPermission.java b/smallboot-api/system/src/main/java/com/zhengqing/system/entity/SysPermission.java new file mode 100644 index 0000000..c46c669 --- /dev/null +++ b/smallboot-api/system/src/main/java/com/zhengqing/system/entity/SysPermission.java @@ -0,0 +1,47 @@ +package com.zhengqing.system.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import com.zhengqing.common.db.entity.BaseEntity; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + *

+ * 系统管理-菜单关联权限表 + *

+ * + * @author zhengqingya + * @description + * @date 2020/4/15 20:33 + */ +@Data +@Builder +@AllArgsConstructor +@NoArgsConstructor +@ApiModel +@TableName("t_sys_permission") +public class SysPermission extends BaseEntity { + + @ApiModelProperty(value = "id") + @TableId(type = IdType.AUTO) + private Integer id; + + @ApiModelProperty(value = "名称") + private String name; + + @ApiModelProperty(value = "菜单ID") + private Integer menuId; + + @ApiModelProperty(value = "按钮权限标识") + private String btnPerm; + + @ApiModelProperty(value = "URL权限标识") + private String urlPerm; + +} diff --git a/smallboot-api/system/src/main/java/com/zhengqing/system/entity/SysProperty.java b/smallboot-api/system/src/main/java/com/zhengqing/system/entity/SysProperty.java new file mode 100644 index 0000000..1ac4839 --- /dev/null +++ b/smallboot-api/system/src/main/java/com/zhengqing/system/entity/SysProperty.java @@ -0,0 +1,40 @@ +package com.zhengqing.system.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import com.zhengqing.common.db.entity.BaseEntity; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.*; + +/** + *

系统管理-系统属性

+ * + * @author zhengqingya + * @description + * @date 2021/09/06 22:57 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@Builder +@AllArgsConstructor +@NoArgsConstructor +@TableName("t_sys_property") +@ApiModel("系统管理-系统属性") +public class SysProperty extends BaseEntity { + + @ApiModelProperty("主键ID") + @TableId(value = "id", type = IdType.AUTO) + private Integer id; + + @ApiModelProperty("属性key") + private String key; + + @ApiModelProperty("属性value") + private String value; + + @ApiModelProperty("备注") + private String remark; + +} diff --git a/smallboot-api/system/src/main/java/com/zhengqing/system/entity/SysRole.java b/smallboot-api/system/src/main/java/com/zhengqing/system/entity/SysRole.java new file mode 100644 index 0000000..5b79d54 --- /dev/null +++ b/smallboot-api/system/src/main/java/com/zhengqing/system/entity/SysRole.java @@ -0,0 +1,47 @@ +package com.zhengqing.system.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.zhengqing.common.core.custom.fieldrepeat.FieldRepeatValidator; +import com.zhengqing.common.db.entity.BaseEntity; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.*; + +/** + *

+ * 系统管理-角色管理 + *

+ * + * @author zhengqingya + * @description + * @date 2020/4/15 15:08 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@Builder +@AllArgsConstructor +@NoArgsConstructor +@ApiModel("角色管理") +@TableName("t_sys_role") +@FieldRepeatValidator(tableName = "t_sys_role", idDbName = "role_id", fieldNames = "code", dbFieldNames = {"code"}, + message = "角色编号重复,请重新输入!") +@JsonIgnoreProperties(ignoreUnknown = true) +public class SysRole extends BaseEntity { + + @ApiModelProperty(value = "主键ID") + @TableId(value = "role_id", type = IdType.AUTO) + private Integer roleId; + + @ApiModelProperty(value = "角色名") + private String name; + + @ApiModelProperty(value = "角色编号") + private String code; + + @ApiModelProperty(value = "状态(1:开启 0:禁用)") + private Integer status; + +} diff --git a/smallboot-api/system/src/main/java/com/zhengqing/system/entity/SysRoleMenu.java b/smallboot-api/system/src/main/java/com/zhengqing/system/entity/SysRoleMenu.java new file mode 100644 index 0000000..4d2d7ab --- /dev/null +++ b/smallboot-api/system/src/main/java/com/zhengqing/system/entity/SysRoleMenu.java @@ -0,0 +1,39 @@ +package com.zhengqing.system.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import com.zhengqing.common.db.entity.BaseEntity; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.*; + +/** + *

+ * 系统管理-角色菜单关联表 + *

+ * + * @author zhengqingya + * @description + * @date 2020/4/15 20:50 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@Builder +@AllArgsConstructor +@NoArgsConstructor +@ApiModel("系统管理-角色菜单关联表") +@TableName("t_sys_role_menu") +public class SysRoleMenu extends BaseEntity { + + @TableId(type = IdType.AUTO) + @ApiModelProperty(value = "主键ID") + private Integer id; + + @ApiModelProperty(value = "角色ID") + private Integer roleId; + + @ApiModelProperty(value = "菜单ID") + private Integer menuId; + +} diff --git a/smallboot-api/system/src/main/java/com/zhengqing/system/entity/SysRolePermission.java b/smallboot-api/system/src/main/java/com/zhengqing/system/entity/SysRolePermission.java new file mode 100644 index 0000000..0f48204 --- /dev/null +++ b/smallboot-api/system/src/main/java/com/zhengqing/system/entity/SysRolePermission.java @@ -0,0 +1,39 @@ +package com.zhengqing.system.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import com.zhengqing.common.db.entity.BaseEntity; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.*; + +/** + *

+ * 角色关联权限表 + *

+ * + * @author zhengqingya + * @description + * @date 2020/4/15 20:33 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@Builder +@AllArgsConstructor +@NoArgsConstructor +@ApiModel +@TableName("t_sys_role_permission") +public class SysRolePermission extends BaseEntity { + + @ApiModelProperty(value = "主键ID") + @TableId(type = IdType.AUTO) + private Integer id; + + @ApiModelProperty(value = "角色ID") + private Integer roleId; + + @ApiModelProperty(value = "权限ID") + private Integer permissionId; + +} diff --git a/smallboot-api/system/src/main/java/com/zhengqing/system/entity/SysUser.java b/smallboot-api/system/src/main/java/com/zhengqing/system/entity/SysUser.java new file mode 100644 index 0000000..b2cf476 --- /dev/null +++ b/smallboot-api/system/src/main/java/com/zhengqing/system/entity/SysUser.java @@ -0,0 +1,59 @@ +package com.zhengqing.system.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import com.zhengqing.common.core.enums.UserSexEnum; +import com.zhengqing.common.db.entity.IsDeletedBaseEntity; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.*; + +/** + *

+ * 系统管理-用户基础信息表 + *

+ * + * @author zhengqingya + * @date 2019-08-19 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@Builder +@AllArgsConstructor +@NoArgsConstructor +@ApiModel("系统管理-用户基础信息表") +@TableName("t_sys_user") +// 对注解分组的排序,可以通脱他判断先后顺序 +// @GroupSequence({FieldRepeatValidator.class,NotNull.class, Default.class}) +// @JsonIgnoreProperties(ignoreUnknown = true) +public class SysUser extends IsDeletedBaseEntity { + + @ApiModelProperty(value = "主键ID") + @TableId(type = IdType.AUTO) + private Integer userId; + + @ApiModelProperty(value = "账号") + private String username; + + @ApiModelProperty(value = "登录密码") + private String password; + + @ApiModelProperty(value = "昵称") + private String nickname; + + @ApiModelProperty(value = "性别") + @TableField(value = "sex") + private UserSexEnum sexEnum; + + @ApiModelProperty(value = "手机号码") + private String phone; + + @ApiModelProperty(value = "邮箱") + private String email; + + @ApiModelProperty(value = "头像") + private String avatarUrl; + +} diff --git a/smallboot-api/system/src/main/java/com/zhengqing/system/entity/SysUserRole.java b/smallboot-api/system/src/main/java/com/zhengqing/system/entity/SysUserRole.java new file mode 100644 index 0000000..d8518fe --- /dev/null +++ b/smallboot-api/system/src/main/java/com/zhengqing/system/entity/SysUserRole.java @@ -0,0 +1,39 @@ +package com.zhengqing.system.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import com.zhengqing.common.db.entity.BaseEntity; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.*; + +/** + *

+ * 系统管理-用户角色关联表 + *

+ * + * @author zhengqingya + * @description + * @date 2020/4/15 18:24 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@Builder +@AllArgsConstructor +@NoArgsConstructor +@ApiModel("系统管理-用户角色关联表") +@TableName("t_sys_user_role") +public class SysUserRole extends BaseEntity { + + @TableId(type = IdType.AUTO) + @ApiModelProperty(value = "主键ID") + private Integer id; + + @ApiModelProperty(value = "用户ID") + private Integer userId; + + @ApiModelProperty(value = "角色ID") + private Integer roleId; + +} diff --git a/smallboot-api/system/src/main/java/com/zhengqing/system/enums/SysCacheTypeEnum.java b/smallboot-api/system/src/main/java/com/zhengqing/system/enums/SysCacheTypeEnum.java new file mode 100644 index 0000000..b12ccfc --- /dev/null +++ b/smallboot-api/system/src/main/java/com/zhengqing/system/enums/SysCacheTypeEnum.java @@ -0,0 +1,24 @@ +package com.zhengqing.system.enums; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + *

+ * 缓存类型 + *

+ * + * @author zhengqingya + * @description + * @date 2020/5/23 1:35 + */ +@Getter +@AllArgsConstructor +public enum SysCacheTypeEnum { + + 系统菜单, + 个人菜单按钮权限, + 个人所有信息, + 系统所有人菜单按钮权限; + +} diff --git a/smallboot-api/system/src/main/java/com/zhengqing/system/enums/SysDictTypeEnum.java b/smallboot-api/system/src/main/java/com/zhengqing/system/enums/SysDictTypeEnum.java new file mode 100644 index 0000000..d6a3ba1 --- /dev/null +++ b/smallboot-api/system/src/main/java/com/zhengqing/system/enums/SysDictTypeEnum.java @@ -0,0 +1,93 @@ +package com.zhengqing.system.enums; + +import com.google.common.collect.Lists; +import com.zhengqing.common.base.exception.MyException; +import lombok.AllArgsConstructor; +import lombok.Getter; + +import java.util.Arrays; +import java.util.List; + +/** + *

+ * 数据字典枚举 + *

+ * + * @author zhengqingya + * @description + * @date 2020/4/12 0:01 + */ +@Getter +@AllArgsConstructor +public enum SysDictTypeEnum { + + 权限按钮("permission_btn", "权限按钮"), + 文件后缀名("file_suffix", "文件后缀名"), + Element_Icon图标("element_icon", "Element_Icon图标"), + 小工具_爬虫_CSDN文章导出数据类型("st_crawler_csdn_export_data_type", "小工具_爬虫_CSDN文章导出数据类型"), + 小工具_数据库_数据源类型("st_db_data_source_type", "小工具_数据库_数据源类型"), + 第三方帐号授权类型("oauth_type", "第三方帐号授权类型"), + + // ====================================== ↓↓↓↓↓↓ 商城 ↓↓↓↓↓↓ ====================================== + + /** + * 商城-商品-tab + */ + MALL_SPU_TAB_CONDITION("mall_spu_tab_condition", "商城-商品-tab"), + /** + * 商城-商品-品类大标题 + */ + MALL_SPU_CATEGORY_BIG_TITLE("mall_spu_category_big_title", "商城-商品-品类大标题"), + /** + * 商城-商品-按钮文案 + */ + MALL_SPU_BUTTON_DOC("mall_spu_button_doc", "商城-商品-按钮文案"), + /** + * 商城-商品-服务 + */ + MALL_SPU_SERVICE("mall_spu_service", "商城-商品-服务"), + /** + * 商城-商品-说明 + */ + MALL_SPU_EXPLAIN("mall_spu_explain", "商城-商品-说明"), + /** + * 商城-订单-tab + */ + MALL_ORDER_TAB_CONDITION_WEB("mall_order_tab_condition_web", "商城-订单-tab-web"), + MALL_ORDER_TAB_CONDITION_MINI("mall_order_tab_condition_mini", "商城-订单-tab-mini"), + /** + * 商城-售后-tab + */ + MALL_ORDER_AFTER_SALE_TAB_CONDITION("mall_order_after_sale_tab_condition", "商城-售后-tab"), + /** + * 商城-订单-发货微信消息通知 + */ + MALL_ORDER_DELIVER_WX_MSG_NOTICE("mall_order_deliver_wx_msg_notice", "商城-订单-发货微信消息通知"); + + private final String code; + private final String desc; + + private static final List LIST = Lists.newArrayList(); + + static { + LIST.addAll(Arrays.asList(SysDictTypeEnum.values())); + } + + /** + * 根据指定的数据字典编码查找相应枚举类 + * + * @param code 数据字典编码 + * @return 数据字典枚举信息 + * @author zhengqingya + * @date 2020/8/30 2:56 + */ + public static SysDictTypeEnum getEnum(String code) { + for (SysDictTypeEnum itemEnum : LIST) { + if (itemEnum.getCode().equals(code)) { + return itemEnum; + } + } + throw new MyException("未找到指定的数据字典编码!"); + } + +} diff --git a/smallboot-api/system/src/main/java/com/zhengqing/system/enums/SysMenuTypeEnum.java b/smallboot-api/system/src/main/java/com/zhengqing/system/enums/SysMenuTypeEnum.java new file mode 100644 index 0000000..4a14823 --- /dev/null +++ b/smallboot-api/system/src/main/java/com/zhengqing/system/enums/SysMenuTypeEnum.java @@ -0,0 +1,24 @@ +package com.zhengqing.system.enums; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + *

+ * 菜单类型 + *

+ * + * @author zhengqingya + * @description + * @date 2020/4/15 17:16 + */ +@Getter +@AllArgsConstructor +public enum SysMenuTypeEnum { + + 菜单(0), + 按钮(1); + + private final Integer type; + +} diff --git a/smallboot-api/system/src/main/java/com/zhengqing/system/enums/SysOauthTypeEnum.java b/smallboot-api/system/src/main/java/com/zhengqing/system/enums/SysOauthTypeEnum.java new file mode 100644 index 0000000..4ba3f8c --- /dev/null +++ b/smallboot-api/system/src/main/java/com/zhengqing/system/enums/SysOauthTypeEnum.java @@ -0,0 +1,49 @@ +package com.zhengqing.system.enums; + +import com.google.common.collect.Lists; +import com.zhengqing.common.base.exception.MyException; +import lombok.AllArgsConstructor; +import lombok.Getter; + +import java.util.Arrays; +import java.util.List; + +/** + *

+ * 授权数据类型 + *

+ * + * @author zhengqingya + * @description + * @date 2020/11/27 17:51 + */ +@Getter +@AllArgsConstructor +public enum SysOauthTypeEnum { + + Gitee(1, "gitee", "Gitee"), + GiteeBind(1, "giteeBind", "Gitee账号绑定"), + GitHub(2, "github", "GitHub"), + GitHubBind(2, "githubBind", "GitHub账号绑定"), + QQ(3, "qq", "QQ"); + + private final Integer oauthTypeValue; + private final String oauthTypeName; + private final String desc; + + private static final List LIST = Lists.newArrayList(); + + static { + LIST.addAll(Arrays.asList(SysOauthTypeEnum.values())); + } + + public static SysOauthTypeEnum getEnum(String oauthTypeName) { + for (SysOauthTypeEnum itemEnum : LIST) { + if (itemEnum.getOauthTypeName().equals(oauthTypeName)) { + return itemEnum; + } + } + throw new MyException("未找到指定的数据类型!"); + } + +} diff --git a/smallboot-api/system/src/main/java/com/zhengqing/system/enums/SysPropertyKeyEnum.java b/smallboot-api/system/src/main/java/com/zhengqing/system/enums/SysPropertyKeyEnum.java new file mode 100644 index 0000000..562169b --- /dev/null +++ b/smallboot-api/system/src/main/java/com/zhengqing/system/enums/SysPropertyKeyEnum.java @@ -0,0 +1,75 @@ +package com.zhengqing.system.enums; + +import com.google.common.collect.Lists; +import com.zhengqing.common.base.exception.MyException; +import io.swagger.annotations.ApiModel; +import lombok.AllArgsConstructor; +import lombok.Getter; + +import java.util.Arrays; +import java.util.List; + +/** + *

系统-系统属性key枚举

+ * + * @author zhengqingya + * @description + * @date 2021/8/19 9:50 + */ +@Getter +@AllArgsConstructor +@ApiModel("系统-系统属性-类型") +public enum SysPropertyKeyEnum { + + // ====================================== ↓↓↓↓↓↓ 商城 ↓↓↓↓↓↓ ====================================== + + /** + * 商城-订单-设置 + */ + MALL_ORDER_SET_AUTO_RECEIVE_MILLISECOND("mall_order_set_auto_receive_millisecond", "864000000", "发货后?毫秒后自动确认收货"), + // MALL_ORDER_SET_UN_PAY_CLOSE_MILLISECOND("mall_order_set_un_pay_close_millisecond", "600000", "待付款订单?毫秒后自动关闭"), + MALL_ORDER_SET_BUYER_APPLY_AFTER_SALE_HANDLE_MILLISECOND("mall_order_set_buyer_apply_after_sale_handle_millisecond", "864000000", "买家发起售后申请?毫秒后,卖家未处理,自动关闭"), + MALL_ORDER_SET_AFTER_SALE_BUYER_DELIVER_MILLISECOND("mall_order_set_after_sale_buyer_deliver_millisecond", "864000000", "待买家发货(买家申请售后,卖家同意后,买家未填写退货返回物流单号)?毫秒后自动关闭"), + MALL_ORDER_SET_BUYER_APPLY_AFTER_SALE_MILLISECOND("mall_order_set_buyer_apply_after_sale_millisecond", "864000000", "买家确认收货?毫秒后无法发起售后申请"), + MALL_ORDER_SET_STOCK_CHECK_TYPE("mall_order_set_stock_check_type", "1", "减库存设置(1:提交订单减库存 2:付款减库存)"); + + private final String key; + private final String value; + private final String desc; + + private static final List LIST = Lists.newArrayList(); + + /** + * 商城-订单-设置-枚举数据 + */ + public static final List LIST_MALL_ORDER_SET = Lists.newArrayList(); + + static { + LIST.addAll(Arrays.asList(SysPropertyKeyEnum.values())); + // 商城-订单-设置-枚举数据 + LIST_MALL_ORDER_SET.add(MALL_ORDER_SET_AUTO_RECEIVE_MILLISECOND); +// LIST_MALL_ORDER_SET.add(MALL_ORDER_SET_UN_PAY_CLOSE_MILLISECOND); + LIST_MALL_ORDER_SET.add(MALL_ORDER_SET_BUYER_APPLY_AFTER_SALE_HANDLE_MILLISECOND); + LIST_MALL_ORDER_SET.add(MALL_ORDER_SET_AFTER_SALE_BUYER_DELIVER_MILLISECOND); + LIST_MALL_ORDER_SET.add(MALL_ORDER_SET_BUYER_APPLY_AFTER_SALE_MILLISECOND); + LIST_MALL_ORDER_SET.add(MALL_ORDER_SET_STOCK_CHECK_TYPE); + } + + /** + * 根据指定的系统属性key查找相应枚举类 + * + * @param key 系统属性key + * @return 系统属性key枚举 + * @author zhengqingya + * @date 2021/8/19 9:50 + */ + public static SysPropertyKeyEnum getEnum(String key) { + for (SysPropertyKeyEnum itemEnum : LIST) { + if (itemEnum.getKey().equals(key)) { + return itemEnum; + } + } + throw new MyException("未找到指定的系统属性key!"); + } + +} diff --git a/smallboot-api/system/src/main/java/com/zhengqing/system/enums/SysRoleOperationTypeEnum.java b/smallboot-api/system/src/main/java/com/zhengqing/system/enums/SysRoleOperationTypeEnum.java new file mode 100644 index 0000000..8f8cd55 --- /dev/null +++ b/smallboot-api/system/src/main/java/com/zhengqing/system/enums/SysRoleOperationTypeEnum.java @@ -0,0 +1,44 @@ +package com.zhengqing.system.enums; + +import com.google.common.collect.Lists; +import lombok.AllArgsConstructor; +import lombok.Getter; + +import java.util.Arrays; +import java.util.List; + +/** + *

+ * 角色操作类型 + *

+ * + * @author zhengqingya + * @description + * @date 2020/4/15 17:14 + */ +@Getter +@AllArgsConstructor +public enum SysRoleOperationTypeEnum { + + 添加或修改角色(1, "添加或修改角色"), + 角色关联菜单权限(2, "角色关联菜单权限"); + + private final Integer type; + private final String desc; + + private static final List LIST = Lists.newArrayList(); + + static { + LIST.addAll(Arrays.asList(SysRoleOperationTypeEnum.values())); + } + + public static SysRoleOperationTypeEnum getEnum(Integer type) { + for (SysRoleOperationTypeEnum itemEnum : LIST) { + if (itemEnum.getType().equals(type)) { + return itemEnum; + } + } + return 添加或修改角色; + } + +} diff --git a/smallboot-api/system/src/main/java/com/zhengqing/system/enums/SysSystemSourceEnum.java b/smallboot-api/system/src/main/java/com/zhengqing/system/enums/SysSystemSourceEnum.java new file mode 100644 index 0000000..165da75 --- /dev/null +++ b/smallboot-api/system/src/main/java/com/zhengqing/system/enums/SysSystemSourceEnum.java @@ -0,0 +1,44 @@ +package com.zhengqing.system.enums; + +import com.google.common.collect.Lists; +import lombok.AllArgsConstructor; +import lombok.Getter; + +import java.util.Arrays; +import java.util.List; + +/** + *

+ * 系统来源 + *

+ * + * @author zhengqingya + * @description + * @date 2021/1/13 20:25 + */ +@Getter +@AllArgsConstructor +public enum SysSystemSourceEnum { + + 小工具(0, "smallboot"), + 其它(1, "other"); + + private final Integer systemSource; + private final String desc; + + private static final List LIST = Lists.newArrayList(); + + static { + LIST.addAll(Arrays.asList(SysSystemSourceEnum.values())); + } + + public static SysSystemSourceEnum getEnum(Integer systemSource) { + for (SysSystemSourceEnum itemEnum : LIST) { + if (itemEnum.getSystemSource().equals(systemSource)) { + return itemEnum; + } + } + return 小工具; + } + +} diff --git a/smallboot-api/system/src/main/java/com/zhengqing/system/enums/SysUserReRoleEnum.java b/smallboot-api/system/src/main/java/com/zhengqing/system/enums/SysUserReRoleEnum.java new file mode 100644 index 0000000..03866db --- /dev/null +++ b/smallboot-api/system/src/main/java/com/zhengqing/system/enums/SysUserReRoleEnum.java @@ -0,0 +1,25 @@ +package com.zhengqing.system.enums; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + *

+ * 用户关联角色枚举类 + *

+ * + * @author zhengqingya + * @description + * @date 2020/11/28 23:35 + */ +@Getter +@AllArgsConstructor +public enum SysUserReRoleEnum { + + 凡人(1, "凡人"), + 超级管理员(9, "超级管理员"); + + private final Integer roleId; + private final String desc; + +} diff --git a/smallboot-api/system/src/main/java/com/zhengqing/system/mapper/SysDictMapper.java b/smallboot-api/system/src/main/java/com/zhengqing/system/mapper/SysDictMapper.java new file mode 100644 index 0000000..f5bb96b --- /dev/null +++ b/smallboot-api/system/src/main/java/com/zhengqing/system/mapper/SysDictMapper.java @@ -0,0 +1,56 @@ +package com.zhengqing.system.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.zhengqing.system.entity.SysDict; +import com.zhengqing.system.model.dto.SysDictSaveBatchDTO; +import com.zhengqing.system.model.vo.SysDictVO; +import org.apache.ibatis.annotations.Delete; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + *

+ * 数据字典-Mapper 接口 + *

+ * + * @author zhengqingya + * @description + * @date 2020/4/15 20:53 + */ +public interface SysDictMapper extends BaseMapper { + + /** + * 根据数据字典编码获取数据字典列表 + * + * @param status 状态 1:启用 0:禁用 + * @param codeList 数据字典编码 + * @return 数据字典列表信息 + * @author zhengqingya + * @date 2020/9/12 17:58 + */ + List selectDictListByCode(@Param("status") Integer status, @Param("codeList") List codeList); + + /** + * 根据类型编码删除关联数据字典 + * + * @param code 数据字典类型编码 + * @return void + * @author zhengqingya + * @date 2020/9/12 17:41 + */ + @Delete("DELETE FROM t_sys_dict WHERE code = #{code}") +// @Update("UPDATE t_sys_dict SET is_deleted=1 WHERE code = #{code}") + void deleteByCode(@Param("code") String code); + + /** + * 批量保存,主键id存在时,作修改处理;不存在时,作插入新数据处理。 + * + * @param list 保存数据 + * @return void + * @author zhengqingya + * @date 2021/8/28 1:39 上午 + */ + void batchInsertOrUpdate(@Param("list") List list); + +} diff --git a/smallboot-api/system/src/main/java/com/zhengqing/system/mapper/SysDictTypeMapper.java b/smallboot-api/system/src/main/java/com/zhengqing/system/mapper/SysDictTypeMapper.java new file mode 100644 index 0000000..cf4ece2 --- /dev/null +++ b/smallboot-api/system/src/main/java/com/zhengqing/system/mapper/SysDictTypeMapper.java @@ -0,0 +1,43 @@ +package com.zhengqing.system.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.zhengqing.system.entity.SysDictType; +import com.zhengqing.system.model.bo.SysDictTypeBO; +import com.zhengqing.system.model.vo.SysDictTypeListVO; +import org.apache.ibatis.annotations.Param; +import org.apache.ibatis.annotations.Select; + +import java.util.List; + +/** + *

+ * 数据字典类型 Mapper 接口 + *

+ * + * @author zhengqingya + * @description + * @date 2020/4/15 20:53 + */ +public interface SysDictTypeMapper extends BaseMapper { + + /** + * 查询已启用的数据字典类型列表信息 + * + * @return 数据字典类型列表信息 + * @author zhengqingya + * @date 2020/9/12 18:51 + */ + @Select("SELECT id,code,name,sort FROM t_sys_dict_type WHERE status=1 AND is_deleted = 0") + List selectDictTypeListByOpen(); + + /** + * 查询字典类型 + * + * @param codeList 字典编码list + * @return 编码code -> 字典类型ID + * @author zhengqingya + * @date 2021/8/28 4:45 上午 + */ + List selectDataList(@Param("codeList") List codeList); + +} diff --git a/smallboot-api/system/src/main/java/com/zhengqing/system/mapper/SysMenuMapper.java b/smallboot-api/system/src/main/java/com/zhengqing/system/mapper/SysMenuMapper.java new file mode 100644 index 0000000..888e640 --- /dev/null +++ b/smallboot-api/system/src/main/java/com/zhengqing/system/mapper/SysMenuMapper.java @@ -0,0 +1,53 @@ +package com.zhengqing.system.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.zhengqing.system.entity.SysMenu; +import com.zhengqing.system.model.dto.SysMenuListDTO; +import com.zhengqing.system.model.vo.SysMenuTreeVO; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + *

+ * 系统管理-菜单表 Mapper 接口 + *

+ * + * @author zhengqingya + * @description + * @date 2020/4/15 18:51 + */ +public interface SysMenuMapper extends BaseMapper { + + /** + * 列表分页 + * + * @param page: + * @param filter: 过滤参数 + * @return 菜单信息 + * @author zhengqingya + * @date 2020/9/10 20:29 + */ + IPage selectMenus(IPage page, @Param("filter") SysMenuListDTO filter); + + /** + * 列表 + * + * @param filter: 过滤参数 + * @return 菜单信息 + * @author zhengqingya + * @date 2020/9/10 20:29 + */ + List selectMenus(@Param("filter") SysMenuListDTO filter); + + /** + * 获取所有菜单 - 仅菜单树使用 + * + * @return 菜单信息 + * @author zhengqingya + * @date 2020/9/10 20:30 + */ + List selectMenuTree(); + +} diff --git a/smallboot-api/system/src/main/java/com/zhengqing/system/mapper/SysOauthClientMapper.java b/smallboot-api/system/src/main/java/com/zhengqing/system/mapper/SysOauthClientMapper.java new file mode 100644 index 0000000..b3742fd --- /dev/null +++ b/smallboot-api/system/src/main/java/com/zhengqing/system/mapper/SysOauthClientMapper.java @@ -0,0 +1,29 @@ +package com.zhengqing.system.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.zhengqing.system.entity.SysOauthClient; +import com.zhengqing.system.model.vo.SysOauthClientVO; +import org.apache.ibatis.annotations.Param; +import org.apache.ibatis.annotations.Select; + +/** + *

oauth客户端 Mapper

+ * + * @author zhengqingya + * @description + * @date 2022/06/10 16:25 + */ +public interface SysOauthClientMapper extends BaseMapper { + + /** + * 详情 + * + * @param clientId 客户端ID + * @return 详情 + * @author zhengqingya + * @date 2022/06/10 16:25 + */ + @Select("SELECT * FROM t_sys_oauth_client WHERE client_id = #{clientId}") + SysOauthClientVO selectClient(@Param("clientId") String clientId); + +} diff --git a/smallboot-api/system/src/main/java/com/zhengqing/system/mapper/SysOauthMapper.java b/smallboot-api/system/src/main/java/com/zhengqing/system/mapper/SysOauthMapper.java new file mode 100644 index 0000000..33ff435 --- /dev/null +++ b/smallboot-api/system/src/main/java/com/zhengqing/system/mapper/SysOauthMapper.java @@ -0,0 +1,43 @@ +package com.zhengqing.system.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.zhengqing.system.entity.SysOauth; +import com.zhengqing.system.model.dto.SysOauthListDTO; +import com.zhengqing.system.model.vo.SysOauthListVO; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + *

+ * 系统管理 - 用户三方授权表 Mapper 接口 + *

+ * + * @author zhengqingya + * @description + * @date 2020/11/28 22:14 + */ +public interface SysOauthMapper extends BaseMapper { + + /** + * 列表 + * + * @param filter: 过滤参数 + * @return 列表数据 + * @author zhengqingya + * @date 2020/12/6 13:59 + */ + List selectDataList(@Param("filter") SysOauthListDTO filter); + + /** + * 查询授权信息 + * + * @param oauthType: 授权类型 + * @param openId 三方id + * @return 授权信息 + * @author zhengqingya + * @date 2020/11/28 22:24 + */ + SysOauth detail(@Param("oauthType") Integer oauthType, @Param("openId") Integer openId); + +} diff --git a/smallboot-api/system/src/main/java/com/zhengqing/system/mapper/SysPermissionMapper.java b/smallboot-api/system/src/main/java/com/zhengqing/system/mapper/SysPermissionMapper.java new file mode 100644 index 0000000..0a4aded --- /dev/null +++ b/smallboot-api/system/src/main/java/com/zhengqing/system/mapper/SysPermissionMapper.java @@ -0,0 +1,65 @@ +package com.zhengqing.system.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.zhengqing.system.entity.SysPermission; +import com.zhengqing.system.model.vo.SysMenuReBtnPermListVO; +import com.zhengqing.system.model.vo.SysRoleRePermListVO; +import org.apache.ibatis.annotations.Delete; +import org.apache.ibatis.annotations.Param; +import org.apache.ibatis.annotations.Select; + +import java.util.List; + +/** + *

+ * 系统管理-权限 Mapper 接口 + *

+ * + * @author zhengqingya + * @description + * @date 2020/4/15 19:00 + */ +public interface SysPermissionMapper extends BaseMapper { + + /** + * 根据菜单ID查询已经配置的按钮ids + * + * @param menuId 菜单id + * @return 按钮ids + * @author zhengqingya + * @date 2020/9/10 21:21 + */ + @Select("SELECT sd.id FROM t_sys_permission sp INNER JOIN t_sys_dict sd on sd.id = sp.btn_id WHERE sp.menu_id = #{menuId}") + List getBtnIdsByMenuId(@Param("menuId") Integer menuId); + + /** + * 根据菜单id删除所属的按钮ids + * + * @param menuId 菜单id + * @return void + * @author zhengqingya + * @date 2020/9/10 21:36 + */ + @Delete("DELETE FROM t_sys_permission WHERE menu_id = #{menuId}") + void deleteByMenuId(@Param("menuId") Integer menuId); + + /** + * 通过菜单id查询菜单按钮权限信息 + * + * @param menuId 菜单id + * @return 菜单按钮权限信息 + * @author zhengqingya + * @date 2020/9/10 22:18 + */ + List selectBtnInfoListByMenuId(@Param("menuId") Integer menuId); + + /** + * 获取角色权限映射数据 + * + * @return 权限 + * @author zhengqingya + * @date 2022/6/14 14:55 + */ + List listRoleRePerm(); + +} diff --git a/smallboot-api/system/src/main/java/com/zhengqing/system/mapper/SysPropertyMapper.java b/smallboot-api/system/src/main/java/com/zhengqing/system/mapper/SysPropertyMapper.java new file mode 100644 index 0000000..a47f47c --- /dev/null +++ b/smallboot-api/system/src/main/java/com/zhengqing/system/mapper/SysPropertyMapper.java @@ -0,0 +1,62 @@ +package com.zhengqing.system.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.zhengqing.system.entity.SysProperty; +import com.zhengqing.system.model.dto.SysPropertySaveDTO; +import com.zhengqing.system.model.vo.SysPropertyVO; +import org.apache.ibatis.annotations.Param; +import org.apache.ibatis.annotations.Update; + +import java.util.List; + +/** + *

系统管理-系统属性 Mapper

+ * + * @author zhengqingya + * @description + * @date 2021/09/06 22:57 + */ +public interface SysPropertyMapper extends BaseMapper { + + /** + * 列表 + * + * @param keyList 属性key + * @return 查询结果 + * @author zhengqingya + * @date 2021/09/06 22:57 + */ + List selectDataListByKey(@Param("keyList") List keyList); + + /** + * 根据属性key删除数据 + * + * @param key 属性key + * @return void + * @author zhengqingya + * @date 2021/9/6 11:36 下午 + */ + @Update("UPDATE t_sys_property SET is_deleted=1 WHERE `key` = #{key}") + void deleteByKey(@Param("key") String key); + + /** + * 根据属性key批量删除数据 + * + * @param keyList 根据属性key删除数据 + * @return void + * @author zhengqingya + * @date 2021/9/6 11:52 下午 + */ + void deleteByKeyList(@Param("keyList") List keyList); + + /** + * 批量保存,主键id存在时,作修改处理;不存在时,作插入新数据处理。 + * + * @param list 保存数据 + * @return void + * @author zhengqingya + * @date 2021/9/6 11:36 下午 + */ + void batchInsertOrUpdate(@Param("list") List list); + +} diff --git a/smallboot-api/system/src/main/java/com/zhengqing/system/mapper/SysRoleMapper.java b/smallboot-api/system/src/main/java/com/zhengqing/system/mapper/SysRoleMapper.java new file mode 100644 index 0000000..4d651c9 --- /dev/null +++ b/smallboot-api/system/src/main/java/com/zhengqing/system/mapper/SysRoleMapper.java @@ -0,0 +1,58 @@ +package com.zhengqing.system.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.zhengqing.system.entity.SysRole; +import com.zhengqing.system.model.dto.SysRoleListDTO; +import com.zhengqing.system.model.vo.SysRoleListVO; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + *

+ * 系统管理 - 角色管理 Mapper 接口 + *

+ * + * @author zhengqingya + * @description + * @date 2020/4/15 15:01 + */ +public interface SysRoleMapper extends BaseMapper { + + /** + * 列表分页 + * + * @param page: + * @param filter: 过滤参数 + * @return 角色信息 + * @author zhengqingya + * @date 2020/9/10 18:07 + */ + // @Select({""}) + IPage selectRoles(IPage page, @Param("filter") SysRoleListDTO filter); + + /** + * 列表 + * + * @param filter: 过滤参数 + * @return 角色信息 + * @author zhengqingya + * @date 2020/9/10 18:08 + */ + // @Select({""}) + List selectRoles(@Param("filter") SysRoleListDTO filter); + +} diff --git a/smallboot-api/system/src/main/java/com/zhengqing/system/mapper/SysRoleMenuMapper.java b/smallboot-api/system/src/main/java/com/zhengqing/system/mapper/SysRoleMenuMapper.java new file mode 100644 index 0000000..939fa00 --- /dev/null +++ b/smallboot-api/system/src/main/java/com/zhengqing/system/mapper/SysRoleMenuMapper.java @@ -0,0 +1,65 @@ +package com.zhengqing.system.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.zhengqing.system.entity.SysRoleMenu; +import org.apache.ibatis.annotations.Delete; +import org.apache.ibatis.annotations.Param; +import org.apache.ibatis.annotations.Select; + +import java.util.List; + +/** + *

+ * 系统管理-角色菜单表 Mapper 接口 + *

+ * + * @author zhengqingya + * @description + * @date 2020/4/15 19:00 + */ +public interface SysRoleMenuMapper extends BaseMapper { + + /** + * 批量保存角色菜单对应关系 + * + * @param roleId 角色id + * @param menuIdList: 菜单ids + * @return void + * @author zhengqingya + * @date 2020/9/10 18:08 + */ + void batchInsertRoleMenuIds(@Param("roleId") Integer roleId, @Param("menuIdList") List menuIdList); + + /** + * 根据角色id删除角色对应的所有关联菜单 + * + * @param roleId: 角色id + * @return void + * @author zhengqingya + * @date 2020/9/10 18:08 + */ + @Delete("DELETE FROM t_sys_role_menu WHERE role_id = #{roleId}") + void deleteAllMenusByRoleId(@Param("roleId") Integer roleId); + + /** + * 获取角色id可访问的菜单ids + * + * @param roleId: 角色id + * @return 可访问的菜单ids + * @author zhengqingya + * @date 2020/9/10 18:09 + */ + @Select("SELECT menu_id FROM t_sys_role_menu WHERE role_id = #{roleId} ORDER BY menu_id") + List selectMenuIdsByRoleId(@Param("roleId") Integer roleId); + + /** + * 获取角色ids可访问的菜单ids + * + * @param roleIdList: 角色ids + * @return 可访问的菜单ids + * @author zhengqingya + * @date 2020/9/10 18:09 + */ + List selectMenuIdsByRoleIds(@Param("roleIdList") List roleIdList); + +} diff --git a/smallboot-api/system/src/main/java/com/zhengqing/system/mapper/SysRolePermissionMapper.java b/smallboot-api/system/src/main/java/com/zhengqing/system/mapper/SysRolePermissionMapper.java new file mode 100644 index 0000000..6a5457b --- /dev/null +++ b/smallboot-api/system/src/main/java/com/zhengqing/system/mapper/SysRolePermissionMapper.java @@ -0,0 +1,65 @@ +package com.zhengqing.system.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.zhengqing.system.entity.SysRolePermission; +import com.zhengqing.system.model.vo.SysRoleMenuBtnListVO; +import org.apache.ibatis.annotations.Delete; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + *

+ * 系统管理-角色关联权限表 Mapper 接口 + *

+ * + * @author zhengqingya + * @description + * @date 2020/4/15 19:00 + */ +public interface SysRolePermissionMapper extends BaseMapper { + + /** + * 获取所有角色菜单按钮权限信息 + * + * @return 所有角色关联的菜单按钮信息 + * @author zhengqingya + * @date 2020/9/10 17:54 + */ + List selectRoleMenuBtns(); + + /** + * 通过角色ID和菜单ID查询该菜单所拥有的所有按钮权限 + * + * @param roleId 角色ID + * @param menuId 菜单ID + * @return 菜单所拥有的所有按钮权限ids + * @author zhengqingya + * @date 2020/9/10 17:58 + */ + List selectBtnsByRoleIdAndMenuId(@Param("roleId") Integer roleId, @Param("menuId") Integer menuId); + + /** + * 根据角色id删除关联所有按钮权限 + * + * @param roleId 角色id + * @return void + * @author zhengqingya + * @date 2020/9/10 17:50 + */ + @Delete("DELETE FROM t_sys_role_permission WHERE role_id = #{roleId}") + void deleteBtnsByRoleId(@Param("roleId") Integer roleId); + + /** + * 根据角色ID和菜单ID删除按钮 + * + * @param roleId 角色ID + * @param menuId 菜单ID + * @return void + * @author zhengqingya + * @date 2020/9/10 17:57 + */ + @Delete("DELETE srp FROM t_sys_role_permission srp,t_sys_permission sp WHERE sp.id = srp.permission_id AND srp.role_id = #{roleId} AND sp.menu_id = #{menuId}") + void deleteBtnsByRoleIdAndMenuId(@Param("roleId") Integer roleId, @Param("menuId") Integer menuId); + +} diff --git a/smallboot-api/system/src/main/java/com/zhengqing/system/mapper/SysUserMapper.java b/smallboot-api/system/src/main/java/com/zhengqing/system/mapper/SysUserMapper.java new file mode 100644 index 0000000..b127135 --- /dev/null +++ b/smallboot-api/system/src/main/java/com/zhengqing/system/mapper/SysUserMapper.java @@ -0,0 +1,67 @@ +package com.zhengqing.system.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.zhengqing.system.entity.SysUser; +import com.zhengqing.system.model.dto.SysUserListDTO; +import com.zhengqing.system.model.dto.SysUserPermDTO; +import com.zhengqing.system.model.vo.SysUserDetailVO; +import com.zhengqing.system.model.vo.SysUserListVO; +import com.zhengqing.system.model.vo.SysUserPermVO; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + *

+ * 系统管理-用户基础信息表 Mapper 接口 + *

+ * + * @author zhengqingya + * @description + * @date 2020/4/15 11:05 + */ +public interface SysUserMapper extends BaseMapper { + + /** + * 列表分页 + * + * @param page 分页参数 + * @param filter 过滤参数 + * @return 用户信息 + * @author zhengqingya + * @date 2020/9/10 10:28 + */ + IPage selectDataList(IPage page, @Param("filter") SysUserListDTO filter); + + /** + * 列表 + * + * @param filter 过滤参数 + * @return 用户信息 + * @author zhengqingya + * @date 2020/9/10 10:34 + */ + List selectDataList(@Param("filter") SysUserListDTO filter); + + /** + * 根据用户id查询用户信息 + * + * @param userId 用户id + * @return 用户信息 + * @author zhengqingya + * @date 2020/9/10 10:49 + */ + SysUserDetailVO detail(@Param("userId") Integer userId); + + /** + * 查询用户信息 + * + * @param filter 过滤参数 + * @return 用户信息 + * @author zhengqingya + * @date 2020/9/21 16:18 + */ + SysUserPermVO selectUserPerm(@Param("filter") SysUserPermDTO filter); + +} diff --git a/smallboot-api/system/src/main/java/com/zhengqing/system/mapper/SysUserRoleMapper.java b/smallboot-api/system/src/main/java/com/zhengqing/system/mapper/SysUserRoleMapper.java new file mode 100644 index 0000000..81ae2c8 --- /dev/null +++ b/smallboot-api/system/src/main/java/com/zhengqing/system/mapper/SysUserRoleMapper.java @@ -0,0 +1,41 @@ +package com.zhengqing.system.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.zhengqing.system.entity.SysUserRole; +import com.zhengqing.system.model.bo.SysUserReRoleIdListBO; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + *

+ * 系统管理 - 用户角色关联表 Mapper 接口 + *

+ * + * @author zhengqingya + * @description + * @date 2020/4/15 18:44 + */ +public interface SysUserRoleMapper extends BaseMapper { + + /** + * 根据用户id查询关联角色ids + * + * @param userId 用户id + * @return 角色ids + * @author zhengqingya + * @date 2022/6/14 12:39 + */ + List listRoleId(@Param("userId") Integer userId); + + /** + * 根据用户id查询关联角色ids + * + * @param userIdList 用户ids + * @return 用户关联角色id + * @author zhengqingya + * @date 2022/6/14 12:39 + */ + List selectListByUserIds(@Param("userIdList") List userIdList); + +} diff --git a/smallboot-api/system/src/main/java/com/zhengqing/system/mapper/xml/SysDictMapper.xml b/smallboot-api/system/src/main/java/com/zhengqing/system/mapper/xml/SysDictMapper.xml new file mode 100644 index 0000000..209e2d8 --- /dev/null +++ b/smallboot-api/system/src/main/java/com/zhengqing/system/mapper/xml/SysDictMapper.xml @@ -0,0 +1,75 @@ + + + + + + + + INSERT t_sys_dict ( + id, + dict_type_id, + code, + name, + value, + status, + sort, + remark, + create_by, + update_by, + create_time, + update_time + ) + VALUES + + ( + #{item.id}, + #{item.dictTypeId}, + #{item.code}, + #{item.name}, + #{item.value}, + #{item.status}, + #{item.sort}, + #{item.remark}, + #{item.currentUserId}, + #{item.currentUserId}, + now(), + now() + ) + + ON DUPLICATE KEY UPDATE + dict_type_id = VALUES(dict_type_id), + code = VALUES(code), + name = VALUES(name), + value = VALUES(value), + status = VALUES(status), + sort = VALUES(sort), + remark = VALUES(remark), + update_by = VALUES(update_by), + update_time = VALUES(update_time); + + + diff --git a/smallboot-api/system/src/main/java/com/zhengqing/system/mapper/xml/SysDictTypeMapper.xml b/smallboot-api/system/src/main/java/com/zhengqing/system/mapper/xml/SysDictTypeMapper.xml new file mode 100644 index 0000000..4a843fe --- /dev/null +++ b/smallboot-api/system/src/main/java/com/zhengqing/system/mapper/xml/SysDictTypeMapper.xml @@ -0,0 +1,21 @@ + + + + + + + diff --git a/smallboot-api/system/src/main/java/com/zhengqing/system/mapper/xml/SysMenuMapper.xml b/smallboot-api/system/src/main/java/com/zhengqing/system/mapper/xml/SysMenuMapper.xml new file mode 100644 index 0000000..65871ce --- /dev/null +++ b/smallboot-api/system/src/main/java/com/zhengqing/system/mapper/xml/SysMenuMapper.xml @@ -0,0 +1,42 @@ + + + + + + + + + diff --git a/smallboot-api/system/src/main/java/com/zhengqing/system/mapper/xml/SysOauthClientMapper.xml b/smallboot-api/system/src/main/java/com/zhengqing/system/mapper/xml/SysOauthClientMapper.xml new file mode 100644 index 0000000..6f8ee65 --- /dev/null +++ b/smallboot-api/system/src/main/java/com/zhengqing/system/mapper/xml/SysOauthClientMapper.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/smallboot-api/system/src/main/java/com/zhengqing/system/mapper/xml/SysOauthMapper.xml b/smallboot-api/system/src/main/java/com/zhengqing/system/mapper/xml/SysOauthMapper.xml new file mode 100644 index 0000000..26ef20c --- /dev/null +++ b/smallboot-api/system/src/main/java/com/zhengqing/system/mapper/xml/SysOauthMapper.xml @@ -0,0 +1,41 @@ + + + + + + + + + diff --git a/smallboot-api/system/src/main/java/com/zhengqing/system/mapper/xml/SysPermissionMapper.xml b/smallboot-api/system/src/main/java/com/zhengqing/system/mapper/xml/SysPermissionMapper.xml new file mode 100644 index 0000000..1867e20 --- /dev/null +++ b/smallboot-api/system/src/main/java/com/zhengqing/system/mapper/xml/SysPermissionMapper.xml @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + + + + + + diff --git a/smallboot-api/system/src/main/java/com/zhengqing/system/mapper/xml/SysPropertyMapper.xml b/smallboot-api/system/src/main/java/com/zhengqing/system/mapper/xml/SysPropertyMapper.xml new file mode 100644 index 0000000..5aa92d3 --- /dev/null +++ b/smallboot-api/system/src/main/java/com/zhengqing/system/mapper/xml/SysPropertyMapper.xml @@ -0,0 +1,65 @@ + + + + + + + + + UPDATE t_sys_property + SET is_deleted=1 + WHERE `key` IN + + #{item} + + + + + + INSERT t_sys_property ( + id, + `key`, + value, + remark, + create_by, + update_by, + create_time, + update_time + ) + VALUES + + ( + #{item.id}, + #{item.key}, + #{item.value}, + #{item.remark}, + #{item.currentUserId}, + #{item.currentUserId}, + now(), + now() + ) + + ON DUPLICATE KEY UPDATE + `key` = VALUES(`key`), + value = VALUES(value), + remark = VALUES(remark), + update_by = VALUES(update_by), + update_time = VALUES(update_time); + + + diff --git a/smallboot-api/system/src/main/java/com/zhengqing/system/mapper/xml/SysRoleMapper.xml b/smallboot-api/system/src/main/java/com/zhengqing/system/mapper/xml/SysRoleMapper.xml new file mode 100644 index 0000000..801a7a8 --- /dev/null +++ b/smallboot-api/system/src/main/java/com/zhengqing/system/mapper/xml/SysRoleMapper.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/smallboot-api/system/src/main/java/com/zhengqing/system/mapper/xml/SysRoleMenuMapper.xml b/smallboot-api/system/src/main/java/com/zhengqing/system/mapper/xml/SysRoleMenuMapper.xml new file mode 100644 index 0000000..f35255a --- /dev/null +++ b/smallboot-api/system/src/main/java/com/zhengqing/system/mapper/xml/SysRoleMenuMapper.xml @@ -0,0 +1,26 @@ + + + + + + INSERT INTO t_sys_role_menu (role_id,menu_id) VALUES + + (#{roleId},#{menuId}) + + + + + + + diff --git a/smallboot-api/system/src/main/java/com/zhengqing/system/mapper/xml/SysRolePermissionMapper.xml b/smallboot-api/system/src/main/java/com/zhengqing/system/mapper/xml/SysRolePermissionMapper.xml new file mode 100644 index 0000000..a10ea53 --- /dev/null +++ b/smallboot-api/system/src/main/java/com/zhengqing/system/mapper/xml/SysRolePermissionMapper.xml @@ -0,0 +1,23 @@ + + + + + + + + + diff --git a/smallboot-api/system/src/main/java/com/zhengqing/system/mapper/xml/SysUserMapper.xml b/smallboot-api/system/src/main/java/com/zhengqing/system/mapper/xml/SysUserMapper.xml new file mode 100644 index 0000000..cd21add --- /dev/null +++ b/smallboot-api/system/src/main/java/com/zhengqing/system/mapper/xml/SysUserMapper.xml @@ -0,0 +1,41 @@ + + + + + + + + + + + diff --git a/smallboot-api/system/src/main/java/com/zhengqing/system/mapper/xml/SysUserRoleMapper.xml b/smallboot-api/system/src/main/java/com/zhengqing/system/mapper/xml/SysUserRoleMapper.xml new file mode 100644 index 0000000..bf43cee --- /dev/null +++ b/smallboot-api/system/src/main/java/com/zhengqing/system/mapper/xml/SysUserRoleMapper.xml @@ -0,0 +1,25 @@ + + + + + + + + + diff --git a/smallboot-api/system/src/main/java/com/zhengqing/system/model/bo/SysDictTypeBO.java b/smallboot-api/system/src/main/java/com/zhengqing/system/model/bo/SysDictTypeBO.java new file mode 100644 index 0000000..f07d4c3 --- /dev/null +++ b/smallboot-api/system/src/main/java/com/zhengqing/system/model/bo/SysDictTypeBO.java @@ -0,0 +1,30 @@ +package com.zhengqing.system.model.bo; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + *

数据字典类型参数

+ * + * @author zhengqingya + * @description + * @date 2021/8/28 4:54 上午 + */ +@Data +@Builder +@AllArgsConstructor +@NoArgsConstructor +@ApiModel("数据字典类型参数") +public class SysDictTypeBO { + + @ApiModelProperty(value = "字典id") + private Integer id; + + @ApiModelProperty(value = "字典类型编码") + private String code; + +} diff --git a/smallboot-api/system/src/main/java/com/zhengqing/system/model/bo/SysGitHubUserInfoBO.java b/smallboot-api/system/src/main/java/com/zhengqing/system/model/bo/SysGitHubUserInfoBO.java new file mode 100644 index 0000000..4a0af4b --- /dev/null +++ b/smallboot-api/system/src/main/java/com/zhengqing/system/model/bo/SysGitHubUserInfoBO.java @@ -0,0 +1,47 @@ +package com.zhengqing.system.model.bo; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + *

+ * github用户信息$ + *

+ * + * @author zhengqingya + * @description + * @date 2020/11/27$ 17:28$ + */ +@Data +@Builder +@AllArgsConstructor +@NoArgsConstructor +@ApiModel("github用户信息") +public class SysGitHubUserInfoBO { + + @ApiModelProperty(value = "uuid") + private String uuid; + + @ApiModelProperty(value = "用户名") + private String username; + + @ApiModelProperty(value = "昵称") + private String nickname; + + @ApiModelProperty(value = "头像") + private String avatar; + + @ApiModelProperty(value = "邮箱") + private String email; + + @ApiModelProperty(value = "性别") + private String gender; + + @ApiModelProperty(value = "备注") + private String remark; + +} diff --git a/smallboot-api/system/src/main/java/com/zhengqing/system/model/bo/SysGiteeUserInfoBO.java b/smallboot-api/system/src/main/java/com/zhengqing/system/model/bo/SysGiteeUserInfoBO.java new file mode 100644 index 0000000..ce6a0b0 --- /dev/null +++ b/smallboot-api/system/src/main/java/com/zhengqing/system/model/bo/SysGiteeUserInfoBO.java @@ -0,0 +1,47 @@ +package com.zhengqing.system.model.bo; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + *

+ * gitee用户信息 + *

+ * + * @author zhengqingya + * @description + * @date 2020/11/29 15:05 + */ +@Data +@Builder +@AllArgsConstructor +@NoArgsConstructor +@ApiModel("gitee用户信息") +public class SysGiteeUserInfoBO { + + @ApiModelProperty(value = "uuid") + private String uuid; + + @ApiModelProperty(value = "用户名") + private String username; + + @ApiModelProperty(value = "昵称") + private String nickname; + + @ApiModelProperty(value = "头像") + private String avatar; + + @ApiModelProperty(value = "邮箱") + private String email; + + @ApiModelProperty(value = "性别") + private String gender; + + @ApiModelProperty(value = "备注") + private String remark; + +} diff --git a/smallboot-api/system/src/main/java/com/zhengqing/system/model/bo/SysQQUserInfoBO.java b/smallboot-api/system/src/main/java/com/zhengqing/system/model/bo/SysQQUserInfoBO.java new file mode 100644 index 0000000..0cb47bf --- /dev/null +++ b/smallboot-api/system/src/main/java/com/zhengqing/system/model/bo/SysQQUserInfoBO.java @@ -0,0 +1,47 @@ +package com.zhengqing.system.model.bo; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + *

+ * qq用户信息 + *

+ * + * @author zhengqingya + * @description + * @date 2020/12/2 17:38 + */ +@Data +@Builder +@AllArgsConstructor +@NoArgsConstructor +@ApiModel("qq用户信息") +public class SysQQUserInfoBO { + + @ApiModelProperty(value = "uuid") + private String uuid; + + @ApiModelProperty(value = "用户名") + private String username; + + @ApiModelProperty(value = "昵称") + private String nickname; + + @ApiModelProperty(value = "头像") + private String avatar; + + @ApiModelProperty(value = "邮箱") + private String email; + + @ApiModelProperty(value = "性别") + private String gender; + + @ApiModelProperty(value = "备注") + private String remark; + +} diff --git a/smallboot-api/system/src/main/java/com/zhengqing/system/model/bo/SysThirdpartOauthUserInfoBO.java b/smallboot-api/system/src/main/java/com/zhengqing/system/model/bo/SysThirdpartOauthUserInfoBO.java new file mode 100644 index 0000000..2645e6e --- /dev/null +++ b/smallboot-api/system/src/main/java/com/zhengqing/system/model/bo/SysThirdpartOauthUserInfoBO.java @@ -0,0 +1,54 @@ +package com.zhengqing.system.model.bo; + +import com.zhengqing.common.core.enums.UserSexEnum; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + *

+ * 第三方授权用户信息 + *

+ * + * @author zhengqingya + * @description + * @date 2020/11/28 22:48 + */ +@Data +@Builder +@AllArgsConstructor +@NoArgsConstructor +@ApiModel("第三方授权用户信息") +public class SysThirdpartOauthUserInfoBO { + + @ApiModelProperty(value = "第三方授权类型") + private Integer oauthType; + + @ApiModelProperty(value = "三方id") + private String openId; + + @ApiModelProperty(value = "用户名") + private String username; + + @ApiModelProperty(value = "昵称") + private String nickname; + + @ApiModelProperty(value = "头像") + private String avatar; + + @ApiModelProperty(value = "邮箱") + private String email; + + /** + * {@link UserSexEnum} + */ + @ApiModelProperty(value = "性别") + private Byte sex; + + @ApiModelProperty(value = "备注") + private String remark; + +} diff --git a/smallboot-api/system/src/main/java/com/zhengqing/system/model/bo/SysUserReRoleIdListBO.java b/smallboot-api/system/src/main/java/com/zhengqing/system/model/bo/SysUserReRoleIdListBO.java new file mode 100644 index 0000000..e34117d --- /dev/null +++ b/smallboot-api/system/src/main/java/com/zhengqing/system/model/bo/SysUserReRoleIdListBO.java @@ -0,0 +1,27 @@ +package com.zhengqing.system.model.bo; + +import com.zhengqing.common.base.model.bo.BaseBO; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +/** + *

+ * 系统管理 - 用户关联角色ids + *

+ * + * @author zhengqingya + * @description + * @date 2020/4/15 10:48 + */ +@Data +@ApiModel +public class SysUserReRoleIdListBO extends BaseBO { + + @ApiModelProperty(value = "用户ID") + private Integer userId; + + @ApiModelProperty(value = "角色ID") + private Integer roleId; + +} diff --git a/smallboot-api/system/src/main/java/com/zhengqing/system/model/dto/SysDictSaveBatchDTO.java b/smallboot-api/system/src/main/java/com/zhengqing/system/model/dto/SysDictSaveBatchDTO.java new file mode 100644 index 0000000..e459f22 --- /dev/null +++ b/smallboot-api/system/src/main/java/com/zhengqing/system/model/dto/SysDictSaveBatchDTO.java @@ -0,0 +1,64 @@ +package com.zhengqing.system.model.dto; + +import com.zhengqing.common.base.model.dto.BaseDTO; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import lombok.experimental.SuperBuilder; + +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; + +/** + *

+ * 数据字典批量保存参数 + *

+ * + * @author zhengqingya + * @description + * @date 2020/4/15 20:55 + */ +@Data +@SuperBuilder +@NoArgsConstructor +@AllArgsConstructor +@EqualsAndHashCode(callSuper = true) +@ApiModel("数据字典批量保存参数") +public class SysDictSaveBatchDTO extends BaseDTO { + + @ApiModelProperty(value = "字典类型id(关联`t_sys_dict_type`表`id`字段)") + private Integer dictTypeId; + + @ApiModelProperty(value = "字典类型名称(暂只做新增数据时可用)", required = true, example = "按钮权限") + private String dictTypeName; + + @NotBlank(message = "字典类型id不能为空!") + @ApiModelProperty(value = "字典类型编码", example = "permission_btn") + private String code; + + @ApiModelProperty(value = "字典id", example = "1") + private Integer id; + + @NotBlank(message = "字典名称不能为空!") + @ApiModelProperty(value = "名称", required = true, example = "新增") + private String name; + + @NotBlank(message = "字典值不能为空!") + @ApiModelProperty(value = "值", required = true, example = "add") + private String value; + + @NotNull(message = "字典值不能为空!") + @ApiModelProperty(value = "状态(0->停用 1->正常)", required = true, example = "1") + private Integer status; + + @NotNull(message = "展示顺序不能为空!") + @ApiModelProperty(value = "排序", required = true, example = "1") + private Integer sort; + + @ApiModelProperty(value = "备注", example = "this is the add.") + private String remark; + +} diff --git a/smallboot-api/system/src/main/java/com/zhengqing/system/model/dto/SysDictSaveDTO.java b/smallboot-api/system/src/main/java/com/zhengqing/system/model/dto/SysDictSaveDTO.java new file mode 100644 index 0000000..5665834 --- /dev/null +++ b/smallboot-api/system/src/main/java/com/zhengqing/system/model/dto/SysDictSaveDTO.java @@ -0,0 +1,60 @@ +package com.zhengqing.system.model.dto; + +import com.zhengqing.common.core.custom.validator.common.UpdateGroup; +import com.zhengqing.common.core.custom.fieldrepeat.FieldRepeatValidator; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; + +/** + *

+ * 数据字典保存参数 + *

+ * + * @author zhengqingya + * @description + * @date 2020/4/15 20:55 + */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +@ApiModel("数据字典保存参数") +@FieldRepeatValidator(tableName = "t_sys_dict", fieldNames = {"name", "code"}, + dbFieldNames = {"name", "code"}, message = "字段名称重复,请重新输入!") +public class SysDictSaveDTO { + + @NotNull(groups = {UpdateGroup.class}, message = "字典id不能为空!") + @ApiModelProperty(value = "字典id", example = "1") + private Integer id; + +// @NotNull(message = "字典类型id不能为空!") +// @ApiModelProperty(value = "字典类型id(关联`t_sys_dict_type`表`id`字段)", example = "1") +// private Integer dictTypeId; + + @NotBlank(message = "字典类型编码不能为空!") + @ApiModelProperty(value = "字典类型编码", example = "permission_btn") + private String code; + + @NotBlank(message = "字典名称不能为空!") + @ApiModelProperty(value = "名称", example = "新增") + private String name; + + @NotBlank(message = "字典值不能为空!") + @ApiModelProperty(value = "值", required = true, example = "add") + private String value; + + @NotNull(message = "展示顺序不能为空!") + @ApiModelProperty(value = "排序", example = "1") + private Integer sort; + + @ApiModelProperty(value = "备注", example = "this is the add.") + private String remark; + +} diff --git a/smallboot-api/system/src/main/java/com/zhengqing/system/model/dto/SysDictTypeSaveDTO.java b/smallboot-api/system/src/main/java/com/zhengqing/system/model/dto/SysDictTypeSaveDTO.java new file mode 100644 index 0000000..f8238e1 --- /dev/null +++ b/smallboot-api/system/src/main/java/com/zhengqing/system/model/dto/SysDictTypeSaveDTO.java @@ -0,0 +1,50 @@ +package com.zhengqing.system.model.dto; + +import com.zhengqing.common.core.custom.validator.common.CreateGroup; +import com.zhengqing.common.core.custom.validator.common.UpdateGroup; +import com.zhengqing.common.core.custom.fieldrepeat.FieldRepeatValidator; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; + +/** + *

+ * 数据字典类型保存参数 + *

+ * + * @author zhengqingya + * @description + * @date 2020/4/15 20:55 + */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +@ApiModel("数据字典类型保存参数") +@FieldRepeatValidator(tableName = "t_sys_dict_type", fieldNames = {"code"}, dbFieldNames = {"code"}, + message = "字典类型编码重复,请重新输入!") +public class SysDictTypeSaveDTO { + + @NotNull(groups = {UpdateGroup.class}, message = "id不能为空!") + @ApiModelProperty(value = "主键", example = "1") + private Integer id; + + @NotBlank(groups = {CreateGroup.class}, message = "字典类型编码不能为空!") + @ApiModelProperty(value = "字典类型编码-新增时才有用") + private String code; + + @NotBlank(message = "字典类型名称不能为空!") + @ApiModelProperty(value = "字典类型名称(展示用)", required = true, example = "权限按钮") + private String name; + + @NotNull(message = "请选择状态!") + @ApiModelProperty(value = "状态(0->停用 1->正常)", required = true, example = "1") + private Integer status; + +} diff --git a/smallboot-api/system/src/main/java/com/zhengqing/system/model/dto/SysMenuListDTO.java b/smallboot-api/system/src/main/java/com/zhengqing/system/model/dto/SysMenuListDTO.java new file mode 100644 index 0000000..fdfb144 --- /dev/null +++ b/smallboot-api/system/src/main/java/com/zhengqing/system/model/dto/SysMenuListDTO.java @@ -0,0 +1,23 @@ +package com.zhengqing.system.model.dto; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +/** + *

+ * 菜单表查询参数 + *

+ * + * @author zhengqingya + * @description + * @date 2020/4/15 20:55 + */ +@Data +@ApiModel("菜单表查询参数") +public class SysMenuListDTO { + + @ApiModelProperty(value = "菜单ID") + private Integer menuId; + +} diff --git a/smallboot-api/system/src/main/java/com/zhengqing/system/model/dto/SysMenuReBtnPermSaveDTO.java b/smallboot-api/system/src/main/java/com/zhengqing/system/model/dto/SysMenuReBtnPermSaveDTO.java new file mode 100644 index 0000000..6b959f7 --- /dev/null +++ b/smallboot-api/system/src/main/java/com/zhengqing/system/model/dto/SysMenuReBtnPermSaveDTO.java @@ -0,0 +1,44 @@ +package com.zhengqing.system.model.dto; + +import com.zhengqing.common.core.custom.validator.common.UpdateGroup; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; + +/** + *

+ * 菜单按钮保存参数 + *

+ * + * @author zhengqingya + * @description + * @date 2020/4/15 21:00 + */ +@Data +@ApiModel("菜单按钮保存参数") +public class SysMenuReBtnPermSaveDTO { + + @ApiModelProperty(value = "id") + @NotNull(groups = {UpdateGroup.class}, message = "id不能为空!") + private Integer id; + + @NotBlank(message = "名称不能为空!") + @ApiModelProperty(value = "名称") + private String name; + + @NotNull(message = "菜单id") + @ApiModelProperty(value = "菜单ID") + private Integer menuId; + + @NotBlank(message = "按钮权限标识不能为空!") + @ApiModelProperty(value = "按钮权限标识") + private String btnPerm; + + @NotBlank(message = "URL权限标识不能为空!") + @ApiModelProperty(value = "URL权限标识") + private String urlPerm; + +} diff --git a/smallboot-api/system/src/main/java/com/zhengqing/system/model/dto/SysMenuSaveDTO.java b/smallboot-api/system/src/main/java/com/zhengqing/system/model/dto/SysMenuSaveDTO.java new file mode 100644 index 0000000..65a93d4 --- /dev/null +++ b/smallboot-api/system/src/main/java/com/zhengqing/system/model/dto/SysMenuSaveDTO.java @@ -0,0 +1,66 @@ +package com.zhengqing.system.model.dto; + +import com.zhengqing.common.core.custom.validator.common.UpdateGroup; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import javax.validation.constraints.NotNull; + +/** + *

+ * 菜单保存参数 + *

+ * + * @author zhengqingya + * @description + * @date 2020/4/15 20:55 + */ +@Data +@ApiModel +public class SysMenuSaveDTO { + + @NotNull(groups = {UpdateGroup.class}, message = "菜单ID不能为空!") + @ApiModelProperty(value = "菜单ID") + private Integer menuId; + + @ApiModelProperty(value = "菜单名称") + private String title; + + @ApiModelProperty(value = "菜单名称 - 英文") + private String name; + + @ApiModelProperty(value = "菜单图标") + private String icon; + + @ApiModelProperty(value = "菜单链接url") + private String path; + + @ApiModelProperty(value = "父类菜单ID") + private Integer parentId; + + @ApiModelProperty(value = "菜单排序") + private Integer sort; + + @ApiModelProperty(value = "组件名") + private String component; + + @ApiModelProperty(value = "是否隐藏 true:隐藏 false:显示") + private Boolean hidden; + + @ApiModelProperty(value = "重定向路径") + private String redirect; + + @ApiModelProperty(value = "菜单状态 1:启用 0:禁用") + private Integer status; + + @ApiModelProperty(value = "菜单类型 0菜单 1按钮") + private Integer type; + + @ApiModelProperty(value = "0 false 1 true") + private Integer alwaysShow; + + @ApiModelProperty(value = "面包屑") + private Boolean breadcrumb; + +} diff --git a/smallboot-api/system/src/main/java/com/zhengqing/system/model/dto/SysOauthListDTO.java b/smallboot-api/system/src/main/java/com/zhengqing/system/model/dto/SysOauthListDTO.java new file mode 100644 index 0000000..f9a66ae --- /dev/null +++ b/smallboot-api/system/src/main/java/com/zhengqing/system/model/dto/SysOauthListDTO.java @@ -0,0 +1,32 @@ +package com.zhengqing.system.model.dto; + +import com.zhengqing.common.base.model.dto.BaseDTO; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import lombok.experimental.SuperBuilder; + +/** + *

+ * 系统管理 - 用户三方授权列表查询参数 + *

+ * + * @author zhengqingya + * @description + * @date 2020/12/6 13:59 + */ +@Data +@SuperBuilder +@NoArgsConstructor +@AllArgsConstructor +@EqualsAndHashCode(callSuper = true) +@ApiModel("系统管理 - 用户三方授权列表查询参数") +public class SysOauthListDTO extends BaseDTO { + + @ApiModelProperty(value = "用户id") + private Integer userId; + +} diff --git a/smallboot-api/system/src/main/java/com/zhengqing/system/model/dto/SysOauthRemoveBindDTO.java b/smallboot-api/system/src/main/java/com/zhengqing/system/model/dto/SysOauthRemoveBindDTO.java new file mode 100644 index 0000000..87ed5cb --- /dev/null +++ b/smallboot-api/system/src/main/java/com/zhengqing/system/model/dto/SysOauthRemoveBindDTO.java @@ -0,0 +1,35 @@ +package com.zhengqing.system.model.dto; + +import com.zhengqing.common.base.model.dto.BaseDTO; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import lombok.experimental.SuperBuilder; + +/** + *

+ * 系统管理 - 用户三方授权移除绑定参数 + *

+ * + * @author zhengqingya + * @description + * @date 2020/12/6 14:54 + */ +@Data +@SuperBuilder +@NoArgsConstructor +@AllArgsConstructor +@EqualsAndHashCode(callSuper = true) +@ApiModel("系统管理 - 用户三方授权移除绑定参数") +public class SysOauthRemoveBindDTO extends BaseDTO { + + // @ApiModelProperty(value = "用户id") + // private Integer userId; + + @ApiModelProperty(value = "主键id") + private Integer userReOauthId; + +} diff --git a/smallboot-api/system/src/main/java/com/zhengqing/system/model/dto/SysOauthSaveDTO.java b/smallboot-api/system/src/main/java/com/zhengqing/system/model/dto/SysOauthSaveDTO.java new file mode 100644 index 0000000..3cc7dca --- /dev/null +++ b/smallboot-api/system/src/main/java/com/zhengqing/system/model/dto/SysOauthSaveDTO.java @@ -0,0 +1,44 @@ +package com.zhengqing.system.model.dto; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; + +/** + *

+ * 系统管理 - 用户三方授权表提交参数 + *

+ * + * @author zhengqingya + * @description + * @date 2020/11/28 22:14 + */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +@ApiModel("系统管理 - 用户三方授权表提交参数") +public class SysOauthSaveDTO { + + @NotNull(message = "用户id不能为空!") + @ApiModelProperty(value = "用户id") + private Integer userId; + + @NotNull(message = "第三方授权类型不能为空!") + @ApiModelProperty(value = "第三方授权类型") + private Integer oauthType; + + @NotBlank(message = "三方id不能为空!") + @ApiModelProperty(value = "三方id") + private String openId; + + // @ApiModelProperty(value = "三方授权用户信息") + // private ThirdpartOauthUserInfoBO oauthUserInfo; + +} diff --git a/smallboot-api/system/src/main/java/com/zhengqing/system/model/dto/SysPropertySaveDTO.java b/smallboot-api/system/src/main/java/com/zhengqing/system/model/dto/SysPropertySaveDTO.java new file mode 100644 index 0000000..90a343b --- /dev/null +++ b/smallboot-api/system/src/main/java/com/zhengqing/system/model/dto/SysPropertySaveDTO.java @@ -0,0 +1,45 @@ +package com.zhengqing.system.model.dto; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.zhengqing.common.base.model.dto.BaseDTO; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import lombok.experimental.SuperBuilder; + +import javax.validation.constraints.NotBlank; + +/** + *

系统管理-系统属性-保存-提交参数

+ * + * @author zhengqingya + * @description + * @date 2021/09/06 22:57 + */ +@Data +@SuperBuilder +@NoArgsConstructor +@AllArgsConstructor +@EqualsAndHashCode(callSuper = true) +@ApiModel("系统管理-系统属性-保存-提交参数") +public class SysPropertySaveDTO extends BaseDTO { + + @JsonIgnore + @ApiModelProperty(value = "主键ID", hidden = true, example = "1") + private Integer id; + + @NotBlank(message = "属性key不能为空!") + @ApiModelProperty(value = "属性key", required = true, example = "hello") + private String key; + + @NotBlank(message = "属性value不能为空!") + @ApiModelProperty(value = "属性value", required = true, example = "world") + private String value; + + @ApiModelProperty(value = "备注", example = "hello world !") + private String remark; + +} diff --git a/smallboot-api/system/src/main/java/com/zhengqing/system/model/dto/SysRoleListDTO.java b/smallboot-api/system/src/main/java/com/zhengqing/system/model/dto/SysRoleListDTO.java new file mode 100644 index 0000000..429728c --- /dev/null +++ b/smallboot-api/system/src/main/java/com/zhengqing/system/model/dto/SysRoleListDTO.java @@ -0,0 +1,29 @@ +package com.zhengqing.system.model.dto; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + *

+ * 系统管理 - 角色管理查询参数 + *

+ * + * @author zhengqingya + * @description + * @date 2020/4/15 16:35 + */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +@ApiModel("角色管理查询参数") +public class SysRoleListDTO { + + @ApiModelProperty(value = "角色名称") + private String name; + +} diff --git a/smallboot-api/system/src/main/java/com/zhengqing/system/model/dto/SysRoleMenuBtnSaveDTO.java b/smallboot-api/system/src/main/java/com/zhengqing/system/model/dto/SysRoleMenuBtnSaveDTO.java new file mode 100644 index 0000000..06aab3a --- /dev/null +++ b/smallboot-api/system/src/main/java/com/zhengqing/system/model/dto/SysRoleMenuBtnSaveDTO.java @@ -0,0 +1,34 @@ +package com.zhengqing.system.model.dto; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import javax.validation.constraints.NotNull; +import java.util.List; + +/** + *

+ * 角色关联菜单ids提交参数 + *

+ * + * @author zhengqingya + * @description + * @date 2020/9/10 18:48 + */ +@Data +@ApiModel("角色关联菜单ids提交参数") +public class SysRoleMenuBtnSaveDTO { + + @NotNull(message = "角色Id不能为空!") + @ApiModelProperty(value = "角色Id") + private Integer roleId; + + @NotNull(message = "菜单id不能为空!") + @ApiModelProperty(value = "菜单ID") + private Integer menuId; + + @ApiModelProperty(value = "菜单下按钮权限id") + private List permissionIdList; + +} diff --git a/smallboot-api/system/src/main/java/com/zhengqing/system/model/dto/SysRoleMenuSaveDTO.java b/smallboot-api/system/src/main/java/com/zhengqing/system/model/dto/SysRoleMenuSaveDTO.java new file mode 100644 index 0000000..6d50065 --- /dev/null +++ b/smallboot-api/system/src/main/java/com/zhengqing/system/model/dto/SysRoleMenuSaveDTO.java @@ -0,0 +1,31 @@ +package com.zhengqing.system.model.dto; + +import com.zhengqing.common.core.custom.validator.common.UpdateGroup; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import javax.validation.constraints.NotNull; +import java.util.List; + +/** + *

+ * 保存角色菜单权限信息传入参数 + *

+ * + * @author zhengqingya + * @description + * @date 2020/9/14 11:15 + */ +@Data +@ApiModel("保存角色菜单权限信息传入参数") +public class SysRoleMenuSaveDTO { + + @NotNull(message = "角色id不能为空!", groups = UpdateGroup.class) + @ApiModelProperty(value = "角色ID") + private Integer roleId; + + @ApiModelProperty(value = "角色可访问的菜单ids") + private List menuIdList; + +} diff --git a/smallboot-api/system/src/main/java/com/zhengqing/system/model/dto/SysRolePermissionSaveDTO.java b/smallboot-api/system/src/main/java/com/zhengqing/system/model/dto/SysRolePermissionSaveDTO.java new file mode 100644 index 0000000..a8c9a15 --- /dev/null +++ b/smallboot-api/system/src/main/java/com/zhengqing/system/model/dto/SysRolePermissionSaveDTO.java @@ -0,0 +1,35 @@ +package com.zhengqing.system.model.dto; + +import com.zhengqing.common.core.custom.validator.common.UpdateGroup; +import com.zhengqing.system.model.vo.SysMenuTreeVO; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import javax.validation.constraints.NotNull; +import java.util.List; + +/** + *

+ * 保存角色权限信息传入参数 + *

+ * + * @author zhengqingya + * @description + * @date 2020/9/10 15:00 + */ +@Data +@ApiModel("保存角色权限信息传入参数") +public class SysRolePermissionSaveDTO { + + @NotNull(message = "角色id不能为空!", groups = UpdateGroup.class) + @ApiModelProperty(value = "角色ID") + private Integer roleId; + + @ApiModelProperty(value = "角色可访问的菜单ids") + private List menuIdList; + + @ApiModelProperty(value = "菜单树(含拥有的按钮权限,需循环里面的菜单保存其关联的按钮权限信息数据)") + private List menuAndBtnPermissionTree; + +} diff --git a/smallboot-api/system/src/main/java/com/zhengqing/system/model/dto/SysRoleSaveDTO.java b/smallboot-api/system/src/main/java/com/zhengqing/system/model/dto/SysRoleSaveDTO.java new file mode 100644 index 0000000..f239c99 --- /dev/null +++ b/smallboot-api/system/src/main/java/com/zhengqing/system/model/dto/SysRoleSaveDTO.java @@ -0,0 +1,39 @@ +package com.zhengqing.system.model.dto; + +import com.zhengqing.common.core.custom.validator.common.UpdateGroup; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; + +/** + *

+ * 保存角色信息传入参数 + *

+ * + * @author zhengqingya + * @description + * @date 2020/9/10 15:01 + */ +@Data +@ApiModel("保存角色信息传入参数") +public class SysRoleSaveDTO { + + @NotNull(message = "角色id不能为空!", groups = UpdateGroup.class) + @ApiModelProperty(value = "主键ID") + private Integer roleId; + + @NotBlank(message = "角色名不能为空!") + @ApiModelProperty(value = "角色名") + private String name; + + @NotBlank(message = "角色编号不能为空!") + @ApiModelProperty(value = "角色编号") + private String code; + + @ApiModelProperty(value = "状态(1:开启 0:禁用)") + private Integer status; + +} diff --git a/smallboot-api/system/src/main/java/com/zhengqing/system/model/dto/SysUserListDTO.java b/smallboot-api/system/src/main/java/com/zhengqing/system/model/dto/SysUserListDTO.java new file mode 100644 index 0000000..3dd396c --- /dev/null +++ b/smallboot-api/system/src/main/java/com/zhengqing/system/model/dto/SysUserListDTO.java @@ -0,0 +1,32 @@ +package com.zhengqing.system.model.dto; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + *

+ * 系统管理-用户基础信息表查询参数 + *

+ * + * @author zhengqingya + * @description + * @date 2020/4/15 10:50 + */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +@ApiModel("系统管理-用户基础信息表查询参数") +public class SysUserListDTO { + + @ApiModelProperty(value = "账号") + private String username; + + @ApiModelProperty(value = "名称") + private String nickname; + +} diff --git a/smallboot-api/system/src/main/java/com/zhengqing/system/model/dto/SysUserLoginDTO.java b/smallboot-api/system/src/main/java/com/zhengqing/system/model/dto/SysUserLoginDTO.java new file mode 100644 index 0000000..0103a16 --- /dev/null +++ b/smallboot-api/system/src/main/java/com/zhengqing/system/model/dto/SysUserLoginDTO.java @@ -0,0 +1,32 @@ +package com.zhengqing.system.model.dto; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + *

+ * 系统管理-用户登录参数 + *

+ * + * @author zhengqingya + * @description + * @date 2020/4/15 10:50 + */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +@ApiModel("系统管理-用户登录参数") +public class SysUserLoginDTO { + + @ApiModelProperty(value = "账号") + private String username; + + @ApiModelProperty(value = "密码") + private String password; + +} diff --git a/smallboot-api/system/src/main/java/com/zhengqing/system/model/dto/SysUserPermDTO.java b/smallboot-api/system/src/main/java/com/zhengqing/system/model/dto/SysUserPermDTO.java new file mode 100644 index 0000000..a6fb904 --- /dev/null +++ b/smallboot-api/system/src/main/java/com/zhengqing/system/model/dto/SysUserPermDTO.java @@ -0,0 +1,33 @@ +package com.zhengqing.system.model.dto; + +import com.zhengqing.common.base.model.dto.BaseDTO; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.SuperBuilder; + +/** + *

+ * 系统管理 - 用户信息查询 + *

+ * + * @author zhengqingya + * @description + * @date 2020/4/15 10:48 + */ +@Data +@SuperBuilder +@NoArgsConstructor +@AllArgsConstructor +@ApiModel +public class SysUserPermDTO extends BaseDTO { + + @ApiModelProperty(value = "用户ID") + private Integer userId; + + @ApiModelProperty(value = "账号") + private String username; + +} diff --git a/smallboot-api/system/src/main/java/com/zhengqing/system/model/dto/SysUserRoleListDTO.java b/smallboot-api/system/src/main/java/com/zhengqing/system/model/dto/SysUserRoleListDTO.java new file mode 100644 index 0000000..ad4a1b0 --- /dev/null +++ b/smallboot-api/system/src/main/java/com/zhengqing/system/model/dto/SysUserRoleListDTO.java @@ -0,0 +1,23 @@ +package com.zhengqing.system.model.dto; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +/** + *

+ * 用户角色关联表查询参数 + *

+ * + * @author zhengqingya + * @description + * @date 2020/4/15 20:55 + */ +@Data +@ApiModel("用户角色关联表查询参数") +public class SysUserRoleListDTO { + + @ApiModelProperty(value = "id") + private String id; + +} diff --git a/smallboot-api/system/src/main/java/com/zhengqing/system/model/dto/SysUserRoleSaveDTO.java b/smallboot-api/system/src/main/java/com/zhengqing/system/model/dto/SysUserRoleSaveDTO.java new file mode 100644 index 0000000..7aea80e --- /dev/null +++ b/smallboot-api/system/src/main/java/com/zhengqing/system/model/dto/SysUserRoleSaveDTO.java @@ -0,0 +1,30 @@ +package com.zhengqing.system.model.dto; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import javax.validation.constraints.NotNull; +import java.util.List; + +/** + *

+ * 保存用户角色参数 + *

+ * + * @author zhengqingya + * @description + * @date 2020/9/10 14:19 + */ +@Data +@ApiModel("保存用户角色参数") +public class SysUserRoleSaveDTO { + + @NotNull(message = "用户id不能为空!") + @ApiModelProperty("用户ID") + private Integer userId; + + @ApiModelProperty("角色ids") + private List roleIdList; + +} diff --git a/smallboot-api/system/src/main/java/com/zhengqing/system/model/dto/SysUserSaveDTO.java b/smallboot-api/system/src/main/java/com/zhengqing/system/model/dto/SysUserSaveDTO.java new file mode 100644 index 0000000..dddb492 --- /dev/null +++ b/smallboot-api/system/src/main/java/com/zhengqing/system/model/dto/SysUserSaveDTO.java @@ -0,0 +1,64 @@ +package com.zhengqing.system.model.dto; + +import com.zhengqing.common.core.custom.fieldrepeat.FieldRepeatValidator; +import com.zhengqing.common.core.custom.validator.common.UpdateGroup; +import com.zhengqing.common.core.enums.UserSexEnum; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import org.hibernate.validator.constraints.Length; + +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; +import javax.validation.constraints.Pattern; + +/** + *

+ * 保存用户参数 + *

+ * + * @author zhengqingya + * @description + * @date 2020/9/10 11:15 + */ +@Data +@ApiModel("保存用户参数") +@FieldRepeatValidator(tableName = "t_sys_user", idDbName = "user_id", fieldNames = "username", + dbFieldNames = {"username"}, message = "账号重复,请重新输入账号!") +public class SysUserSaveDTO { + + @ApiModelProperty(value = "主键ID groups:标识在更新的时候才能验证非空") + @NotNull(message = "用户id不能为空", groups = {UpdateGroup.class}) + private Integer userId; + + @ApiModelProperty(value = "账号") + @NotBlank(message = "账号不能为空") + @Length(max = 100, message = "账号不能超过100个字符") + @Pattern(regexp = "^[\\u4E00-\\u9FA5A-Za-z0-9\\*]*$", message = "账号限制:最多100字符,包含文字、字母和数字") + private String username; + + @ApiModelProperty(value = "昵称") + @NotBlank(message = "昵称不能为空") + @Pattern(regexp = "^[\\u4E00-\\u9FA5A-Za-z0-9\\*]*$", message = "账号限制:最多100字符,包含文字、字母和数字") + private String nickname; + + /** + * {@link UserSexEnum} + */ + @ApiModelProperty(value = "性别") + private Byte sex; + + @ApiModelProperty(value = "手机号码") + // @NotBlank(message = "手机号不能为空") + // @Pattern(regexp = "^[1][3,4,5,6,7,8,9][0-9]{9}$", message = "手机号格式有误") + private String phone; + + @ApiModelProperty(value = "邮箱") + // @NotBlank(message = "联系邮箱不能为空") + // @Email(message = "邮箱格式不对") + private String email; + + @ApiModelProperty(value = "头像") + private String avatarUrl; + +} diff --git a/smallboot-api/system/src/main/java/com/zhengqing/system/model/dto/SysUserUpdatePasswordDTO.java b/smallboot-api/system/src/main/java/com/zhengqing/system/model/dto/SysUserUpdatePasswordDTO.java new file mode 100644 index 0000000..1b034d6 --- /dev/null +++ b/smallboot-api/system/src/main/java/com/zhengqing/system/model/dto/SysUserUpdatePasswordDTO.java @@ -0,0 +1,44 @@ +package com.zhengqing.system.model.dto; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +import org.hibernate.validator.constraints.Length; + +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; + +/** + *

+ * 系统管理-用户基础信息表查询参数 + *

+ * + * @author zhengqingya + * @description + * @date 2020/4/15 10:50 + */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +@ApiModel("系统管理-用户基础信息表查询参数") +public class SysUserUpdatePasswordDTO { + + @NotNull(message = "用户ID不能为空!") + @ApiModelProperty(value = "用户ID") + private Integer userId; + + @Length(min = 6, message = "密码最少6位数!") + @NotBlank(message = "久密码不能为空!") + @ApiModelProperty(value = "密码") + private String password; + + @Length(min = 6, message = "密码最少6位数!") + @NotBlank(message = "新密码不能为空!") + @ApiModelProperty("新密码") + private String newPassword; + +} diff --git a/smallboot-api/system/src/main/java/com/zhengqing/system/model/vo/SysDictTypeListVO.java b/smallboot-api/system/src/main/java/com/zhengqing/system/model/vo/SysDictTypeListVO.java new file mode 100644 index 0000000..62e05c3 --- /dev/null +++ b/smallboot-api/system/src/main/java/com/zhengqing/system/model/vo/SysDictTypeListVO.java @@ -0,0 +1,30 @@ +package com.zhengqing.system.model.vo; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +/** + *

数据字典类型-响应参数

+ * + * @author zhengqingya + * @description + * @date 2021/8/15 4:37 下午 + */ +@Data +@ApiModel("数据字典类型-响应参数") +public class SysDictTypeListVO { + + @ApiModelProperty(value = "主键") + private Integer id; + + @ApiModelProperty(value = "字典类型编码") + private String code; + + @ApiModelProperty(value = "字典类型名称(展示用)") + private String name; + + @ApiModelProperty(value = "排序") + private Integer sort; + +} diff --git a/smallboot-api/system/src/main/java/com/zhengqing/system/model/vo/SysDictVO.java b/smallboot-api/system/src/main/java/com/zhengqing/system/model/vo/SysDictVO.java new file mode 100644 index 0000000..5dedf2c --- /dev/null +++ b/smallboot-api/system/src/main/java/com/zhengqing/system/model/vo/SysDictVO.java @@ -0,0 +1,51 @@ +package com.zhengqing.system.model.vo; + +import com.zhengqing.common.base.model.vo.BaseVO; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import lombok.experimental.SuperBuilder; + + +/** + *

+ * 数据字典表 + *

+ * + * @author zhengqingya + * @description + * @date 2020/4/15 20:57 + */ +@Data +@SuperBuilder +@NoArgsConstructor +@AllArgsConstructor +@EqualsAndHashCode(callSuper = true) +@ApiModel("数据字典表") +public class SysDictVO extends BaseVO { + + @ApiModelProperty(value = "字典类型id(关联`t_sys_dict_type`表`id`字段)") + private Integer dictTypeId; + + @ApiModelProperty(value = "类型编码") + private String code; + + @ApiModelProperty(value = "字典ID") + private Integer id; + + @ApiModelProperty(value = "字典名(展示用)") + private String name; + + @ApiModelProperty(value = "字典值") + private String value; + + @ApiModelProperty(value = "展示排序") + private Integer sort; + + @ApiModelProperty(value = "备注") + private String remark; + +} diff --git a/smallboot-api/system/src/main/java/com/zhengqing/system/model/vo/SysMenuReBtnPermListVO.java b/smallboot-api/system/src/main/java/com/zhengqing/system/model/vo/SysMenuReBtnPermListVO.java new file mode 100644 index 0000000..3d43ba3 --- /dev/null +++ b/smallboot-api/system/src/main/java/com/zhengqing/system/model/vo/SysMenuReBtnPermListVO.java @@ -0,0 +1,35 @@ +package com.zhengqing.system.model.vo; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +/** + *

+ * 菜单按钮权限展示参数 + *

+ * + * @author zhengqingya + * @description + * @date 2020/9/10 22:03 + */ +@Data +@ApiModel("菜单按钮权限展示参数") +public class SysMenuReBtnPermListVO { + + @ApiModelProperty(value = "id") + private Integer id; + + @ApiModelProperty(value = "菜单ID") + private Integer menuId; + + @ApiModelProperty(value = "权限名称") + private String name; + + @ApiModelProperty(value = "按钮权限标识") + private String btnPerm; + + @ApiModelProperty(value = "URL权限标识") + private String urlPerm; + +} diff --git a/smallboot-api/system/src/main/java/com/zhengqing/system/model/vo/SysMenuTreeVO.java b/smallboot-api/system/src/main/java/com/zhengqing/system/model/vo/SysMenuTreeVO.java new file mode 100644 index 0000000..bfe324a --- /dev/null +++ b/smallboot-api/system/src/main/java/com/zhengqing/system/model/vo/SysMenuTreeVO.java @@ -0,0 +1,88 @@ +package com.zhengqing.system.model.vo; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.SuperBuilder; + +import java.util.LinkedList; +import java.util.List; + +/** + *

+ * 用户菜单权限树 + *

+ * + * @author zhengqingya + * @description + * @date 2020/4/15 20:54 + */ +@Data +@SuperBuilder +@NoArgsConstructor +@AllArgsConstructor +@ApiModel +public class SysMenuTreeVO { + + @ApiModelProperty(value = "菜单ID") + private Integer menuId; + + @ApiModelProperty(value = "菜单名称") + private String title; + + @ApiModelProperty(value = "菜单名称 - 英文") + private String name; + + @ApiModelProperty(value = "菜单图标") + private String icon; + + @ApiModelProperty(value = "菜单链接") + private String path; + + @ApiModelProperty(value = "上级菜单ID") + private Integer parentId; + + @ApiModelProperty(value = "上级菜单名") + private String parentName; + + @ApiModelProperty(value = "显示顺序") + private Integer sort; + + @ApiModelProperty(value = "组件名") + private String component; + + @ApiModelProperty(value = "是否隐藏 true:隐藏 false:显示") + private Boolean hidden; + + @ApiModelProperty(value = "重定向url") + private String redirect; + + @ApiModelProperty(value = "菜单状态") + private Integer status; + + @ApiModelProperty(value = "类型 0菜单 1按钮") + private Integer type; + + @ApiModelProperty(value = "菜单只有一级的情况为true") + private Integer alwaysShow; + + @ApiModelProperty(value = "面包屑") + private Boolean breadcrumb; + + @ApiModelProperty(value = "下级菜单") + private List children = new LinkedList<>(); + + @ApiModelProperty(value = "按钮权限") + private SysUserBtnVO meta; + + // 下面属性只在角色管理页面-权限中使用 + + @ApiModelProperty(value = "菜单关联的所有按钮信息") + private List btnInfoList; + + @ApiModelProperty(value = "角色关联菜单下的所拥有的按钮权限信息") + private List permissionIdList; + +} diff --git a/smallboot-api/system/src/main/java/com/zhengqing/system/model/vo/SysOauthClientVO.java b/smallboot-api/system/src/main/java/com/zhengqing/system/model/vo/SysOauthClientVO.java new file mode 100644 index 0000000..579318a --- /dev/null +++ b/smallboot-api/system/src/main/java/com/zhengqing/system/model/vo/SysOauthClientVO.java @@ -0,0 +1,63 @@ +package com.zhengqing.system.model.vo; + +import com.zhengqing.common.base.model.vo.BaseVO; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import lombok.experimental.SuperBuilder; + +/** + *

oauth客户端-响应参数

+ * + * @author zhengqingya + * @description + * @date 2022/06/10 16:25 + */ +@Data +@SuperBuilder +@NoArgsConstructor +@AllArgsConstructor +@EqualsAndHashCode(callSuper = true) +@ApiModel +public class SysOauthClientVO extends BaseVO { + + @ApiModelProperty("客户端ID,唯一标识") + private String clientId; + + @ApiModelProperty("客户端访问秘钥,BCryptPasswordEncoder加密算法加密") + private String clientSecret; + + @ApiModelProperty("可访问资源id(英文逗号分隔)") + private String resourceIds; + + @ApiModelProperty("授权范围(英文逗号分隔)") + private String scope; + + @ApiModelProperty("授权类型(英文逗号分隔)") + private String authorizedGrantTypes; + + @ApiModelProperty("重定向uri") + private String webServerRedirectUri; + + @ApiModelProperty("@PreAuthorize(\"hasAuthority('admin')\")可以在方法上标志 用户或者说client 需要说明样的权限\n" + + "指定客户端所拥有的Spring Security的权限值\n" + + "(英文逗号分隔)") + private String authorities; + + @ApiModelProperty("令牌有效期(单位:秒)") + private Integer accessTokenValidity; + + @ApiModelProperty("刷新令牌有效期(单位:秒)") + private Integer refreshTokenValidity; + + @ApiModelProperty("预留字段,在Oauth的流程中没有实际的使用(JSON格式数据)") + private String additionalInformation; + + @ApiModelProperty("设置用户是否自动Approval操作, 默认值为 'false' 可选值包括 'true','false', 'read','write'.\n" + + "该字段只适用于grant_type=\"authorization_code\"的情况,当用户登录成功后,若该值为'true'或支持的scope值,则会跳过用户Approve的页面, 直接授权") + private String autoapprove; + +} diff --git a/smallboot-api/system/src/main/java/com/zhengqing/system/model/vo/SysOauthDataListVO.java b/smallboot-api/system/src/main/java/com/zhengqing/system/model/vo/SysOauthDataListVO.java new file mode 100644 index 0000000..6c276e4 --- /dev/null +++ b/smallboot-api/system/src/main/java/com/zhengqing/system/model/vo/SysOauthDataListVO.java @@ -0,0 +1,38 @@ +package com.zhengqing.system.model.vo; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +/** + *

+ * 第三方授权数据展示参数 + *

+ * + * @author zhengqingya + * @description + * @date 2020/12/6 13:50 + */ +@Data +@ApiModel("第三方授权数据展示参数") +public class SysOauthDataListVO { + + @ApiModelProperty(value = "三方绑定主键id(做解除绑定时使用)") + private Integer userReOauthId; + + @ApiModelProperty(value = "第三方授权类型") + private Integer oauthType; + + @ApiModelProperty(value = "第三方授权类型名称") + private String oauthTypeName; + + @ApiModelProperty(value = "第三方授权类型绑定名称") + private String oauthTypeBindName; + + @ApiModelProperty(value = "第三方授权类型描述") + private String oauthTypeDesc; + + @ApiModelProperty(value = "第三方授权类型名称") + private Integer ifBind; + +} diff --git a/smallboot-api/system/src/main/java/com/zhengqing/system/model/vo/SysOauthListVO.java b/smallboot-api/system/src/main/java/com/zhengqing/system/model/vo/SysOauthListVO.java new file mode 100644 index 0000000..3972581 --- /dev/null +++ b/smallboot-api/system/src/main/java/com/zhengqing/system/model/vo/SysOauthListVO.java @@ -0,0 +1,32 @@ +package com.zhengqing.system.model.vo; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +/** + *

+ * 系统管理 - 用户三方授权表展示视图 + *

+ * + * @author zhengqingya + * @description + * @date 2020/11/28 22:14 + */ +@Data +@ApiModel("系统管理 - 用户三方授权表展示视图") +public class SysOauthListVO { + + @ApiModelProperty(value = "主键id") + private Integer userReOauthId; + + @ApiModelProperty(value = "用户id") + private Integer userId; + + @ApiModelProperty(value = "三方id") + private String openId; + + @ApiModelProperty(value = "第三方授权类型") + private Integer oauthType; + +} diff --git a/smallboot-api/system/src/main/java/com/zhengqing/system/model/vo/SysPermissionVO.java b/smallboot-api/system/src/main/java/com/zhengqing/system/model/vo/SysPermissionVO.java new file mode 100644 index 0000000..b40e998 --- /dev/null +++ b/smallboot-api/system/src/main/java/com/zhengqing/system/model/vo/SysPermissionVO.java @@ -0,0 +1,43 @@ +package com.zhengqing.system.model.vo; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.util.List; + +/** + *

+ * 系统管理 - 角色+菜单+所拥有按钮权限 输出内容 + *

+ * + * @author zhengqingya + * @description + * @date 2020/4/15 16:19 + */ +@Data +@ApiModel("系统管理 - 角色+菜单+所拥有按钮权限 输出内容") +public class SysPermissionVO { + + @ApiModelProperty(value = "用户ID") + private String userId; + + @ApiModelProperty(value = "角色名") + private String roleNames; + + @ApiModelProperty(value = "角色编号") + private String roleCodes; + + @ApiModelProperty(value = "角色ID") + private List roleIdList; + + @ApiModelProperty(value = "角色") + private List roleList; + + @ApiModelProperty(value = "角色可访问的菜单ID") + private List menuIdList; + + @ApiModelProperty(value = "菜单树(含拥有的按钮权限)") + private List menuAndBtnPermissionTree; + +} diff --git a/smallboot-api/system/src/main/java/com/zhengqing/system/model/vo/SysPropertyVO.java b/smallboot-api/system/src/main/java/com/zhengqing/system/model/vo/SysPropertyVO.java new file mode 100644 index 0000000..e3a725f --- /dev/null +++ b/smallboot-api/system/src/main/java/com/zhengqing/system/model/vo/SysPropertyVO.java @@ -0,0 +1,42 @@ +package com.zhengqing.system.model.vo; + + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.zhengqing.common.base.model.vo.BaseVO; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import lombok.experimental.SuperBuilder; + +/** + *

系统管理-系统属性-列表-响应参数

+ * + * @author zhengqingya + * @description + * @date 2021/09/06 22:57 + */ +@Data +@SuperBuilder +@NoArgsConstructor +@AllArgsConstructor +@EqualsAndHashCode(callSuper = true) +@ApiModel("系统管理-系统属性-列表-响应参数") +public class SysPropertyVO extends BaseVO { + + @JsonIgnore + @ApiModelProperty(value = "主键ID", hidden = true) + private String id; + + @ApiModelProperty("属性key") + private String key; + + @ApiModelProperty("属性value") + private String value; + + @ApiModelProperty("备注") + private String remark; + +} diff --git a/smallboot-api/system/src/main/java/com/zhengqing/system/model/vo/SysRoleAllPermissionDetailVO.java b/smallboot-api/system/src/main/java/com/zhengqing/system/model/vo/SysRoleAllPermissionDetailVO.java new file mode 100644 index 0000000..51b8c6c --- /dev/null +++ b/smallboot-api/system/src/main/java/com/zhengqing/system/model/vo/SysRoleAllPermissionDetailVO.java @@ -0,0 +1,40 @@ +package com.zhengqing.system.model.vo; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.util.List; + +/** + *

+ * 角色具体权限输出内容(带菜单+按钮+所拥有的权限信息) + *

+ * + * @author zhengqingya + * @description + * @date 2020/9/11 16:04 + */ +@Data +@ApiModel("角色具体权限输出内容(带菜单+按钮+所拥有的权限信息)") +public class SysRoleAllPermissionDetailVO { + + @ApiModelProperty(value = "角色ID") + private Integer roleId; + + @ApiModelProperty(value = "角色名") + private String name; + + @ApiModelProperty(value = "角色编号") + private String code; + + @ApiModelProperty(value = "状态(1:开启 0:禁用)") + private Integer status; + + @ApiModelProperty(value = "角色可访问的菜单ids") + private List menuIdList; + + @ApiModelProperty(value = "菜单树(含拥有的按钮权限)") + private List menuAndBtnPermissionTree; + +} diff --git a/smallboot-api/system/src/main/java/com/zhengqing/system/model/vo/SysRoleListVO.java b/smallboot-api/system/src/main/java/com/zhengqing/system/model/vo/SysRoleListVO.java new file mode 100644 index 0000000..4d05c8e --- /dev/null +++ b/smallboot-api/system/src/main/java/com/zhengqing/system/model/vo/SysRoleListVO.java @@ -0,0 +1,32 @@ +package com.zhengqing.system.model.vo; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +/** + *

+ * 系统管理-角色表 输出内容 + *

+ * + * @author zhengqingya + * @description + * @date 2020/4/15 16:19 + */ +@Data +@ApiModel("系统管理 - 角色表 输出内容") +public class SysRoleListVO { + + @ApiModelProperty(value = "角色ID") + private Integer roleId; + + @ApiModelProperty(value = "角色名") + private String name; + + @ApiModelProperty(value = "角色编号") + private String code; + + @ApiModelProperty(value = "状态(1:开启 0:禁用)") + private Integer status; + +} diff --git a/smallboot-api/system/src/main/java/com/zhengqing/system/model/vo/SysRoleMenuBtnListVO.java b/smallboot-api/system/src/main/java/com/zhengqing/system/model/vo/SysRoleMenuBtnListVO.java new file mode 100644 index 0000000..d6629f3 --- /dev/null +++ b/smallboot-api/system/src/main/java/com/zhengqing/system/model/vo/SysRoleMenuBtnListVO.java @@ -0,0 +1,32 @@ +package com.zhengqing.system.model.vo; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +/** + *

+ * 角色关联菜单按钮输出参数 + *

+ * + * @author zhengqingya + * @description + * @date 2020/4/15 20:58 + */ +@Data +@ApiModel("角色关联菜单按钮输出参数") +public class SysRoleMenuBtnListVO { + + @ApiModelProperty(value = "角色ID") + private Integer roleId; + + @ApiModelProperty(value = "菜单ID") + private Integer menuId; + + @ApiModelProperty(value = "按钮ID") + private Integer btnId; + + @ApiModelProperty(value = "按钮值") + private String btnPerm; + +} diff --git a/smallboot-api/system/src/main/java/com/zhengqing/system/model/vo/SysRolePermissionDetailVO.java b/smallboot-api/system/src/main/java/com/zhengqing/system/model/vo/SysRolePermissionDetailVO.java new file mode 100644 index 0000000..a81b2a9 --- /dev/null +++ b/smallboot-api/system/src/main/java/com/zhengqing/system/model/vo/SysRolePermissionDetailVO.java @@ -0,0 +1,44 @@ +package com.zhengqing.system.model.vo; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.SuperBuilder; + +import java.util.List; + +/** + *

+ * 角色权限输出内容 + *

+ * + * @author zhengqingya + * @description + * @date 2020/9/10 14:48 + */ +@Data +@SuperBuilder +@AllArgsConstructor +@NoArgsConstructor +@ApiModel("角色权限输出内容") +public class SysRolePermissionDetailVO { + + @ApiModelProperty(value = "角色ID") + private Integer roleId; + + @ApiModelProperty(value = "角色名") + private String name; + + @ApiModelProperty(value = "角色编号") + private String code; + + @ApiModelProperty(value = "状态(1:开启 0:禁用)") + private Integer status; + + @ApiModelProperty(value = "角色可访问的菜单ids") + private List menuIdList; + +} diff --git a/smallboot-api/system/src/main/java/com/zhengqing/system/model/vo/SysRoleRePermListVO.java b/smallboot-api/system/src/main/java/com/zhengqing/system/model/vo/SysRoleRePermListVO.java new file mode 100644 index 0000000..2e5e9cc --- /dev/null +++ b/smallboot-api/system/src/main/java/com/zhengqing/system/model/vo/SysRoleRePermListVO.java @@ -0,0 +1,44 @@ +package com.zhengqing.system.model.vo; + +import com.zhengqing.common.base.model.vo.BaseVO; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.SuperBuilder; + +import java.util.List; + +/** + *

+ * 角色 -- url/btn权限 + *

+ * + * @author zhengqingya + * @description + * @date 2020/9/10 22:03 + */ +@Data +@SuperBuilder +@NoArgsConstructor +@AllArgsConstructor +@ApiModel +public class SysRoleRePermListVO extends BaseVO { + + @ApiModelProperty(value = "权限描述") + private String name; + + @ApiModelProperty(value = "菜单ID") + private Integer menuId; + + @ApiModelProperty(value = "url权限标识") + private String urlPerm; + + @ApiModelProperty(value = "按钮权限标识") + private String btnPerm; + + @ApiModelProperty(value = "角色编码") + private List roleCodeList; + +} diff --git a/smallboot-api/system/src/main/java/com/zhengqing/system/model/vo/SysUserBtnVO.java b/smallboot-api/system/src/main/java/com/zhengqing/system/model/vo/SysUserBtnVO.java new file mode 100644 index 0000000..2a3632b --- /dev/null +++ b/smallboot-api/system/src/main/java/com/zhengqing/system/model/vo/SysUserBtnVO.java @@ -0,0 +1,38 @@ +package com.zhengqing.system.model.vo; + +import com.zhengqing.common.base.model.vo.BaseVO; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.SuperBuilder; + +import java.util.List; + +/** + *

+ * 用户按钮权限 + *

+ * + * @author zhengqingya + * @description + * @date 2020/9/10 22:03 + */ +@Data +@SuperBuilder +@NoArgsConstructor +@AllArgsConstructor +@ApiModel +public class SysUserBtnVO extends BaseVO { + + @ApiModelProperty(value = "标题") + private String title; + + @ApiModelProperty(value = "图标") + private String icon; + + @ApiModelProperty(value = "按钮权限") + private List btnPermList; + +} diff --git a/smallboot-api/system/src/main/java/com/zhengqing/system/model/vo/SysUserDetailVO.java b/smallboot-api/system/src/main/java/com/zhengqing/system/model/vo/SysUserDetailVO.java new file mode 100644 index 0000000..4d6bfee --- /dev/null +++ b/smallboot-api/system/src/main/java/com/zhengqing/system/model/vo/SysUserDetailVO.java @@ -0,0 +1,57 @@ +package com.zhengqing.system.model.vo; + +import com.zhengqing.common.core.enums.UserSexEnum; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.util.Date; + +/** + *

+ * 系统管理 - 用户详情-展示内容 + *

+ * + * @author zhengqingya + * @description + * @date 2020/4/15 10:48 + */ +@Data +@ApiModel +public class SysUserDetailVO { + + @ApiModelProperty(value = "主键ID") + private Integer userId; + + @ApiModelProperty(value = "账号") + private String username; + + @ApiModelProperty(value = "昵称") + private String nickname; + + /** + * {@link com.zhengqing.common.core.enums.UserSexEnum} + */ + @ApiModelProperty(value = "性别") + private Byte sex; + + @ApiModelProperty("性别值") + private String sexName; + + @ApiModelProperty(value = "手机号码") + private String phone; + + @ApiModelProperty(value = "邮箱") + private String email; + + @ApiModelProperty(value = "头像") + private String avatar; + + @ApiModelProperty(value = "创建时间") + private Date createTime; + + public void handleData() { + this.sexName = UserSexEnum.getEnum(this.sex).getDesc(); + } + +} diff --git a/smallboot-api/system/src/main/java/com/zhengqing/system/model/vo/SysUserInfoVO.java b/smallboot-api/system/src/main/java/com/zhengqing/system/model/vo/SysUserInfoVO.java new file mode 100644 index 0000000..974ecb4 --- /dev/null +++ b/smallboot-api/system/src/main/java/com/zhengqing/system/model/vo/SysUserInfoVO.java @@ -0,0 +1,43 @@ +package com.zhengqing.system.model.vo; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.util.Date; + +/** + *

+ * 用户个人中心 + *

+ * + * @author zhengqingya + * @description + * @date 2020/4/15 11:18 + */ +@Data +@ApiModel("用户个人中心") +public class SysUserInfoVO { + + @ApiModelProperty(value = "用户账号") + private String username; + + @ApiModelProperty(value = "用户名称") + private String nickname; + + @ApiModelProperty(value = "创建时间") + private Date createTime; + + @ApiModelProperty(value = "性别") + private String sexName; + + @ApiModelProperty(value = "手机号码") + private String phone; + + @ApiModelProperty(value = "邮箱") + private String email; + + @ApiModelProperty(value = "头像") + private String avatar; + +} diff --git a/smallboot-api/system/src/main/java/com/zhengqing/system/model/vo/SysUserListVO.java b/smallboot-api/system/src/main/java/com/zhengqing/system/model/vo/SysUserListVO.java new file mode 100644 index 0000000..9f22878 --- /dev/null +++ b/smallboot-api/system/src/main/java/com/zhengqing/system/model/vo/SysUserListVO.java @@ -0,0 +1,61 @@ +package com.zhengqing.system.model.vo; + +import com.zhengqing.common.core.enums.UserSexEnum; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.util.Date; +import java.util.List; + +/** + *

+ * 系统管理 - 用户信息列表-展示内容 + *

+ * + * @author zhengqingya + * @description + * @date 2020/4/15 10:48 + */ +@Data +@ApiModel +public class SysUserListVO { + + @ApiModelProperty(value = "主键ID") + private Integer userId; + + @ApiModelProperty(value = "账号") + private String username; + + @ApiModelProperty(value = "昵称") + private String nickname; + + /** + * {@link com.zhengqing.common.core.enums.UserSexEnum} + */ + @ApiModelProperty(value = "性别") + private Byte sex; + + @ApiModelProperty("性别值") + private String sexName; + + @ApiModelProperty(value = "手机号码") + private String phone; + + @ApiModelProperty(value = "邮箱") + private String email; + + @ApiModelProperty(value = "头像") + private String avatarUrl; + + @ApiModelProperty(value = "创建时间") + private Date createTime; + + @ApiModelProperty("角色ids") + private List roleIdList; + + public void handleData() { + this.sexName = UserSexEnum.getEnum(this.sex).getDesc(); + } + +} diff --git a/smallboot-api/system/src/main/java/com/zhengqing/system/model/vo/SysUserPermVO.java b/smallboot-api/system/src/main/java/com/zhengqing/system/model/vo/SysUserPermVO.java new file mode 100644 index 0000000..c89aa92 --- /dev/null +++ b/smallboot-api/system/src/main/java/com/zhengqing/system/model/vo/SysUserPermVO.java @@ -0,0 +1,69 @@ +package com.zhengqing.system.model.vo; + +import com.zhengqing.common.base.model.vo.BaseVO; +import com.zhengqing.common.core.enums.UserSexEnum; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.SuperBuilder; + +import java.util.List; + +/** + *

+ * 系统管理 - 用户信息 + *

+ * + * @author zhengqingya + * @description 基本信息+角色+权限... + * @date 2020/4/15 10:48 + */ +@Data +@SuperBuilder +@NoArgsConstructor +@AllArgsConstructor +@ApiModel +public class SysUserPermVO extends BaseVO { + + // ================= ↓↓↓↓↓↓ 基本信息 ↓↓↓↓↓↓ ================= + + @ApiModelProperty(value = "用户ID") + private Integer userId; + + @ApiModelProperty(value = "账号") + private String username; + + @ApiModelProperty(value = "登录密码", hidden = true) + private String password; + + @ApiModelProperty(value = "昵称") + private String nickname; + + @ApiModelProperty(value = "性别") + private UserSexEnum sexEnum; + + @ApiModelProperty(value = "手机号码") + private String phone; + + @ApiModelProperty(value = "邮箱") + private String email; + + @ApiModelProperty(value = "头像") + private String avatarUrl; + + // ================= ↓↓↓↓↓↓ 角色信息 ↓↓↓↓↓↓ ================= + + @ApiModelProperty("角色名") + private String roleNames; + + @ApiModelProperty("角色编码") + private List roleCodeList; + + // ================= ↓↓↓↓↓↓ 权限信息 ↓↓↓↓↓↓ ================= + + @ApiModelProperty(value = "角色可访问菜单+按钮权限") + private List permissionTreeList; + +} diff --git a/smallboot-api/system/src/main/java/com/zhengqing/system/service/IAuthService.java b/smallboot-api/system/src/main/java/com/zhengqing/system/service/IAuthService.java new file mode 100644 index 0000000..5bd30c1 --- /dev/null +++ b/smallboot-api/system/src/main/java/com/zhengqing/system/service/IAuthService.java @@ -0,0 +1,27 @@ +package com.zhengqing.common.auth.service; + +import com.zhengqing.common.auth.model.dto.AuthLoginDTO; +import com.zhengqing.common.auth.model.vo.AuthLoginVO; + +/** + *

+ * 授权 服务类 + *

+ * + * @author zhengqingya + * @description + * @date 2020/4/15 11:33 + */ +public interface IAuthService { + + /** + * 登录 + * + * @param params 参数 + * @return token + * @author zhengqingya + * @date 2020/9/21 16:18 + */ + AuthLoginVO login(AuthLoginDTO params); + +} diff --git a/smallboot-api/system/src/main/java/com/zhengqing/system/service/ISysDictService.java b/smallboot-api/system/src/main/java/com/zhengqing/system/service/ISysDictService.java new file mode 100644 index 0000000..8801b42 --- /dev/null +++ b/smallboot-api/system/src/main/java/com/zhengqing/system/service/ISysDictService.java @@ -0,0 +1,146 @@ +package com.zhengqing.system.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.zhengqing.common.core.custom.validator.common.ValidList; +import com.zhengqing.system.entity.SysDict; +import com.zhengqing.system.model.dto.SysDictSaveBatchDTO; +import com.zhengqing.system.model.dto.SysDictSaveDTO; +import com.zhengqing.system.model.vo.SysDictVO; + +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotEmpty; +import java.util.List; +import java.util.Map; + +/** + *

+ * 数据字典-服务类 + *

+ * + * @author zhengqingya + * @description + * @date 2020/4/15 20:53 + */ +public interface ISysDictService extends IService { + + /** + * 通过类型code获取数据字典列表数据(启用+禁用的数据一起) + * + * @param code 类型编码 + * @return 数据字典列表数据 + * @author zhengqingya + * @date 2020/9/12 17:38 + */ + List listByCode(@NotBlank(message = "查询编码不能为空!") String code); + + /** + * 通过类型code获取数据字典列表数据(启用数据) + * + * @param codeList 类型编码 + * @return 字典类型 -> 字典列表数据 + * @author zhengqingya + * @date 2020/9/12 17:38 + */ + Map> listByOpenCode(@NotEmpty(message = "查询编码不能为空!") List codeList); + + /** + * 通过类型code获取数据字典列表数据 - 数据库方式(只有启用的数据) + * + * @param codeList 类型编码 + * @return 字典类型 -> 字典列表数据 + * @author zhengqingya + * @date 2020/9/12 17:38 + */ + Map> listFromDbByOpenCode(@NotEmpty(message = "查询编码不能为空!") List codeList); + + /** + * 通过类型code获取数据字典列表数据 - 从缓存中取数据(只有启用的数据) + * + * @param codeList 类型编码 + * @return 字典类型 -> 字典列表数据 + * @author zhengqingya + * @date 2020/9/12 17:38 + */ + Map> listFromCacheByCode(@NotEmpty(message = "查询编码不能为空!") List codeList); + + /** + * 查询字典 + * + * @param idList 字典ids + * @return 字典id -> 字典信息 + * @author zhengqingya + * @date 2022/7/15 16:37 + */ + Map map(List idList); + + /** + * 详情 + * + * @param dictId 字典id + * @return 字典 + * @author zhengqingya + * @date 2021/8/19 11:11 + */ + SysDict detail(Integer dictId); + + /** + * 新增或更新 + * + * @param params: 提交参数 + * @return java.lang.Integer + * @author zhengqingya + * @date 2020/9/12 17:38 + */ + Integer addOrUpdateData(SysDictSaveDTO params); + + /** + * 批量更新 + * + * @param dictDataMap 提交参数 + * @param isAddForNotExist 如果code不存在是否新增数据 + * @return void + * @author zhengqingya + * @date 2021/8/27 11:24 下午 + */ + void addOrUpdateBatch(Map> dictDataMap, Boolean isAddForNotExist); + + /** + * 根据id删除数据字典 + * + * @param id: 数据字典id + * @return void + * @author zhengqingya + * @date 2020/9/12 17:37 + */ + void deleteDictById(Integer id); + + /** + * 根据类型编码删除数据字典 + * + * @param code 数据字典类型编码 + * @return void + * @author zhengqingya + * @date 2020/9/12 17:36 + */ + void deleteDictByCode(String code); + + /** + * 根据字典类型编码更新缓存 + * + * @param codeList 字典类型编码 + * @return void + * @author zhengqingya + * @date 2020/9/3 21:48 + */ + void updateCache(List codeList); + + /** + * 初始化字典类型缓存 + * + * @return void + * @author zhengqingya + * @date 2020/9/12 17:37 + */ + void initCache(); + +} diff --git a/smallboot-api/system/src/main/java/com/zhengqing/system/service/ISysDictTypeService.java b/smallboot-api/system/src/main/java/com/zhengqing/system/service/ISysDictTypeService.java new file mode 100644 index 0000000..e8a0bfa --- /dev/null +++ b/smallboot-api/system/src/main/java/com/zhengqing/system/service/ISysDictTypeService.java @@ -0,0 +1,81 @@ +package com.zhengqing.system.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.zhengqing.system.entity.SysDictType; +import com.zhengqing.system.model.dto.SysDictTypeSaveDTO; +import com.zhengqing.system.model.vo.SysDictTypeListVO; + +import java.util.List; +import java.util.Map; + +/** + *

+ * 数据字典类型-服务类 + *

+ * + * @author zhengqingya + * @description + * @date 2020/4/15 20:53 + */ +public interface ISysDictTypeService extends IService { + + /** + * 查询已启用的数据字典类型列表信息 + * + * @return 数据字典类型列表信息 + * @author zhengqingya + * @date 2020/9/12 18:51 + */ + List listByOpen(); + + /** + * 查询字典类型 + * + * @param codeList 字典编码list + * @return 编码code -> 字典类型ID + * @author zhengqingya + * @date 2021/8/28 4:45 上午 + */ + Map getDictTypeIdMap(List codeList); + + /** + * 详情 + * + * @param dictTypeId 字典类型id + * @return 字典类型数据 + * @author zhengqingya + * @date 2021/8/19 10:22 + */ + SysDictType detail(Integer dictTypeId); + + /** + * 详情 + * + * @param code 字段类型编码 + * @return 字典类型数据 + * @author zhengqingya + * @date 2021/8/27 11:41 下午 + */ + SysDictType detailByCode(String code); + + /** + * 新增或更新 + * + * @param params: + * @return java.lang.Integer + * @author zhengqingya + * @date 2020/9/12 17:28 + */ + Integer addOrUpdateData(SysDictTypeSaveDTO params); + + /** + * 根据id删除数据字典类型及其数据字典 + * + * @param id 数据字典id + * @return void + * @author zhengqingya + * @date 2020/9/12 17:26 + */ + void deleteType(Integer id); + +} diff --git a/smallboot-api/system/src/main/java/com/zhengqing/system/service/ISysMenuService.java b/smallboot-api/system/src/main/java/com/zhengqing/system/service/ISysMenuService.java new file mode 100644 index 0000000..f4e41be --- /dev/null +++ b/smallboot-api/system/src/main/java/com/zhengqing/system/service/ISysMenuService.java @@ -0,0 +1,62 @@ +package com.zhengqing.system.service; + +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.extension.service.IService; +import com.zhengqing.system.entity.SysMenu; +import com.zhengqing.system.model.dto.SysMenuListDTO; +import com.zhengqing.system.model.dto.SysMenuSaveDTO; +import com.zhengqing.system.model.vo.SysMenuTreeVO; + +import java.util.List; + +/** + *

+ * 系统管理-菜单表 服务类 + *

+ * + * @author zhengqingya + * @description + * @date 2020/4/15 18:51 + */ +public interface ISysMenuService extends IService { + + /** + * 列表分页 + * + * @param params: 查询参数 + * @return 菜单信息 + * @author zhengqingya + * @date 2020/9/10 19:04 + */ + IPage listPage(SysMenuListDTO params); + + /** + * 列表 + * + * @param params 查询参数 + * @return 菜单信息 + * @author zhengqingya + * @date 2020/9/10 19:18 + */ + List list(SysMenuListDTO params); + + /** + * 新增或更新 + * + * @param params 提交参数 + * @return 菜单id + * @author zhengqingya + * @date 2021/1/13 20:46 + */ + Integer addOrUpdateData(SysMenuSaveDTO params); + + /** + * 获取菜单树 + * + * @return 菜单树信息 + * @author zhengqingya + * @date 2021/1/13 20:44 + */ + List tree(); + +} diff --git a/smallboot-api/system/src/main/java/com/zhengqing/system/service/ISysOauthClientService.java b/smallboot-api/system/src/main/java/com/zhengqing/system/service/ISysOauthClientService.java new file mode 100644 index 0000000..55a6802 --- /dev/null +++ b/smallboot-api/system/src/main/java/com/zhengqing/system/service/ISysOauthClientService.java @@ -0,0 +1,27 @@ +package com.zhengqing.system.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.zhengqing.system.entity.SysOauthClient; +import com.zhengqing.system.model.vo.SysOauthClientVO; + +/** + *

oauth客户端 服务类

+ * + * @author zhengqingya + * @description + * @date 2022/06/10 16:25 + */ +public interface ISysOauthClientService extends IService { + + /** + * 详情 + * + * @param clientId 客户端ID + * @return 详情 + * @author zhengqingya + * @date 2022/06/10 16:25 + */ + SysOauthClientVO detail(String clientId); + + +} diff --git a/smallboot-api/system/src/main/java/com/zhengqing/system/service/ISysOauthService.java b/smallboot-api/system/src/main/java/com/zhengqing/system/service/ISysOauthService.java new file mode 100644 index 0000000..14492f7 --- /dev/null +++ b/smallboot-api/system/src/main/java/com/zhengqing/system/service/ISysOauthService.java @@ -0,0 +1,101 @@ +package com.zhengqing.system.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.zhengqing.system.entity.SysOauth; +import com.zhengqing.system.model.dto.SysOauthListDTO; +import com.zhengqing.system.model.dto.SysOauthRemoveBindDTO; +import com.zhengqing.system.model.dto.SysOauthSaveDTO; +import com.zhengqing.system.model.vo.SysOauthDataListVO; +import com.zhengqing.system.model.vo.SysOauthListVO; +import me.zhyd.oauth.model.AuthCallback; + +import javax.servlet.http.HttpServletResponse; +import java.util.List; + +/** + *

+ * 系统管理 - 用户三方授权表 服务类 + *

+ * + * @author zhengqingya + * @description + * @date 2020/11/28 22:14 + */ +public interface ISysOauthService extends IService { + + /** + * 登录授权 + * + * @param oauthType: 授权数据类型 + * @param response 响应数据 + * @return 重定向url + * @author zhengqingya + * @date 2020/11/27 17:37 + */ + String handleOauth(String oauthType, HttpServletResponse response); + + /** + * 处理登录授权后回调数据 + * + * @param oauthType: 授权数据类型 + * @param callback 回调信息 + * @param response 响应 + * @return void + * @author zhengqingya + * @date 2020/11/27 17:39 + */ + void handleCallback(String oauthType, AuthCallback callback, HttpServletResponse response); + + /** + * 处理绑定后回调数据 + * + * @param oauthType: 授权数据类型 + * @param callback 回调信息 + * @param response 响应 + * @return void + * @author zhengqingya + * @date 2020/12/6 18:44 + */ + void handleCallbackBind(String oauthType, AuthCallback callback, HttpServletResponse response); + + /** + * 新增或更新 + * + * @param params: 授权用户信息 + * @return 授权id + * @author zhengqingya + * @date 2020/11/28 22:14 + */ + Integer addOrUpdateData(SysOauthSaveDTO params); + + /** + * 列表 + * + * @param params: 查询参数 + * @return 列表数据 + * @author zhengqingya + * @date 2020/12/6 13:59 + */ + List list(SysOauthListDTO params); + + /** + * 获取指定用户的三方账号绑定数据信息 + * + * @param userId: 用户id + * @return 三方账号绑定数据信息 + * @author zhengqingya + * @date 2020/12/6 13:54 + */ + List getOauthDataList(Integer userId); + + /** + * 解除绑定 + * + * @param params: 提交参数 + * @return void + * @author zhengqingya + * @date 2020/12/6 14:55 + */ + void removeBind(SysOauthRemoveBindDTO params); + +} diff --git a/smallboot-api/system/src/main/java/com/zhengqing/system/service/ISysPermissionBusinessService.java b/smallboot-api/system/src/main/java/com/zhengqing/system/service/ISysPermissionBusinessService.java new file mode 100644 index 0000000..cb61bd9 --- /dev/null +++ b/smallboot-api/system/src/main/java/com/zhengqing/system/service/ISysPermissionBusinessService.java @@ -0,0 +1,19 @@ +package com.zhengqing.system.service; + +/** + *

+ * 系统管理 - 权限系列缓存 服务类 + *

+ * + * @author zhengqingya + * @description + * @date 2020/4/15 18:51 + */ +public interface ISysPermissionBusinessService { + + /** + * 刷新Redis缓存中的角色菜单权限 + */ + void refreshRedisPerm(); + +} diff --git a/smallboot-api/system/src/main/java/com/zhengqing/system/service/ISysPermissionService.java b/smallboot-api/system/src/main/java/com/zhengqing/system/service/ISysPermissionService.java new file mode 100644 index 0000000..91bfd39 --- /dev/null +++ b/smallboot-api/system/src/main/java/com/zhengqing/system/service/ISysPermissionService.java @@ -0,0 +1,61 @@ +package com.zhengqing.system.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.zhengqing.system.entity.SysPermission; +import com.zhengqing.system.model.dto.SysMenuReBtnPermSaveDTO; +import com.zhengqing.system.model.vo.SysMenuReBtnPermListVO; +import com.zhengqing.system.model.vo.SysRoleRePermListVO; + +import java.util.List; + +/** + *

+ * 系统管理-权限 服务类 + *

+ * + * @author zhengqingya + * @description + * @date 2020/4/15 20:31 + */ +public interface ISysPermissionService extends IService { + + /** + * 通过菜单ID获取已经配置的按钮ids + * + * @param menuId 菜单id + * @return 按钮ids + * @author zhengqingya + * @date 2020/9/10 21:19 + */ + List getBtnIdsByMenuId(Integer menuId); + + /** + * 保存菜单按钮ids + * + * @param params 提交参数 + * @return void + * @author zhengqingya + * @date 2020/9/10 21:10 + */ + void addOrUpdateData(SysMenuReBtnPermSaveDTO params); + + /** + * 通过菜单id查询菜单按钮权限信息 + * + * @param menuId 菜单id + * @return 菜单按钮权限信息 + * @author zhengqingya + * @date 2020/9/10 22:06 + */ + List getPermListByMenuId(Integer menuId); + + /** + * 获取角色权限映射数据 + * + * @return 权限 + * @author zhengqingya + * @date 2022/6/14 14:55 + */ + List listRoleRePerm(); + +} diff --git a/smallboot-api/system/src/main/java/com/zhengqing/system/service/ISysPropertyService.java b/smallboot-api/system/src/main/java/com/zhengqing/system/service/ISysPropertyService.java new file mode 100644 index 0000000..8e370fd --- /dev/null +++ b/smallboot-api/system/src/main/java/com/zhengqing/system/service/ISysPropertyService.java @@ -0,0 +1,103 @@ +package com.zhengqing.system.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.zhengqing.common.core.custom.validator.common.ValidList; +import com.zhengqing.system.entity.SysProperty; +import com.zhengqing.system.model.dto.SysPropertySaveDTO; +import com.zhengqing.system.model.vo.SysPropertyVO; + +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotEmpty; +import java.util.List; +import java.util.Map; + +/** + *

系统管理-系统属性 服务类

+ * + * @author zhengqingya + * @description + * @date 2021/09/06 22:57 + */ +public interface ISysPropertyService extends IService { + + /** + * 通过属性key查询数据 + * + * @param keyList 属性key + * @return 属性key -> 系统属性 + * @author zhengqingya + * @date 2021/09/06 22:57 + */ + Map mapByKey(@NotEmpty(message = "属性key不能为空!") List keyList); + + /** + * 列表 + * + * @param keyList 属性key + * @return 数据查询结果 + * @author zhengqingya + * @date 2021/9/7 11:04 + */ + List listByKey(@NotEmpty(message = "属性key不能为空!") List keyList); + + /** + * 通过属性key查询数据 - 数据库方式 + * + * @param keyList 属性key + * @return 系统属性 + * @author zhengqingya + * @date 2021/09/06 22:57 + */ + List listFromDbByKey(@NotEmpty(message = "属性key不能为空!") List keyList); + + /** + * 通过属性key查询数据 - 缓存方式 + * + * @param keyList 属性key + * @return 系统属性 + * @author zhengqingya + * @date 2021/09/06 22:57 + */ + List listFromCacheByKey(@NotEmpty(message = "属性key不能为空!") List keyList); + + /** + * 详情 + * + * @param id 主键ID + * @return 详情 + * @author zhengqingya + * @date 2021/09/06 22:57 + */ + SysProperty detail(Integer id); + + /** + * 详情 + * + * @param key 属性key + * @return 详情 + * @author zhengqingya + * @date 2021/09/06 22:57 + */ + SysProperty detailByKey(@NotBlank(message = "属性不能为空!") String key); + + /** + * 批量保存 + * + * @param dataList 保存参数 + * @return void + * @author zhengqingya + * @date 2021/09/06 22:57 + */ + void saveBatch(@NotEmpty(message = "属性不能为空!") ValidList dataList); + + /** + * 根据属性key删除数据 + * + * @param key 属性key + * @return void + * @author zhengqingya + * @date 2021/09/06 22:57 + */ + void deleteByKey(@NotBlank(message = "属性key不能为空!") String key); + +} diff --git a/smallboot-api/system/src/main/java/com/zhengqing/system/service/ISysRoleMenuService.java b/smallboot-api/system/src/main/java/com/zhengqing/system/service/ISysRoleMenuService.java new file mode 100644 index 0000000..9e831a2 --- /dev/null +++ b/smallboot-api/system/src/main/java/com/zhengqing/system/service/ISysRoleMenuService.java @@ -0,0 +1,71 @@ +package com.zhengqing.system.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.zhengqing.system.entity.SysRoleMenu; +import com.zhengqing.system.model.dto.SysRoleMenuSaveDTO; +import com.zhengqing.system.model.dto.SysRolePermissionSaveDTO; + +import java.util.List; + +/** + *

+ * 系统管理 - 角色菜单关联服务类 + *

+ * + * @author zhengqingya + * @description + * @date 2020/4/15 20:31 + */ +public interface ISysRoleMenuService extends IService { + + /** + * 获取角色id可访问的菜单ids + * + * @param roleId: 角色id + * @return 可访问的菜单ids + * @author zhengqingya + * @date 2020/9/10 18:09 + */ + List getMenuIdsByRoleId(Integer roleId); + + /** + * 获取角色ids可访问的菜单ids + * + * @param roleIdList: 角色ids + * @return 可访问的菜单ids + * @author zhengqingya + * @date 2020/9/10 18:09 + */ + List getMenuIdsByRoleIds(List roleIdList); + + /** + * 保存角色关联菜单ids + * + * @param params: 提交参数 + * @return void + * @author zhengqingya + * @date 2020/9/14 11:15 + */ + void saveRoleMenuIds(SysRoleMenuSaveDTO params); + + /** + * 保存角色权限(菜单权限+按钮权限) + * + * @param params: 提交参数 + * @return void + * @author zhengqingya + * @date 2020/9/10 15:01 + */ + void saveRolePermission(SysRolePermissionSaveDTO params); + + /** + * 根据角色id删除角色对应的所有关联菜单 + * + * @param roleId: 角色id + * @return void + * @author zhengqingya + * @date 2020/9/10 18:08 + */ + void deleteAllMenusByRoleId(Integer roleId); + +} diff --git a/smallboot-api/system/src/main/java/com/zhengqing/system/service/ISysRolePermissionService.java b/smallboot-api/system/src/main/java/com/zhengqing/system/service/ISysRolePermissionService.java new file mode 100644 index 0000000..76b0481 --- /dev/null +++ b/smallboot-api/system/src/main/java/com/zhengqing/system/service/ISysRolePermissionService.java @@ -0,0 +1,73 @@ +package com.zhengqing.system.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.zhengqing.system.entity.SysRolePermission; +import com.zhengqing.system.model.dto.SysRoleMenuBtnSaveDTO; +import com.zhengqing.system.model.vo.SysRoleMenuBtnListVO; + +import java.util.List; + +/** + *

+ * 系统管理 - 角色关联权限服务类 + *

+ * + * @author zhengqingya + * @description + * @date 2020/4/15 20:31 + */ +public interface ISysRolePermissionService extends IService { + + /** + * 获取所有角色菜单按钮权限信息 + * + * @return 所有角色关联的菜单按钮信息 + * @author zhengqingya + * @date 2020/9/10 17:54 + */ + List listRoleMenuBtn(); + + + /** + * 通过角色ID和菜单ID查询该菜单所拥有的所有按钮权限 + * + * @param roleId 角色ID + * @param menuId 菜单ID + * @return 菜单所拥有的所有按钮权限ids + * @author zhengqingya + * @date 2020/9/10 17:58 + */ + List getPermissionBtnsByRoleIdAndMenuId(Integer roleId, Integer menuId); + + /** + * 根据角色id删除关联所有按钮权限 + * + * @param roleId 角色id + * @return void + * @author zhengqingya + * @date 2020/9/10 17:50 + */ + void deleteBtnsByRoleId(Integer roleId); + + /** + * 根据角色ID和菜单ID删除按钮 + * + * @param roleId 角色ID + * @param menuId 菜单ID + * @return void + * @author zhengqingya + * @date 2020/9/10 17:57 + */ + void deleteBtnsByRoleIdAndMenuId(Integer roleId, Integer menuId); + + /** + * 保存角色关联菜单按钮ids + * + * @param params 提交参数 + * @return void + * @author zhengqingya + * @date 2020/9/10 18:34 + */ + void saveRoleMenuBtnIds(SysRoleMenuBtnSaveDTO params); + +} diff --git a/smallboot-api/system/src/main/java/com/zhengqing/system/service/ISysRoleService.java b/smallboot-api/system/src/main/java/com/zhengqing/system/service/ISysRoleService.java new file mode 100644 index 0000000..973ca85 --- /dev/null +++ b/smallboot-api/system/src/main/java/com/zhengqing/system/service/ISysRoleService.java @@ -0,0 +1,85 @@ +package com.zhengqing.system.service; + +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.extension.service.IService; +import com.zhengqing.system.entity.SysRole; +import com.zhengqing.system.model.dto.SysRoleListDTO; +import com.zhengqing.system.model.dto.SysRoleSaveDTO; +import com.zhengqing.system.model.vo.SysRoleAllPermissionDetailVO; +import com.zhengqing.system.model.vo.SysRoleListVO; +import com.zhengqing.system.model.vo.SysRolePermissionDetailVO; + +import java.util.List; + +/** + *

+ * 系统管理 - 角色管理 服务类 + *

+ * + * @author zhengqingya + * @description + * @date 2020/4/15 15:01 + */ +public interface ISysRoleService extends IService { + + /** + * 列表分页 + * + * @param params: + * @return 角色信息 + * @author zhengqingya + * @date 2020/9/10 14:44 + */ + IPage listPage(SysRoleListDTO params); + + /** + * 列表 + * + * @param params: 查询参数 + * @return 角色信息 + * @author zhengqingya + * @date 2020/9/10 14:45 + */ + List list(SysRoleListDTO params); + + /** + * 新增或更新 + * + * @param params: 提交参数 + * @return 角色id + * @author zhengqingya + * @date 2020/9/10 14:45 + */ + Integer addOrUpdateData(SysRoleSaveDTO params); + + /** + * 根据角色ID获取角色信息详情(包含角色可访问的菜单ids) + * + * @param roleId: 角色id + * @return 角色信息+菜单ids + * @author zhengqingya + * @date 2020/9/10 14:50 + */ + SysRolePermissionDetailVO detail(Integer roleId); + + /** + * 根据角色ID获取角色信息详情(含角色基本信息+菜单信息+按钮信息) + * + * @param roleId 角色id + * @return 角色权限具体详情信息 + * @author zhengqingya + * @date 2020/9/11 16:16 + */ + SysRoleAllPermissionDetailVO permissionDetail(Integer roleId); + + /** + * 根据角色id删除角色与关联菜单权限 + * + * @param roleId: 角色id + * @return void + * @author zhengqingya + * @date 2020/9/10 18:03 + */ + void deleteRoleAndRoleMenu(Integer roleId); + +} diff --git a/smallboot-api/system/src/main/java/com/zhengqing/system/service/ISysUserRoleService.java b/smallboot-api/system/src/main/java/com/zhengqing/system/service/ISysUserRoleService.java new file mode 100644 index 0000000..da4a62a --- /dev/null +++ b/smallboot-api/system/src/main/java/com/zhengqing/system/service/ISysUserRoleService.java @@ -0,0 +1,63 @@ +package com.zhengqing.system.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.zhengqing.system.entity.SysUserRole; +import com.zhengqing.system.model.dto.SysUserRoleSaveDTO; + +import java.util.List; +import java.util.Map; + +/** + *

+ * 系统管理 - 用户角色管理 服务类 + *

+ * + * @author zhengqingya + * @description + * @date 2020/9/10 11:52 + */ +public interface ISysUserRoleService extends IService { + + /** + * 新增或更新 + * + * @param params 提交参数 + * @return void + * @author zhengqingya + * @date 2020/9/10 14:29 + */ + void addOrUpdateData(SysUserRoleSaveDTO params); + + + /** + * 根据用户id查询关联角色ids + * + * @param userId 用户id + * @return 角色ids + * @author zhengqingya + * @date 2022/6/14 12:39 + */ + List listRoleId(Integer userId); + + /** + * 根据用户ids查询关联角色ids + * + * @param userIdList 用户ids + * @return 用户id -> 角色ids + * @author zhengqingya + * @date 2022/6/14 12:39 + */ + Map> mapRoleId(List userIdList); + + + /** + * 删除用户id关联角色ids + * + * @param userId 用户id + * @return void + * @author zhengqingya + * @date 2022/6/14 12:39 + */ + void deleteUserReRoleIds(Integer userId); + +} diff --git a/smallboot-api/system/src/main/java/com/zhengqing/system/service/ISysUserService.java b/smallboot-api/system/src/main/java/com/zhengqing/system/service/ISysUserService.java new file mode 100644 index 0000000..467163a --- /dev/null +++ b/smallboot-api/system/src/main/java/com/zhengqing/system/service/ISysUserService.java @@ -0,0 +1,118 @@ +package com.zhengqing.system.service; + +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.extension.service.IService; +import com.zhengqing.system.entity.SysUser; +import com.zhengqing.system.model.dto.SysUserListDTO; +import com.zhengqing.system.model.dto.SysUserPermDTO; +import com.zhengqing.system.model.dto.SysUserSaveDTO; +import com.zhengqing.system.model.dto.SysUserUpdatePasswordDTO; +import com.zhengqing.system.model.vo.SysUserDetailVO; +import com.zhengqing.system.model.vo.SysUserListVO; +import com.zhengqing.system.model.vo.SysUserPermVO; + +import java.util.List; + +/** + *

+ * 用户管理 服务类 + *

+ * + * @author zhengqingya + * @description + * @date 2020/4/15 11:33 + */ +public interface ISysUserService extends IService { + + /** + * 列表分页 + * + * @param params 查询参数 + * @return 用戶信息 + * @author zhengqingya + * @date 2020/9/10 10:11 + */ + IPage listPage(SysUserListDTO params); + + /** + * 列表 + * + * @param params 查询参数 + * @return 用戶信息 + * @author zhengqingya + * @date 2020/9/10 10:11 + */ + List list(SysUserListDTO params); + + /** + * 根据用户id查询用户信息 + * + * @param userId 用户id + * @return 用户信息 + * @author zhengqingya + * @date 2020/9/10 10:53 + */ + SysUserDetailVO detail(Integer userId); + + /** + * 新增或更新 + * + * @param params 提交参数 + * @return 用户id + * @author zhengqingya + * @date 2020/9/10 10:12 + */ + Integer addOrUpdateData(SysUserSaveDTO params); + + /** + * 删除用户 + * + * @param userId 用户id + * @return void + * @author zhengqingya + * @date 2020/9/10 13:49 + */ + void deleteUser(Integer userId); + + /** + * 修改用户密码 + * + * @param params 提交参数 + * @return void + * @author zhengqingya + * @date 2020/9/10 11:03 + */ + void updatePassword(SysUserUpdatePasswordDTO params); + + /** + * 重置用户密码 + * + * @param userId 用户ID + * @param password 密码 + * @return void + * @author zhengqingya + * @date 2020/9/10 11:03 + */ + void resetPassword(Integer userId, String password); + + /** + * 获取用户的基本信息+角色+权限... + * + * @param params 查询参数 + * @return 用户权限信息 + * @author zhengqingya + * @date 2020/9/21 16:18 + */ + SysUserPermVO getUserPerm(SysUserPermDTO params); + + /** + * 获取用户信息 + * + * @param username 用户名 + * @return 用户信息 + * @author zhengqingya + * @date 2020/9/21 16:18 + */ + SysUser getUserByUsername(String username); + +} diff --git a/smallboot-api/system/src/main/java/com/zhengqing/system/service/impl/AuthServiceImpl.java b/smallboot-api/system/src/main/java/com/zhengqing/system/service/impl/AuthServiceImpl.java new file mode 100644 index 0000000..2fae0c9 --- /dev/null +++ b/smallboot-api/system/src/main/java/com/zhengqing/system/service/impl/AuthServiceImpl.java @@ -0,0 +1,58 @@ +package com.zhengqing.common.auth.service.impl; + +import cn.dev33.satoken.stp.StpUtil; +import cn.hutool.core.lang.Assert; +import cn.hutool.json.JSONUtil; +import com.zhengqing.common.auth.model.bo.JwtUserBO; +import com.zhengqing.common.auth.model.dto.AuthLoginDTO; +import com.zhengqing.common.auth.model.vo.AuthLoginVO; +import com.zhengqing.common.auth.service.IAuthService; +import com.zhengqing.system.entity.SysUser; +import com.zhengqing.system.service.ISysUserService; +import com.zhengqing.system.util.PasswordUtil; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +/** + *

+ * 授权 服务实现类 + *

+ * + * @author zhengqingya + * @description + * @date 2020/4/15 11:33 + */ +@Slf4j +@Service +@RequiredArgsConstructor +public class AuthServiceImpl implements IAuthService { + + private final ISysUserService sysUserService; + + @Override + public AuthLoginVO login(AuthLoginDTO params) { + String username = params.getUsername(); + String password = params.getPassword(); + + SysUser sysUser = this.sysUserService.getUserByUsername(username); + Assert.notNull(sysUser, "账号不存在!"); + boolean isValid = PasswordUtil.isValidPassword(password, sysUser.getPassword()); + + // 校验原始密码是否正确 + Assert.isTrue(isValid, "密码错误!"); + + // 登录 + StpUtil.login(JSONUtil.toJsonStr( + JwtUserBO.builder() + .userId(sysUser.getUserId()) + .userName(sysUser.getUsername()) + .build() + )); + return AuthLoginVO.builder() + .tokenName(StpUtil.getTokenName()) + .tokenValue(StpUtil.getTokenValue()) + .build(); + } + +} diff --git a/smallboot-api/system/src/main/java/com/zhengqing/system/service/impl/SysDictServiceImpl.java b/smallboot-api/system/src/main/java/com/zhengqing/system/service/impl/SysDictServiceImpl.java new file mode 100644 index 0000000..264d895 --- /dev/null +++ b/smallboot-api/system/src/main/java/com/zhengqing/system/service/impl/SysDictServiceImpl.java @@ -0,0 +1,307 @@ +package com.zhengqing.system.service.impl; + +import cn.hutool.core.lang.Assert; +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.JSONArray; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; +import com.zhengqing.common.base.context.SysUserContext; +import com.zhengqing.common.base.enums.YesNoEnum; +import com.zhengqing.common.core.custom.validator.common.ValidList; +import com.zhengqing.common.db.constant.MybatisConstant; +import com.zhengqing.common.redis.util.RedisUtil; +import com.zhengqing.common.web.util.MyValidatorUtil; +import com.zhengqing.system.constant.SystemConstant; +import com.zhengqing.system.entity.SysDict; +import com.zhengqing.system.entity.SysDictType; +import com.zhengqing.system.mapper.SysDictMapper; +import com.zhengqing.system.model.dto.SysDictSaveBatchDTO; +import com.zhengqing.system.model.dto.SysDictSaveDTO; +import com.zhengqing.system.model.dto.SysDictTypeSaveDTO; +import com.zhengqing.system.model.vo.SysDictTypeListVO; +import com.zhengqing.system.model.vo.SysDictVO; +import com.zhengqing.system.service.ISysDictService; +import com.zhengqing.system.service.ISysDictTypeService; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; +import org.springframework.context.annotation.Lazy; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.util.CollectionUtils; +import org.springframework.validation.annotation.Validated; + +import javax.annotation.Resource; +import java.util.Collections; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +/** + *

+ * 数据字典-服务实现类 + *

+ * + * @author zhengqingya + * @description + * @date 2020/4/15 20:51 + */ +@Slf4j +@Service +@Validated +public class SysDictServiceImpl extends ServiceImpl implements ISysDictService { + + @Resource + private SysDictMapper sysDictMapper; + + @Lazy + @Resource + private ISysDictTypeService sysDictTypeService; + + @Override + public List listByCode(String code) { + return this.sysDictMapper.selectDictListByCode(null, Lists.newArrayList(code)); + } + + @Override + public Map> listByOpenCode(List codeList) { + Map> dictDataMap = this.listFromCacheByCode(codeList); + if (CollectionUtils.isEmpty(dictDataMap)) { + log.warn("[系统管理] 数据字典缓存丢失,请检查:{}", codeList); + // 如果缓存数据为空,则从db获取 + return this.listFromDbByOpenCode(codeList); + } + return dictDataMap; + } + + @Override + public Map> listFromDbByOpenCode(List codeList) { + this.checkKey(codeList); + Map> dictDataMap = Maps.newHashMap(); + List dictDataList = this.sysDictMapper.selectDictListByCode(YesNoEnum.YES.getValue(), codeList); + for (SysDictVO dictItem : dictDataList) { + dictDataMap.computeIfAbsent(dictItem.getCode(), k -> new LinkedList<>()).add(dictItem); + } + // 计算有没有差集,若有则装入空数据,返回 +// List codeListByDb = dictDataList.stream().map(SysDictVO::getCode).collect(Collectors.toList()); +// List newCodeList = codeList.stream().filter(code -> !codeListByDb.contains(code)).collect(Collectors.toList()); +// newCodeList.forEach(code -> dictDataMap.put(code, Lists.newArrayList())); + return dictDataMap; + } + + @Override + public Map> listFromCacheByCode(List codeList) { + this.checkKey(codeList); + Map> dictDataMap = Maps.newHashMap(); + codeList.forEach(codeItem -> { + String dictJsonStr = RedisUtil.get(SystemConstant.CACHE_SYS_DICT_PREFIX + codeItem); + if (StringUtils.isBlank(dictJsonStr)) { +// dictDataMap.put(codeItem, Lists.newArrayList()); + } else { + dictDataMap.put(codeItem, JSONArray.parseArray(dictJsonStr, SysDictVO.class)); + } + }); + return dictDataMap; + } + + @Override + public Map map(List idList) { + Map resultMap = Maps.newHashMap(); + if (CollectionUtils.isEmpty(idList)) { + return resultMap; + } + List sysDictList = this.sysDictMapper.selectBatchIds(idList); + return sysDictList.stream().collect(Collectors.toMap(SysDict::getId, e -> e)); + } + + /** + * 字典code值校验 + * + * @param codeList 属性key + * @return void + * @author zhengqingya + * @date 2021/9/7 10:00 + */ + private void checkKey(List codeList) { + Assert.notNull(codeList, "字典code不能为空!"); + codeList.forEach(codeItem -> Assert.notBlank(codeItem, "字典code不能为空!")); + } + + @Override + public SysDict detail(Integer dictId) { + SysDict sysDict = this.sysDictMapper.selectById(dictId); + Assert.notNull(sysDict, "字典不存在!"); + return sysDict; + } + + @Override + @Transactional(rollbackFor = Exception.class) + public Integer addOrUpdateData(SysDictSaveDTO params) { + String code = params.getCode(); + SysDictType dictTypeData = this.sysDictTypeService.detailByCode(code); + Integer id = params.getId(); + String name = params.getName(); + String value = params.getValue(); + Integer sort = params.getSort(); + String remark = params.getRemark(); + + // 校验名称是否重复 + SysDict sysDictOldByName = this.sysDictMapper.selectOne(new LambdaQueryWrapper() + .eq(SysDict::getCode, code) + .eq(SysDict::getName, name) + .last(MybatisConstant.LIMIT_ONE)); + Assert.isTrue(sysDictOldByName == null || sysDictOldByName.getId().equals(id), "字典名称重复,请重新输入!"); + + // 校验Value是否重复 + SysDict sysDictOldByValue = this.sysDictMapper.selectOne(new LambdaQueryWrapper() + .eq(SysDict::getCode, code) + .eq(SysDict::getValue, value) + .last(MybatisConstant.LIMIT_ONE)); + Assert.isTrue(sysDictOldByValue == null || sysDictOldByValue.getId().equals(id), "字典名称值重复,请重新输入!"); + + // 保存数据 + SysDict sysDict = SysDict.builder() + .id(id) + .dictTypeId(dictTypeData.getId()) + .code(code) + .name(name) + .value(value) + .status(YesNoEnum.YES.getValue()) + .sort(sort) + .remark(remark) + .build(); + if (params.getId() == null) { + this.sysDictMapper.insert(sysDict); + } else { + // 校验该数据是否存在 + this.detail(id); + this.sysDictMapper.updateById(sysDict); + } + // 更新缓存 + this.updateCache(Collections.singletonList(code)); + return sysDict.getId(); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void addOrUpdateBatch(Map> dictDataMap, Boolean isAddForNotExist) { + if (CollectionUtils.isEmpty(dictDataMap)) { + return; + } + List saveList = Lists.newArrayList(); + List codeList = Lists.newLinkedList(); + dictDataMap.forEach((code, dictListItem) -> codeList.add(code)); + this.checkKey(codeList); + Map dictTypeIdMap = this.sysDictTypeService.getDictTypeIdMap(codeList); + dictDataMap.forEach((code, dictListItem) -> { + MyValidatorUtil.validate(dictListItem); + Integer dictTypeId = dictTypeIdMap.get(code); + if (dictTypeId == null) { + if (isAddForNotExist) { + // 新增数据 + String dictTypeName = dictListItem.get(0).getDictTypeName(); + Assert.notBlank(dictTypeName, "新增字典类型数据时,字典类型名称值不能为空!"); + dictTypeId = this.sysDictTypeService.addOrUpdateData(SysDictTypeSaveDTO.builder() + .code(code) + .name(dictTypeName) + .status(YesNoEnum.YES.getValue()) + .build()); + } else { + Assert.notNull(dictTypeId, String.format("数据字典[%s]丢失或未启用,请联系系统管理员!", code)); + } + } + + // 删除该code关联字典 + this.sysDictMapper.deleteByCode(code); + + // 校验字典value和名称是否重复 + List repeatValueDataList = dictListItem + .stream().map(SysDictSaveBatchDTO::getValue).collect(Collectors.toList()) + .stream().collect(Collectors.toMap(e -> e, e -> 1, Integer::sum)) + .entrySet().stream() + .filter(entry -> entry.getValue() > 1) + .map(Map.Entry::getKey) + .collect(Collectors.toList()); + Assert.isTrue(CollectionUtils.isEmpty(repeatValueDataList), "字典名称值重复,请重新输入!"); + List repeatNameDataList = dictListItem + .stream().map(SysDictSaveBatchDTO::getName).collect(Collectors.toList()) + .stream().collect(Collectors.toMap(e -> e, e -> 1, Integer::sum)) + .entrySet().stream() + .filter(entry -> entry.getValue() > 1) + .map(Map.Entry::getKey) + .collect(Collectors.toList()); + Assert.isTrue(CollectionUtils.isEmpty(repeatNameDataList), "字典名称重复,请重新输入!"); + + for (SysDictSaveBatchDTO item : dictListItem) { + item.setId(null); + item.setDictTypeId(dictTypeId); + item.setCode(code); + item.setCurrentUserId(SysUserContext.getUserId()); + if (item.getStatus() == null) { + item.setStatus(YesNoEnum.YES.getValue()); + } + } + saveList.addAll(dictListItem); + }); + // 保存数据 + if (!CollectionUtils.isEmpty(saveList)) { + this.sysDictMapper.batchInsertOrUpdate(saveList); + } + // 更新缓存 + this.updateCache(codeList); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void deleteDictById(Integer id) { + SysDict sysDict = this.sysDictMapper.selectById(id); + if (sysDict == null) { + return; + } + this.sysDictMapper.deleteById(id); + // 更新缓存 + this.updateCache(Collections.singletonList(sysDict.getCode())); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void deleteDictByCode(String code) { + Assert.notBlank(code, "字典code不能为空!"); + this.sysDictMapper.deleteByCode(code); + } + + @Override + public void updateCache(List codeList) { + if (CollectionUtils.isEmpty(codeList)) { + return; + } + Map> dictDataMap = this.listFromDbByOpenCode(codeList); + dictDataMap.forEach((code, dictList) -> { + String key = SystemConstant.CACHE_SYS_DICT_PREFIX + code; + // 加入||更新 缓存 + if (RedisUtil.hasKey(key)) { + RedisUtil.delete(key); + log.info("数据字典[{}] 更新之前删除缓存" + key); + } + if (CollectionUtils.isEmpty(dictList)) { + dictList = Lists.newArrayList(); + } + RedisUtil.set(key, JSON.toJSONString(dictList)); + log.info("数据字典[{}] 加入缓存" + key); + }); + } + + @Override + public void initCache() { + List sysDictTypeList = this.sysDictTypeService.listByOpen(); + if (!CollectionUtils.isEmpty(sysDictTypeList)) { + List codeList = sysDictTypeList.stream().map(SysDictTypeListVO::getCode).collect(Collectors.toList()); + Map> dictDataMap = this.listFromDbByOpenCode(codeList); + dictDataMap.forEach((code, dictDataList) -> RedisUtil.set(SystemConstant.CACHE_SYS_DICT_PREFIX + code, JSON.toJSONString(dictDataList))); + } + log.info("初始化数据字典缓存成功!"); + } + +} diff --git a/smallboot-api/system/src/main/java/com/zhengqing/system/service/impl/SysDictTypeServiceImpl.java b/smallboot-api/system/src/main/java/com/zhengqing/system/service/impl/SysDictTypeServiceImpl.java new file mode 100644 index 0000000..817d322 --- /dev/null +++ b/smallboot-api/system/src/main/java/com/zhengqing/system/service/impl/SysDictTypeServiceImpl.java @@ -0,0 +1,126 @@ +package com.zhengqing.system.service.impl; + +import cn.hutool.core.lang.Assert; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.google.common.collect.Maps; +import com.zhengqing.common.db.constant.MybatisConstant; +import com.zhengqing.common.redis.util.RedisUtil; +import com.zhengqing.system.constant.SystemConstant; +import com.zhengqing.system.entity.SysDictType; +import com.zhengqing.system.mapper.SysDictTypeMapper; +import com.zhengqing.system.model.bo.SysDictTypeBO; +import com.zhengqing.system.model.dto.SysDictTypeSaveDTO; +import com.zhengqing.system.model.vo.SysDictTypeListVO; +import com.zhengqing.system.service.ISysDictService; +import com.zhengqing.system.service.ISysDictTypeService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.context.annotation.Lazy; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.util.CollectionUtils; + +import javax.annotation.Resource; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +/** + *

+ * 数据字典类型-服务实现类 + *

+ * + * @author zhengqingya + * @description + * @date 2020/4/15 20:51 + */ +@Slf4j +@Service +public class SysDictTypeServiceImpl extends ServiceImpl implements ISysDictTypeService { + + @Lazy + @Resource + private ISysDictService sysDictService; + + @Resource + private SysDictTypeMapper sysDictTypeMapper; + + @Override + public List listByOpen() { + return this.sysDictTypeMapper.selectDictTypeListByOpen(); + } + + @Override + public Map getDictTypeIdMap(List codeList) { + if (CollectionUtils.isEmpty(codeList)) { + return Maps.newHashMap(); + } + List dictTypeList = this.sysDictTypeMapper.selectDataList(codeList); + return dictTypeList.stream().collect(Collectors.toMap(SysDictTypeBO::getCode, SysDictTypeBO::getId, (k1, k2) -> k1)); + } + + @Override + public SysDictType detail(Integer dictTypeId) { + SysDictType sysDictType = this.sysDictTypeMapper.selectById(dictTypeId); + Assert.notNull(sysDictType, "字典类型不存在!"); + return sysDictType; + } + + @Override + public SysDictType detailByCode(String code) { + SysDictType sysDictType = this.sysDictTypeMapper.selectOne(new LambdaQueryWrapper() + .eq(SysDictType::getCode, code) + .last(MybatisConstant.LIMIT_ONE)); + Assert.notNull(sysDictType, "字典类型不存在!"); + return sysDictType; + } + + @Override + @Transactional(rollbackFor = Exception.class) + public Integer addOrUpdateData(SysDictTypeSaveDTO params) { + Integer id = params.getId(); + String code = params.getCode(); + String name = params.getName(); + Integer status = params.getStatus(); + + // 校验编码是否重复 + SysDictType sysDictTypeOld = this.sysDictTypeMapper.selectOne(new LambdaQueryWrapper() + .eq(SysDictType::getCode, code) + .last(MybatisConstant.LIMIT_ONE)); + Assert.isTrue(sysDictTypeOld == null || sysDictTypeOld.getId().equals(id), "字典类型编码重复,请重新输入!"); + + // 保存数据 + SysDictType sysDictType = SysDictType.builder() + .id(id) + .name(name) + .status(status) + .build(); + if (params.getId() == null) { + sysDictType.setCode(code); + this.sysDictTypeMapper.insert(sysDictType); + } else { + // 校验该数据是否存在 + this.detail(id); + this.sysDictTypeMapper.updateById(sysDictType); + } + // 更新缓存 + this.sysDictService.updateCache(Collections.singletonList(sysDictType.getCode())); + return sysDictType.getId(); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void deleteType(Integer id) { + SysDictType sysDictType = this.sysDictTypeMapper.selectById(id); + String code = sysDictType.getCode(); + // 1、 先删除数据字典 + this.sysDictService.deleteDictByCode(code); + // 2、 再删除数据字典类型 + this.sysDictTypeMapper.deleteById(id); + // 3、 最后删除缓存 + RedisUtil.delete(SystemConstant.CACHE_SYS_DICT_PREFIX + code); + log.info("删除数据字典[{}] & 删除缓存成功", code); + } + +} diff --git a/smallboot-api/system/src/main/java/com/zhengqing/system/service/impl/SysMenuServiceImpl.java b/smallboot-api/system/src/main/java/com/zhengqing/system/service/impl/SysMenuServiceImpl.java new file mode 100644 index 0000000..2cad612 --- /dev/null +++ b/smallboot-api/system/src/main/java/com/zhengqing/system/service/impl/SysMenuServiceImpl.java @@ -0,0 +1,119 @@ +package com.zhengqing.system.service.impl; + +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.google.common.collect.Lists; +import com.zhengqing.common.base.constant.AppConstant; +import com.zhengqing.system.entity.SysMenu; +import com.zhengqing.system.mapper.SysMenuMapper; +import com.zhengqing.system.model.dto.SysMenuListDTO; +import com.zhengqing.system.model.dto.SysMenuSaveDTO; +import com.zhengqing.system.model.vo.SysMenuTreeVO; +import com.zhengqing.system.service.ISysMenuService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import javax.annotation.Resource; +import java.util.List; + +/** + *

+ * 系统管理-菜单表 服务实现类 + *

+ * + * @author zhengqingya + * @description + * @date 2020/4/15 18:51 + */ +@Slf4j +@Service +@Transactional(rollbackFor = Exception.class) +public class SysMenuServiceImpl extends ServiceImpl implements ISysMenuService { + + @Resource + private SysMenuMapper sysMenuMapper; + + @Override + public IPage listPage(SysMenuListDTO params) { + return this.sysMenuMapper.selectMenus(new Page(), params); + } + + @Override + public List list(SysMenuListDTO params) { + return this.sysMenuMapper.selectMenus(params); + } + + @Override + public Integer addOrUpdateData(SysMenuSaveDTO params) { + Integer menuId = params.getMenuId(); + SysMenu sysMenu = SysMenu.builder() + .menuId(menuId) + .title(params.getTitle()) + .name(params.getName()) + .icon(params.getIcon()) + .path(params.getPath()) + .parentId(params.getParentId()) + .sort(params.getSort()) + .component(params.getComponent()) + .hidden(params.getHidden()) + .redirect(params.getRedirect()) + .status(params.getStatus()) + .type(params.getType()) + .alwaysShow(params.getAlwaysShow()) + .breadcrumb(params.getBreadcrumb()) + .build(); + if (menuId == null) { + sysMenu.insert(); + } else { + sysMenu.updateById(); + } + return sysMenu.getMenuId(); + } + + @Override + public List tree() { + // 1、拿到所有菜单 + List allMenuList = this.sysMenuMapper.selectMenuTree(); + // 2、准备一个空的父菜单集合 + List parentMenuList = Lists.newArrayList(); + // 3、遍历子菜单 -> 进行对父菜单的设置 + for (SysMenuTreeVO parentMenu : allMenuList) { + if (parentMenu.getParentId().equals(AppConstant.PARENT_ID)) { + parentMenuList.add(parentMenu); + } + } + // 4、遍历出父菜单对应的子菜单 + for (SysMenuTreeVO parent : parentMenuList) { + List child = this.getChildMenu(parent.getMenuId(), allMenuList); + parent.setChildren(child); + } + return parentMenuList; + } + + /** + * 递归子菜单 + * + * @param parentMenuId 父菜单id + * @param allMenuList 所有菜单 + * @return 菜单树列表 + * @author zhengqingya + * @date 2020/9/10 20:56 + */ + private List getChildMenu(Integer parentMenuId, List allMenuList) { + // 5、存放子菜单的集合 + List childMenuList = Lists.newArrayList(); + for (SysMenuTreeVO menu : allMenuList) { + if (menu.getParentId().equals(parentMenuId)) { + childMenuList.add(menu); + } + } + // 6、递归 + for (SysMenuTreeVO treeVO : childMenuList) { + treeVO.setChildren(this.getChildMenu(treeVO.getMenuId(), allMenuList)); + } + return childMenuList; + } + +} diff --git a/smallboot-api/system/src/main/java/com/zhengqing/system/service/impl/SysOauthClientServiceImpl.java b/smallboot-api/system/src/main/java/com/zhengqing/system/service/impl/SysOauthClientServiceImpl.java new file mode 100644 index 0000000..ddcfabc --- /dev/null +++ b/smallboot-api/system/src/main/java/com/zhengqing/system/service/impl/SysOauthClientServiceImpl.java @@ -0,0 +1,35 @@ +package com.zhengqing.system.service.impl; + +import cn.hutool.core.lang.Assert; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.zhengqing.system.entity.SysOauthClient; +import com.zhengqing.system.mapper.SysOauthClientMapper; +import com.zhengqing.system.model.vo.SysOauthClientVO; +import com.zhengqing.system.service.ISysOauthClientService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +import javax.annotation.Resource; + +/** + *

oauth客户端 服务实现类

+ * + * @author zhengqingya + * @description + * @date 2022/06/10 16:25 + */ +@Slf4j +@Service +public class SysOauthClientServiceImpl extends ServiceImpl implements ISysOauthClientService { + + @Resource + private SysOauthClientMapper sysOauthClientMapper; + + @Override + public SysOauthClientVO detail(String clientId) { + SysOauthClientVO sysOauthClientVO = this.sysOauthClientMapper.selectClient(clientId); + Assert.notNull(sysOauthClientVO, "该数据不存在!"); + return sysOauthClientVO; + } + +} diff --git a/smallboot-api/system/src/main/java/com/zhengqing/system/service/impl/SysOauthServiceImpl.java b/smallboot-api/system/src/main/java/com/zhengqing/system/service/impl/SysOauthServiceImpl.java new file mode 100644 index 0000000..3285b41 --- /dev/null +++ b/smallboot-api/system/src/main/java/com/zhengqing/system/service/impl/SysOauthServiceImpl.java @@ -0,0 +1,299 @@ +package com.zhengqing.system.service.impl; + +import com.alibaba.fastjson.JSONObject; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.google.common.collect.Lists; +import com.zhengqing.common.base.enums.YesNoEnum; +import com.zhengqing.common.base.exception.MyException; +import com.zhengqing.common.base.util.MyBeanUtil; +import com.zhengqing.common.core.enums.UserSexEnum; +import com.zhengqing.system.config.SystemProperty; +import com.zhengqing.system.entity.SysOauth; +import com.zhengqing.system.entity.SysUser; +import com.zhengqing.system.enums.SysDictTypeEnum; +import com.zhengqing.system.enums.SysOauthTypeEnum; +import com.zhengqing.system.mapper.SysOauthMapper; +import com.zhengqing.system.model.bo.SysGitHubUserInfoBO; +import com.zhengqing.system.model.bo.SysGiteeUserInfoBO; +import com.zhengqing.system.model.bo.SysQQUserInfoBO; +import com.zhengqing.system.model.bo.SysThirdpartOauthUserInfoBO; +import com.zhengqing.system.model.dto.SysOauthListDTO; +import com.zhengqing.system.model.dto.SysOauthRemoveBindDTO; +import com.zhengqing.system.model.dto.SysOauthSaveDTO; +import com.zhengqing.system.model.dto.SysUserSaveDTO; +import com.zhengqing.system.model.vo.SysDictVO; +import com.zhengqing.system.model.vo.SysOauthDataListVO; +import com.zhengqing.system.model.vo.SysOauthListVO; +import com.zhengqing.system.service.ISysDictService; +import com.zhengqing.system.service.ISysOauthService; +import com.zhengqing.system.service.ISysUserService; +import lombok.SneakyThrows; +import lombok.extern.slf4j.Slf4j; +import me.zhyd.oauth.model.AuthCallback; +import me.zhyd.oauth.model.AuthResponse; +import me.zhyd.oauth.request.AuthGiteeRequest; +import me.zhyd.oauth.request.AuthGithubRequest; +import me.zhyd.oauth.request.AuthQqRequest; +import me.zhyd.oauth.request.AuthRequest; +import me.zhyd.oauth.utils.AuthStateUtils; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import javax.annotation.Resource; +import javax.servlet.http.HttpServletResponse; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +/** + *

+ * 系统管理 - 用户三方授权表 服务实现类 + *

+ * + * @author zhengqingya + * @description + * @date 2020/11/28 22:14 + */ +@Slf4j +@Service +@Transactional(rollbackFor = Exception.class) +public class SysOauthServiceImpl extends ServiceImpl implements ISysOauthService { + + @Resource + private SysOauthMapper sysOauthMapper; + + @Resource + private SystemProperty systemProperty; + + @Resource + private ISysUserService sysUserService; + + @Resource + private ISysDictService dictService; + + @Override + @SneakyThrows(Exception.class) + public String handleOauth(String oauthType, HttpServletResponse response) { + AuthRequest authRequest = this.getAuthRequest(oauthType); + String redirectUrl = authRequest.authorize(AuthStateUtils.createState()); + response.sendRedirect(redirectUrl); + log.info("《三方授权》 重定向url:{}", redirectUrl); + return redirectUrl; + } + + @Override + @SneakyThrows(Exception.class) + public void handleCallback(String oauthType, AuthCallback callback, HttpServletResponse response) { + if (oauthType.endsWith("Bind")) { + this.handleCallbackBind(oauthType, callback, response); + return; + } + Map authResponseMap = this.handleCallbackData(oauthType, callback); + SysThirdpartOauthUserInfoBO oauthUserInfoBO = new SysThirdpartOauthUserInfoBO(); + switch (SysOauthTypeEnum.getEnum(oauthType)) { + case Gitee: + SysGiteeUserInfoBO sysGiteeUserInfoBO = + MyBeanUtil.mapToObj(authResponseMap, SysGiteeUserInfoBO.class); + log.info("《三方授权》 用户信息: {}", sysGiteeUserInfoBO); + oauthUserInfoBO.setOauthType(SysOauthTypeEnum.Gitee.getOauthTypeValue()); + oauthUserInfoBO.setOpenId(sysGiteeUserInfoBO.getUuid()); + oauthUserInfoBO.setUsername(sysGiteeUserInfoBO.getUsername()); + oauthUserInfoBO.setNickname(sysGiteeUserInfoBO.getNickname()); + oauthUserInfoBO.setAvatar(sysGiteeUserInfoBO.getAvatar()); + oauthUserInfoBO.setEmail(sysGiteeUserInfoBO.getEmail()); + oauthUserInfoBO.setSex(UserSexEnum.未知.getType()); + oauthUserInfoBO.setRemark(sysGiteeUserInfoBO.getRemark()); + break; + case GitHub: + SysGitHubUserInfoBO sysGitHubUserInfoBO = + MyBeanUtil.mapToObj(authResponseMap, SysGitHubUserInfoBO.class); + log.info("《三方授权》 用户信息: {}", sysGitHubUserInfoBO); + oauthUserInfoBO.setOauthType(SysOauthTypeEnum.GitHub.getOauthTypeValue()); + oauthUserInfoBO.setOpenId(sysGitHubUserInfoBO.getUuid()); + oauthUserInfoBO.setUsername(sysGitHubUserInfoBO.getUsername()); + oauthUserInfoBO.setNickname(sysGitHubUserInfoBO.getNickname()); + oauthUserInfoBO.setAvatar(sysGitHubUserInfoBO.getAvatar()); + oauthUserInfoBO.setEmail(sysGitHubUserInfoBO.getEmail()); + oauthUserInfoBO.setSex(UserSexEnum.未知.getType()); + oauthUserInfoBO.setRemark(sysGitHubUserInfoBO.getRemark()); + break; + case QQ: + SysQQUserInfoBO sysQqUserInfoBO = MyBeanUtil.mapToObj(authResponseMap, SysQQUserInfoBO.class); + log.info("《三方授权》 用户信息: {}", sysQqUserInfoBO); + oauthUserInfoBO.setOauthType(SysOauthTypeEnum.QQ.getOauthTypeValue()); + oauthUserInfoBO.setOpenId(sysQqUserInfoBO.getUuid()); + oauthUserInfoBO.setUsername(sysQqUserInfoBO.getUsername()); + oauthUserInfoBO.setNickname(sysQqUserInfoBO.getNickname()); + oauthUserInfoBO.setAvatar(sysQqUserInfoBO.getAvatar()); + oauthUserInfoBO.setEmail(sysQqUserInfoBO.getEmail()); + oauthUserInfoBO.setSex(UserSexEnum.未知.getType()); + oauthUserInfoBO.setRemark(sysQqUserInfoBO.getRemark()); + break; + default: + break; + } + + Integer userId = this.handleOauthThirdPartData(oauthUserInfoBO); + SysUser sysUser = new SysUser().selectById(userId); +// // 塞个token信息 +// JwtUserBO jwtUserBO = JwtUserBO.buildUser(JSONObject.parseObject(JSONObject.toJSONString(sysUser), Map.class)); +// String jwtToken = JwtUtil.buildJWT(jwtUserBO); +// sysUser.setToken(jwtToken); +// sysUser.updateById(); + String jwtToken = "token..."; + + String redirectUrl = this.systemProperty.getThirdpartOauth().getWebRedirectUrl() + jwtToken; + response.sendRedirect(redirectUrl); + log.info("《三方授权》 授权回调地址: {}", redirectUrl); + } + + @Override + @SneakyThrows(Exception.class) + public void handleCallbackBind(String oauthType, AuthCallback callback, HttpServletResponse response) { + Map authResponseMap = this.handleCallbackData(oauthType, callback); + String openId = String.valueOf(authResponseMap.get("uuid")); + String redirectUrl = String.format(this.systemProperty.getThirdpartOauth().getWebBindRedirectUrl(), + SysOauthTypeEnum.getEnum(oauthType).getOauthTypeValue(), openId); + response.sendRedirect(redirectUrl); + log.info("《三方授权》 绑定回调地址: {}", redirectUrl); + } + + /** + * 处理回调数据 + * + * @param oauthType: 授权数据类型 + * @param callback 回调信息 + * @return 回调响应map数据 + * @author zhengqingya + * @date 2020/12/6 18:48 + */ + private Map handleCallbackData(String oauthType, AuthCallback callback) { + AuthRequest authRequest = this.getAuthRequest(oauthType); + AuthResponse authResponse = authRequest.login(callback); + int code = authResponse.getCode(); + if (code != 2000) { + throw new MyException("《三方授权》 回调异常: " + authResponse.getMsg()); + } + Object authResponseData = authResponse.getData(); + Map authResponseMap = + JSONObject.parseObject(JSONObject.toJSONString(authResponseData), Map.class); + log.debug("《三方授权》 授权信息:{}", authResponseMap); + return authResponseMap; + } + + private AuthRequest getAuthRequest(String oauthType) { + switch (SysOauthTypeEnum.getEnum(oauthType)) { + case Gitee: + return new AuthGiteeRequest(this.systemProperty.getThirdpartOauth().getGitee()); + case GiteeBind: + return new AuthGiteeRequest(this.systemProperty.getThirdpartOauth().getGiteeBind()); + case GitHub: + return new AuthGithubRequest(this.systemProperty.getThirdpartOauth().getGithub()); + case GitHubBind: + return new AuthGithubRequest(this.systemProperty.getThirdpartOauth().getGithubBind()); + case QQ: + return new AuthQqRequest(this.systemProperty.getThirdpartOauth().getQq()); + default: + throw new MyException("未找到存在的授权数据类型!"); + } + } + + /** + * 处理授权数据 + * + * @param params: + * @return java.lang.Integer + * @author zhengqingya + * @date 2020/12/6 19:03 + */ + private Integer handleOauthThirdPartData(SysThirdpartOauthUserInfoBO params) { + Integer oauthType = params.getOauthType(); + String openId = params.getOpenId(); + String username = params.getUsername(); + String nickname = params.getNickname(); + String avatar = params.getAvatar(); + String email = params.getEmail(); + Byte sex = params.getSex(); + String remark = params.getRemark(); + + SysOauth oauthInfo = this.sysOauthMapper.selectOne( + new LambdaQueryWrapper().eq(SysOauth::getOpenId, openId).eq(SysOauth::getOauthType, oauthType)); + if (oauthInfo != null) { + return oauthInfo.getUserId(); + } + + // 新增一个用户,然后拿到用户id,之后登录进系统进行用户其它信息补全... + SysUserSaveDTO userSaveDTO = new SysUserSaveDTO(); + // TODO 账号暂不填!!! + userSaveDTO.setUsername(""); + userSaveDTO.setNickname(nickname); + userSaveDTO.setSex(sex); + userSaveDTO.setEmail(email); + userSaveDTO.setAvatarUrl(avatar); + + Integer userId = this.sysUserService.addOrUpdateData(userSaveDTO); + + SysOauthSaveDTO oauthSaveDTO = new SysOauthSaveDTO(); + oauthSaveDTO.setUserId(userId); + oauthSaveDTO.setOpenId(openId); + oauthSaveDTO.setOauthType(oauthType); + this.addOrUpdateData(oauthSaveDTO); + return userId; + } + + @Override + public Integer addOrUpdateData(SysOauthSaveDTO params) { + Integer userId = params.getUserId(); + String openId = params.getOpenId(); + Integer oauthType = params.getOauthType(); + SysOauth oauthInfo = this.sysOauthMapper.selectOne( + new LambdaQueryWrapper().eq(SysOauth::getOpenId, openId).eq(SysOauth::getOauthType, oauthType)); + if (oauthInfo != null) { + return oauthInfo.getUserId(); + } + oauthInfo = new SysOauth(); + oauthInfo.setUserId(userId); + oauthInfo.setOpenId(openId); + oauthInfo.setOauthType(oauthType); + oauthInfo.insert(); + return oauthInfo.getUserReOauthId(); + } + + @Override + public List list(SysOauthListDTO params) { + return this.sysOauthMapper.selectDataList(params); + } + + @Override + public List getOauthDataList(Integer userId) { + List oauthDataList = Lists.newArrayList(); + List oauthTypeList = this.dictService.listFromCacheByCode(Lists.newArrayList(SysDictTypeEnum.第三方帐号授权类型.getCode())).get(SysDictTypeEnum.第三方帐号授权类型.getCode()); + List oauthBindList = this.list(SysOauthListDTO.builder().userId(userId).build()); + List oauthTypeBindList = + oauthBindList.stream().map(SysOauthListVO::getOauthType).collect(Collectors.toList()); + Map oauthBindMap = oauthBindList.stream() + .collect(Collectors.toMap(SysOauthListVO::getOauthType, SysOauthListVO::getUserReOauthId, (k1, k2) -> k1)); + oauthTypeList.forEach(oauthTypeItem -> { + String oauthTypeName = oauthTypeItem.getName(); + Integer oauthTypeValue = Integer.valueOf(oauthTypeItem.getValue()); + SysOauthDataListVO oauthData = new SysOauthDataListVO(); + oauthData.setOauthType(oauthTypeValue); + oauthData.setOauthTypeName(oauthTypeName); + oauthData.setOauthTypeBindName(oauthTypeName + "Bind"); + oauthData.setOauthTypeDesc(SysOauthTypeEnum.getEnum(oauthTypeName).getDesc()); + oauthData.setIfBind( + oauthTypeBindList.contains(oauthTypeValue) ? YesNoEnum.YES.getValue() : YesNoEnum.NO.getValue()); + oauthData.setUserReOauthId(oauthBindMap.get(oauthTypeValue)); + oauthDataList.add(oauthData); + }); + return oauthDataList; + } + + @Override + public void removeBind(SysOauthRemoveBindDTO params) { + Integer userReOauthId = params.getUserReOauthId(); + this.removeById(userReOauthId); + } + +} diff --git a/smallboot-api/system/src/main/java/com/zhengqing/system/service/impl/SysPermissionBusinessServiceImpl.java b/smallboot-api/system/src/main/java/com/zhengqing/system/service/impl/SysPermissionBusinessServiceImpl.java new file mode 100644 index 0000000..d2ef1f3 --- /dev/null +++ b/smallboot-api/system/src/main/java/com/zhengqing/system/service/impl/SysPermissionBusinessServiceImpl.java @@ -0,0 +1,52 @@ +package com.zhengqing.system.service.impl; + +import com.alibaba.fastjson.JSON; +import com.google.common.collect.Maps; +import com.zhengqing.common.base.constant.SecurityConstant; +import com.zhengqing.common.redis.util.RedisUtil; +import com.zhengqing.system.model.vo.SysRoleRePermListVO; +import com.zhengqing.system.service.ISysPermissionBusinessService; +import com.zhengqing.system.service.ISysPermissionService; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.util.CollectionUtils; + +import java.util.List; +import java.util.Map; + +/** + *

+ * 系统管理 - 权限系列缓存 服务实现类 + *

+ * + * @author zhengqingya + * @description + * @date 2020/4/15 18:51 + */ +@Slf4j +@Service +@RequiredArgsConstructor +public class SysPermissionBusinessServiceImpl implements ISysPermissionBusinessService { + + private final ISysPermissionService sysPermissionService; + + @Override + public void refreshRedisPerm() { + // 1、先删除缓存 + RedisUtil.delete(SecurityConstant.URL_PERM_RE_ROLES); + + // 2、查询角色关联权限数据 + List roleRePermList = this.sysPermissionService.listRoleRePerm(); + if (CollectionUtils.isEmpty(roleRePermList)) { + return; + } + Map roleReUrlPermMap = Maps.newHashMap(); + roleRePermList.forEach(item -> roleReUrlPermMap.put(item.getUrlPerm(), JSON.toJSONString(item.getRoleCodeList()))); + + // 3、加入缓存中 + RedisUtil.hPutAll(SecurityConstant.URL_PERM_RE_ROLES, roleReUrlPermMap); + log.info("初始化权限缓存成功!"); + } + +} diff --git a/smallboot-api/system/src/main/java/com/zhengqing/system/service/impl/SysPermissionServiceImpl.java b/smallboot-api/system/src/main/java/com/zhengqing/system/service/impl/SysPermissionServiceImpl.java new file mode 100644 index 0000000..44070a1 --- /dev/null +++ b/smallboot-api/system/src/main/java/com/zhengqing/system/service/impl/SysPermissionServiceImpl.java @@ -0,0 +1,65 @@ +package com.zhengqing.system.service.impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.zhengqing.system.entity.SysPermission; +import com.zhengqing.system.mapper.SysPermissionMapper; +import com.zhengqing.system.model.dto.SysMenuReBtnPermSaveDTO; +import com.zhengqing.system.model.vo.SysMenuReBtnPermListVO; +import com.zhengqing.system.model.vo.SysRoleRePermListVO; +import com.zhengqing.system.service.ISysDictService; +import com.zhengqing.system.service.ISysPermissionService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import javax.annotation.Resource; +import java.util.List; + +/** + *

+ * 系统管理-权限 服务实现类 + *

+ * + * @author zhengqingya + * @description + * @date 2020/4/15 20:31 + */ +@Slf4j +@Service +public class SysPermissionServiceImpl extends ServiceImpl implements ISysPermissionService { + + @Resource + private SysPermissionMapper sysPermissionMapper; + + @Resource + private ISysDictService sysDictService; + + @Override + public List getBtnIdsByMenuId(Integer menuId) { + return this.sysPermissionMapper.getBtnIdsByMenuId(menuId); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void addOrUpdateData(SysMenuReBtnPermSaveDTO params) { + SysPermission.builder() + .id(params.getId()) + .name(params.getName()) + .menuId(params.getMenuId()) + .btnPerm(params.getBtnPerm()) + .urlPerm(params.getUrlPerm()) + .build() + .insertOrUpdate(); + } + + @Override + public List getPermListByMenuId(Integer menuId) { + return this.sysPermissionMapper.selectBtnInfoListByMenuId(menuId); + } + + @Override + public List listRoleRePerm() { + return this.sysPermissionMapper.listRoleRePerm(); + } + +} diff --git a/smallboot-api/system/src/main/java/com/zhengqing/system/service/impl/SysPropertyServiceImpl.java b/smallboot-api/system/src/main/java/com/zhengqing/system/service/impl/SysPropertyServiceImpl.java new file mode 100644 index 0000000..0b2d930 --- /dev/null +++ b/smallboot-api/system/src/main/java/com/zhengqing/system/service/impl/SysPropertyServiceImpl.java @@ -0,0 +1,186 @@ +package com.zhengqing.system.service.impl; + +import cn.hutool.core.lang.Assert; +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.JSONObject; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; +import com.zhengqing.common.base.context.SysUserContext; +import com.zhengqing.common.core.custom.validator.common.ValidList; +import com.zhengqing.common.db.constant.MybatisConstant; +import com.zhengqing.common.redis.util.RedisUtil; +import com.zhengqing.system.constant.SystemConstant; +import com.zhengqing.system.entity.SysProperty; +import com.zhengqing.system.mapper.SysPropertyMapper; +import com.zhengqing.system.model.dto.SysPropertySaveDTO; +import com.zhengqing.system.model.vo.SysPropertyVO; +import com.zhengqing.system.service.ISysPropertyService; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.util.CollectionUtils; + +import javax.annotation.Resource; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +/** + *

系统管理-系统属性 服务实现类

+ * + * @author zhengqingya + * @description + * @date 2021/09/06 22:57 + */ +@Slf4j +@Service +public class SysPropertyServiceImpl extends ServiceImpl implements ISysPropertyService { + + @Resource + private SysPropertyMapper sysPropertyMapper; + + @Override + public Map mapByKey(List keyList) { + this.checkKey(keyList); + Map dataMap = Maps.newHashMap(); + List dataList = this.listByKey(keyList); + for (SysPropertyVO item : dataList) { + dataMap.put(item.getKey(), item); + } + return dataMap; + } + + @Override + public List listByKey(List keyList) { + List dataList = this.listFromCacheByKey(keyList); + if (CollectionUtils.isEmpty(dataList)) { + log.warn("[系统管理] 系统属性缓存丢失,请检查:{}", keyList); + // 如果缓存数据为空,则从db获取 + return this.listFromDbByKey(keyList); + } + return dataList; + } + + @Override + public List listFromDbByKey(List keyList) { + this.checkKey(keyList); + return this.sysPropertyMapper.selectDataListByKey(keyList); + } + + @Override + public List listFromCacheByKey(List keyList) { + this.checkKey(keyList); + List dataList = Lists.newLinkedList(); + keyList.forEach(keyItem -> { + String dataJsonStr = RedisUtil.get(SystemConstant.CACHE_SYS_PROPERTY_PREFIX + keyItem); + if (StringUtils.isNotBlank(dataJsonStr)) { + dataList.add(JSONObject.parseObject(dataJsonStr, SysPropertyVO.class)); + } + }); + return dataList; + } + + /** + * 属性key值校验 + * + * @param keyList 属性key + * @return void + * @author zhengqingya + * @date 2021/9/7 10:00 + */ + private void checkKey(List keyList) { + Assert.notNull(keyList, "属性key不能为空!"); + keyList.forEach(keyItem -> Assert.notBlank(keyItem, "属性key不能为空!")); + // 校验属性key是否重复 + List repeatKeyDataList = keyList + .stream().collect(Collectors.toMap(e -> e, e -> 1, Integer::sum)) + .entrySet().stream() + .filter(entry -> entry.getValue() > 1) + .map(Map.Entry::getKey) + .collect(Collectors.toList()); + Assert.isTrue(CollectionUtils.isEmpty(repeatKeyDataList), "属性key重复,请重新输入!"); + } + + @Override + public SysProperty detail(Integer id) { + SysProperty detailData = this.sysPropertyMapper.selectById(id); + Assert.notNull(detailData, "该数据不存在!"); + return detailData; + } + + @Override + public SysProperty detailByKey(String key) { + Assert.notBlank(key, "属性不能为空!"); + SysProperty detailData = this.sysPropertyMapper.selectOne( + new LambdaQueryWrapper() + .eq(SysProperty::getKey, key) + .last(MybatisConstant.LIMIT_ONE)); + Assert.notNull(detailData, "该数据不存在!"); + return detailData; + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void saveBatch(ValidList dataList) { + if (CollectionUtils.isEmpty(dataList)) { + return; + } + List keyList = dataList.stream().map(SysPropertySaveDTO::getKey).collect(Collectors.toList()); + this.checkKey(keyList); + dataList.forEach(item -> { + item.setId(null); + if (StringUtils.isBlank(item.getRemark())) { + item.setRemark(""); + } + item.setCurrentUserId(SysUserContext.getUserId()); + }); + // 删除旧数据 + this.sysPropertyMapper.deleteByKeyList(keyList); + // 保存新数据 + this.sysPropertyMapper.batchInsertOrUpdate(dataList); + // 更新redis缓存 + this.updateCache(keyList); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void deleteByKey(String key) { + Assert.notBlank(key, "属性不能为空!"); + this.sysPropertyMapper.deleteByKey(key); + String redisKey = SystemConstant.CACHE_SYS_PROPERTY_PREFIX + key; + log.info("[系统管理] 删除系统属性缓存:[{}]", redisKey); + RedisUtil.delete(redisKey); + } + + /** + * 更新redis缓存 + * + * @param keyList 属性key + * @return void + * @author zhengqingya + * @date 2021/9/7 11:21 + */ + private void updateCache(List keyList) { + if (CollectionUtils.isEmpty(keyList)) { + return; + } + List dataList = this.listFromDbByKey(keyList); + if (CollectionUtils.isEmpty(dataList)) { + return; + } + dataList.forEach(item -> { + String key = SystemConstant.CACHE_SYS_PROPERTY_PREFIX + item.getKey(); + // 加入||更新 缓存 + if (RedisUtil.hasKey(key)) { + RedisUtil.delete(key); + log.info("[系统管理] 系统属性[{}] 更新之前删除缓存" + key); + } + RedisUtil.set(key, JSON.toJSONString(item)); + log.info("[系统管理] 系统属性[{}] 加入缓存" + key); + }); + } + +} diff --git a/smallboot-api/system/src/main/java/com/zhengqing/system/service/impl/SysRoleMenuServiceImpl.java b/smallboot-api/system/src/main/java/com/zhengqing/system/service/impl/SysRoleMenuServiceImpl.java new file mode 100644 index 0000000..be69168 --- /dev/null +++ b/smallboot-api/system/src/main/java/com/zhengqing/system/service/impl/SysRoleMenuServiceImpl.java @@ -0,0 +1,130 @@ +package com.zhengqing.system.service.impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.google.common.collect.Lists; +import com.zhengqing.system.entity.SysRoleMenu; +import com.zhengqing.system.mapper.SysRoleMenuMapper; +import com.zhengqing.system.model.dto.SysRoleMenuBtnSaveDTO; +import com.zhengqing.system.model.dto.SysRoleMenuSaveDTO; +import com.zhengqing.system.model.dto.SysRolePermissionSaveDTO; +import com.zhengqing.system.model.vo.SysMenuTreeVO; +import com.zhengqing.system.service.ISysRoleMenuService; +import com.zhengqing.system.service.ISysRolePermissionService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.util.CollectionUtils; + +import javax.annotation.Resource; +import java.util.List; + +/** + *

+ * 系统管理 - 角色菜单关联服务实现类 + *

+ * + * @author zhengqingya + * @description + * @date 2020/4/15 20:31 + */ +@Slf4j +@Service +@Transactional(rollbackFor = Exception.class) +public class SysRoleMenuServiceImpl extends ServiceImpl implements ISysRoleMenuService { + + @Resource + private SysRoleMenuMapper sysRoleMenuMapper; + + @Resource + private ISysRolePermissionService sysRolePermissionService; + + @Override + public List getMenuIdsByRoleId(Integer roleId) { + return this.sysRoleMenuMapper.selectMenuIdsByRoleId(roleId); + } + + @Override + public List getMenuIdsByRoleIds(List roleIdList) { + return this.sysRoleMenuMapper.selectMenuIdsByRoleIds(roleIdList); + } + + @Override + public void saveRoleMenuIds(SysRoleMenuSaveDTO params) { + Integer roleId = params.getRoleId(); + this.sysRoleMenuMapper.deleteAllMenusByRoleId(roleId); + List menuIdList = params.getMenuIdList(); + if (!CollectionUtils.isEmpty(menuIdList)) { + List roleMenuList = Lists.newArrayList(); + menuIdList.forEach(menuId -> { + SysRoleMenu roleMenuItem = new SysRoleMenu(); + roleMenuItem.setRoleId(roleId); + roleMenuItem.setMenuId(menuId); + roleMenuList.add(roleMenuItem); + }); + // sysRoleMenuMapper.batchInsertRoleMenuIds(roleId, menuIdList); + this.saveBatch(roleMenuList); + } + } + + @Override + public void saveRolePermission(SysRolePermissionSaveDTO params) { + Integer roleId = params.getRoleId(); + // 1、先保存角色关联的菜单权限信息 + this.sysRoleMenuMapper.deleteAllMenusByRoleId(roleId); + List menuIdList = params.getMenuIdList(); + if (!CollectionUtils.isEmpty(menuIdList)) { + List roleMenuList = Lists.newArrayList(); + menuIdList.forEach(menuId -> { + SysRoleMenu roleMenuItem = new SysRoleMenu(); + roleMenuItem.setRoleId(roleId); + roleMenuItem.setMenuId(menuId); + roleMenuList.add(roleMenuItem); + }); + this.saveBatch(roleMenuList); + } + + // 2、再保存角色关联的按钮权限信息 + this.handleMenuAndBtnPermissionTree(roleId, params.getMenuAndBtnPermissionTree()); + } + + /** + * 递归处理菜单+按钮权限树信息数据 -> 保存按钮权限数据 + * + * @param roleId 角色id + * @param menuAndBtnPermissionTree: 权限树信息 + * @return void + * @author zhengqingya + * @date 2020/9/14 11:24 + */ + public void handleMenuAndBtnPermissionTree(Integer roleId, List menuAndBtnPermissionTree) { + if (!CollectionUtils.isEmpty(menuAndBtnPermissionTree)) { + menuAndBtnPermissionTree.forEach(menu -> { + Integer menuId = menu.getMenuId(); + // ① 先删除按钮权限数据 + this.sysRolePermissionService.deleteBtnsByRoleIdAndMenuId(roleId, menuId); + + // ② 保存按钮权限数据 + List permissionIdList = menu.getPermissionIdList(); + if (!CollectionUtils.isEmpty(permissionIdList)) { + SysRoleMenuBtnSaveDTO btnSaveItem = new SysRoleMenuBtnSaveDTO(); + btnSaveItem.setRoleId(roleId); + btnSaveItem.setMenuId(menuId); + btnSaveItem.setPermissionIdList(permissionIdList); + this.sysRolePermissionService.saveRoleMenuBtnIds(btnSaveItem); + } + + // ③ 判断如果有子树则递归下去 + List children = menu.getChildren(); + if (!CollectionUtils.isEmpty(children)) { + this.handleMenuAndBtnPermissionTree(roleId, children); + } + }); + } + } + + @Override + public void deleteAllMenusByRoleId(Integer roleId) { + this.sysRoleMenuMapper.deleteAllMenusByRoleId(roleId); + } + +} diff --git a/smallboot-api/system/src/main/java/com/zhengqing/system/service/impl/SysRolePermissionServiceImpl.java b/smallboot-api/system/src/main/java/com/zhengqing/system/service/impl/SysRolePermissionServiceImpl.java new file mode 100644 index 0000000..a9feddb --- /dev/null +++ b/smallboot-api/system/src/main/java/com/zhengqing/system/service/impl/SysRolePermissionServiceImpl.java @@ -0,0 +1,78 @@ +package com.zhengqing.system.service.impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.google.common.collect.Lists; +import com.zhengqing.system.entity.SysRolePermission; +import com.zhengqing.system.mapper.SysRolePermissionMapper; +import com.zhengqing.system.model.dto.SysRoleMenuBtnSaveDTO; +import com.zhengqing.system.model.vo.SysRoleMenuBtnListVO; +import com.zhengqing.system.service.ISysRolePermissionService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.util.CollectionUtils; + +import javax.annotation.Resource; +import java.util.List; + +/** + *

+ * 系统管理-角色关联权限 服务实现类 + *

+ * + * @author zhengqingya + * @description + * @date 2020/4/15 20:31 + */ +@Slf4j +@Service +@Transactional(rollbackFor = Exception.class) +public class SysRolePermissionServiceImpl extends ServiceImpl + implements ISysRolePermissionService { + + @Resource + private SysRolePermissionMapper sysRolePermissionMapper; + + @Override + public List listRoleMenuBtn() { + return this.sysRolePermissionMapper.selectRoleMenuBtns(); + } + + @Override + public List getPermissionBtnsByRoleIdAndMenuId(Integer roleId, Integer menuId) { + return this.sysRolePermissionMapper.selectBtnsByRoleIdAndMenuId(roleId, menuId); + } + + @Override + public void deleteBtnsByRoleId(Integer roleId) { + this.sysRolePermissionMapper.deleteBtnsByRoleId(roleId); + } + + @Override + public void deleteBtnsByRoleIdAndMenuId(Integer roleId, Integer menuId) { + this.sysRolePermissionMapper.deleteBtnsByRoleIdAndMenuId(roleId, menuId); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void saveRoleMenuBtnIds(SysRoleMenuBtnSaveDTO params) { + Integer roleId = params.getRoleId(); + Integer menuId = params.getMenuId(); + List permissionIdList = params.getPermissionIdList(); + // 1、先删除 + this.deleteBtnsByRoleIdAndMenuId(roleId, menuId); + if (CollectionUtils.isEmpty(permissionIdList)) { + return; + } + // 2、再保存 + List saveList = Lists.newArrayList(); + permissionIdList.forEach(btnId -> { + SysRolePermission item = new SysRolePermission(); + item.setRoleId(roleId); + item.setPermissionId(btnId); + saveList.add(item); + }); + this.saveBatch(saveList); + } + +} diff --git a/smallboot-api/system/src/main/java/com/zhengqing/system/service/impl/SysRoleServiceImpl.java b/smallboot-api/system/src/main/java/com/zhengqing/system/service/impl/SysRoleServiceImpl.java new file mode 100644 index 0000000..f57fa03 --- /dev/null +++ b/smallboot-api/system/src/main/java/com/zhengqing/system/service/impl/SysRoleServiceImpl.java @@ -0,0 +1,129 @@ +package com.zhengqing.system.service.impl; + +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.google.common.collect.Lists; +import com.zhengqing.common.base.util.MyBeanUtil; +import com.zhengqing.system.entity.SysRole; +import com.zhengqing.system.mapper.SysRoleMapper; +import com.zhengqing.system.model.dto.SysRoleListDTO; +import com.zhengqing.system.model.dto.SysRoleSaveDTO; +import com.zhengqing.system.model.vo.*; +import com.zhengqing.system.service.*; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.util.CollectionUtils; + +import javax.annotation.Resource; +import java.util.List; + +/** + *

+ * 系统管理 - 角色管理 服务实现类 + *

+ * + * @author zhengqingya + * @description + * @date 2020/4/15 15:01 + */ +@Slf4j +@Service +@Transactional(rollbackFor = Exception.class) +public class SysRoleServiceImpl extends ServiceImpl implements ISysRoleService { + + @Resource + private SysRoleMapper sysRoleMapper; + + @Resource + private ISysMenuService sysMenuService; + + @Resource + private ISysRoleMenuService sysRoleMenuService; + + @Resource + private ISysRolePermissionService sysRolePermissionService; + + @Resource + private ISysPermissionService sysPermissionService; + + @Override + public IPage listPage(SysRoleListDTO params) { + return this.sysRoleMapper.selectRoles(new Page(), params); + } + + @Override + public List list(SysRoleListDTO params) { + return this.sysRoleMapper.selectRoles(params); + } + + @Override + public Integer addOrUpdateData(SysRoleSaveDTO params) { + SysRole sysRole = SysRole.builder() + .roleId(params.getRoleId()) + .name(params.getName()) + .code(params.getCode()) + .status(params.getStatus()) + .build(); + sysRole.insertOrUpdate(); + return sysRole.getRoleId(); + } + + @Override + public SysRolePermissionDetailVO detail(Integer roleId) { + SysRole sysRole = this.sysRoleMapper.selectById(roleId); + SysRolePermissionDetailVO result = SysRolePermissionDetailVO.builder() + .roleId(sysRole.getRoleId()) + .name(sysRole.getName()) + .code(sysRole.getCode()) + .status(sysRole.getStatus()) + .build(); + List menuIdList = this.sysRoleMenuService.getMenuIdsByRoleId(roleId); + result.setMenuIdList( menuIdList); + return result; + } + + @Override + public SysRoleAllPermissionDetailVO permissionDetail(Integer roleId) { + SysRoleAllPermissionDetailVO permissionDetail = + MyBeanUtil.copyProperties(this.detail(roleId), SysRoleAllPermissionDetailVO.class); + List menTree = this.sysMenuService.tree(); + this.handleRecursionTree(menTree, roleId); + permissionDetail.setMenuAndBtnPermissionTree(menTree); + return permissionDetail; + } + + /** + * 递归树,填充角色菜单对应的菜单+按钮权限信息 + * + * @param menTree 树数据 + * @return void + * @author zhengqingya + * @date 2020/9/11 17:26 + */ + public void handleRecursionTree(List menTree, Integer roleId) { + if (!CollectionUtils.isEmpty(menTree)) { + menTree.forEach(menu -> { + Integer menuId = menu.getMenuId(); + List btnInfoList = this.sysPermissionService.getPermListByMenuId(menuId); + List permissionIdList = this.sysRolePermissionService.getPermissionBtnsByRoleIdAndMenuId(roleId, menuId); + menu.setBtnInfoList(btnInfoList); + menu.setPermissionIdList(permissionIdList); + List menuChildren = menu.getChildren(); + this.handleRecursionTree(menuChildren, roleId); + }); + } + } + + @Override + public void deleteRoleAndRoleMenu(Integer roleId) { + // 1、删除该角色下关联的菜单 + this.sysRoleMenuService.deleteAllMenusByRoleId(roleId); + // 2、删除该角色下关联的按钮 + this.sysRolePermissionService.deleteBtnsByRoleId(roleId); + // 3、删除角色 + this.removeById(roleId); + } + +} diff --git a/smallboot-api/system/src/main/java/com/zhengqing/system/service/impl/SysUserRoleServiceImpl.java b/smallboot-api/system/src/main/java/com/zhengqing/system/service/impl/SysUserRoleServiceImpl.java new file mode 100644 index 0000000..4792381 --- /dev/null +++ b/smallboot-api/system/src/main/java/com/zhengqing/system/service/impl/SysUserRoleServiceImpl.java @@ -0,0 +1,78 @@ +package com.zhengqing.system.service.impl; + +import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; +import com.zhengqing.system.entity.SysUserRole; +import com.zhengqing.system.mapper.SysUserRoleMapper; +import com.zhengqing.system.model.bo.SysUserReRoleIdListBO; +import com.zhengqing.system.model.dto.SysUserRoleSaveDTO; +import com.zhengqing.system.service.ISysUserRoleService; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.util.CollectionUtils; + +import java.util.LinkedList; +import java.util.List; +import java.util.Map; + +/** + *

+ * 系统管理 - 用户角色管理 服务实现类 + *

+ * + * @author zhengqingya + * @description + * @date 2020/9/10 11:52 + */ +@Slf4j +@Service +@RequiredArgsConstructor +public class SysUserRoleServiceImpl extends ServiceImpl implements ISysUserRoleService { + + private final SysUserRoleMapper sysUserRoleMapper; + + @Override + @Transactional(rollbackFor = Exception.class) + public void addOrUpdateData(SysUserRoleSaveDTO params) { + Integer userId = params.getUserId(); + List roleIdList = params.getRoleIdList(); + // ① 先删除关联角色 + this.deleteUserReRoleIds(userId); + // ② 再新增角色 + roleIdList.forEach(roleId -> + SysUserRole.builder() + .userId(userId) + .roleId(roleId) + .build() + .insert()); + } + + @Override + public List listRoleId(Integer userId) { + return this.mapRoleId(Lists.newArrayList(userId)).get(userId); + } + + @Override + public Map> mapRoleId(List userIdList) { + Map> resultMap = Maps.newHashMap(); + if (CollectionUtils.isEmpty(userIdList)) { + return resultMap; + } + List userReRoleIdList = this.sysUserRoleMapper.selectListByUserIds(userIdList); + for (SysUserReRoleIdListBO item : userReRoleIdList) { + resultMap.computeIfAbsent(item.getUserId(), k -> new LinkedList<>()).add(item.getRoleId()); + } + return resultMap; + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void deleteUserReRoleIds(Integer userId) { + this.sysUserRoleMapper.delete(new LambdaUpdateWrapper().eq(SysUserRole::getUserId, userId)); + } + +} diff --git a/smallboot-api/system/src/main/java/com/zhengqing/system/service/impl/SysUserServiceImpl.java b/smallboot-api/system/src/main/java/com/zhengqing/system/service/impl/SysUserServiceImpl.java new file mode 100644 index 0000000..c2cf013 --- /dev/null +++ b/smallboot-api/system/src/main/java/com/zhengqing/system/service/impl/SysUserServiceImpl.java @@ -0,0 +1,266 @@ +package com.zhengqing.system.service.impl; + +import cn.hutool.core.lang.Assert; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.google.common.collect.Lists; +import com.zhengqing.common.base.constant.AppConstant; +import com.zhengqing.common.base.exception.MyException; +import com.zhengqing.common.core.enums.UserSexEnum; +import com.zhengqing.common.core.util.DesUtil; +import com.zhengqing.common.db.constant.MybatisConstant; +import com.zhengqing.system.entity.SysRole; +import com.zhengqing.system.entity.SysUser; +import com.zhengqing.system.enums.SysMenuTypeEnum; +import com.zhengqing.system.enums.SysUserReRoleEnum; +import com.zhengqing.system.mapper.SysUserMapper; +import com.zhengqing.system.model.dto.*; +import com.zhengqing.system.model.vo.*; +import com.zhengqing.system.service.*; +import com.zhengqing.system.util.PasswordUtil; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.util.CollectionUtils; + +import java.util.*; +import java.util.stream.Collectors; + +/** + *

+ * 用户管理 服务实现类 + *

+ * + * @author zhengqingya + * @description + * @date 2020/4/15 11:33 + */ +@Slf4j +@Service +@RequiredArgsConstructor +public class SysUserServiceImpl extends ServiceImpl implements ISysUserService { + + private final SysUserMapper sysUserMapper; + + private final ISysRoleService sysRoleService; + + private final ISysUserRoleService sysUserRoleService; + + private final ISysMenuService sysMenuService; + + private final ISysRolePermissionService sysRolePermissionService; + + private final ISysRoleMenuService sysRoleMenuService; + + + @Override + public IPage listPage(SysUserListDTO params) { + IPage result = this.sysUserMapper.selectDataList(new Page<>(), params); + this.handleUserList(result.getRecords()); + return result; + } + + @Override + public List list(SysUserListDTO params) { + List userList = this.sysUserMapper.selectDataList(params); + this.handleUserList(userList); + return userList; + } + + public void handleUserList(List userList) { + if (CollectionUtils.isEmpty(userList)) { + return; + } + // 用户ids + List userIdList = userList.stream().map(SysUserListVO::getUserId).collect(Collectors.toList()); + Map> mapRoleInfo = this.sysUserRoleService.mapRoleId(userIdList); + userList.forEach(userInfo -> { + userInfo.setRoleIdList(mapRoleInfo.get(userInfo.getUserId())); + userInfo.handleData(); + }); + } + + @Override + public SysUserDetailVO detail(Integer userId) { + SysUserDetailVO detail = this.sysUserMapper.detail(userId); + Assert.notNull(detail, "用户不存在!"); + detail.handleData(); + return detail; + } + + @Override + @Transactional(rollbackFor = Exception.class) + public Integer addOrUpdateData(SysUserSaveDTO params) { + Integer userId = params.getUserId(); + String username = params.getUsername(); + String nickname = params.getNickname(); + Byte sex = params.getSex(); + String phone = params.getPhone(); + String email = params.getEmail(); + String avatarUrl = params.getAvatarUrl(); + + SysUser user = new SysUser(); + user.setUserId(userId); + user.setUsername(username); + user.setNickname(nickname); + user.setSexEnum(UserSexEnum.getEnum(sex)); + user.setPhone(phone); + user.setEmail(email); + user.setAvatarUrl(avatarUrl); + + if (userId == null) { + user.setPassword(PasswordUtil.encodePassword(AppConstant.DEFAULT_PASSWORD)); + user.insert(); + + // 绑定角色信息 + SysUserRoleSaveDTO userRoleSaveDTO = new SysUserRoleSaveDTO(); + userRoleSaveDTO.setUserId(user.getUserId()); + userRoleSaveDTO.setRoleIdList(Lists.newArrayList(SysUserReRoleEnum.凡人.getRoleId())); + this.sysUserRoleService.addOrUpdateData(userRoleSaveDTO); + } else { + user.updateById(); + } + return user.getUserId(); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void deleteUser(Integer userId) { + if (AppConstant.SYSTEM_SUPER_ADMIN_USER_ID.equals(userId)) { + throw new MyException("您没有权限删除超级管理员!"); + } + // 1、删除关联角色 + this.sysUserRoleService.deleteUserReRoleIds(userId); + // 2、删除该用户 + this.removeById(userId); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void updatePassword(SysUserUpdatePasswordDTO params) { + String password = DesUtil.decrypt(params.getPassword(), AppConstant.DES_KEY); + SysUser userInfo = this.sysUserMapper.selectById(params.getUserId()); + boolean isValid = PasswordUtil.isValidPassword(password, userInfo.getPassword()); + // 校验原始密码是否正确 + Assert.isTrue(isValid, AppConstant.WRONG_OLD_PASSWORD); + userInfo.setPassword(PasswordUtil.encodePassword(params.getNewPassword())); + this.sysUserMapper.updateById(userInfo); + } + + + @Override + @Transactional(rollbackFor = Exception.class) + public void resetPassword(Integer userId, String password) { + SysUser sysUser = this.sysUserMapper.selectById(userId); + sysUser.setPassword(PasswordUtil.encodePassword( + StringUtils.isBlank(password) ? AppConstant.DEFAULT_PASSWORD : password + )); + this.sysUserMapper.updateById(sysUser); + } + + @Override + public SysUserPermVO getUserPerm(SysUserPermDTO params) { + Assert.isFalse(params.getUserId() == null && StringUtils.isBlank(params.getUsername()), + "查询用户条件丢失!"); + // 1、拿到基础用户信息 + SysUserPermVO userPerm = this.sysUserMapper.selectUserPerm(params); + Assert.notNull(userPerm, "用户不存在!"); + // 2、角色信息 + List roleIdList = this.sysUserRoleService.listRoleId(userPerm.getUserId()); + Assert.notEmpty(roleIdList, "请联系管理员为其分配角色!"); + List roleList = this.sysRoleService.listByIds(roleIdList); + Assert.notEmpty(roleList, "请联系管理员为其分配角色!"); + StringJoiner roleNameSj = new StringJoiner(",", "[", "]"); + roleList.forEach(item -> roleNameSj.add(item.getName())); + userPerm.setRoleNames(roleNameSj.toString()); + List roleCodeList = roleList.stream().map(e -> e.getCode()).collect(Collectors.toList()); + userPerm.setRoleCodeList(roleCodeList); + + // 3、权限 + // 角色可访问的菜单ID + List menuIdList = this.sysRoleMenuService.getMenuIdsByRoleIds(roleIdList); + // 所有菜单 + List menuTreeList = this.sysMenuService.tree(); + // 所有按钮 + List btnList = this.sysRolePermissionService.listRoleMenuBtn(); + // 用户关联的权限 + List permTreeList = this.getUserPremTreeList(menuTreeList, menuIdList, roleIdList, btnList); + userPerm.setPermissionTreeList(permTreeList); + return userPerm; + } + + + /** + * 获取用户菜单权限树 + * + * @param menuTreeList 菜单树 + * @param menuIdList 用户所拥有的菜单权限ids + * @param roleIdList 用户所拥有的角色ids + * @param btnList 用户所拥有的菜单按钮权限 + * @return 过滤后的用户关联的权限菜单树 + * @author zhengqingya + * @date 2020/9/11 14:34 + */ + private List getUserPremTreeList(List menuTreeList, + List menuIdList, + List roleIdList, + List btnList) { + List resultList = Lists.newArrayList(); + for (SysMenuTreeVO menu : menuTreeList) { + Integer menuId = menu.getMenuId(); + if (menuIdList.contains(menuId) && SysMenuTypeEnum.菜单.getType().equals(menu.getType())) { + List menuChildList = menu.getChildren(); + if (!CollectionUtils.isEmpty(menuChildList)) { + menu.setChildren(this.getUserPremTreeList(menuChildList, menuIdList, roleIdList, btnList)); + } + List btnPermList = this.getUserBtnPermList(menuId, roleIdList, btnList); + menu.setMeta(SysUserBtnVO.builder() + .title(menu.getTitle()) + .icon(menu.getIcon()) + .btnPermList(btnPermList) + .build()); + resultList.add(menu); + } + } + return resultList; + } + + /** + * 获取用户按钮权限标识 + * + * @param menuId 菜单id + * @param roleIdList 用户所拥有的角色ids + * @param btnList 按钮权限 + * @return 按钮权限标识 + * @author zhengqingya + * @date 2020/9/11 14:36 + */ + private List getUserBtnPermList(Integer menuId, + List roleIdList, + List btnList) { + if (CollectionUtils.isEmpty(btnList)) { + return Lists.newArrayList(); + } + Set btnSet = new HashSet<>(); + btnList.forEach(btn -> { + if (menuId.equals(btn.getMenuId()) && roleIdList.contains(btn.getRoleId())) { + btnSet.add(btn.getBtnPerm()); + } + }); + return new ArrayList<>(btnSet); + } + + @Override + public SysUser getUserByUsername(String username) { + return this.sysUserMapper.selectOne( + new LambdaQueryWrapper() + .eq(SysUser::getUsername, username) + .last(MybatisConstant.LIMIT_ONE) + ); + } + +} diff --git a/smallboot-api/system/src/main/java/com/zhengqing/system/util/PasswordUtil.java b/smallboot-api/system/src/main/java/com/zhengqing/system/util/PasswordUtil.java new file mode 100644 index 0000000..522eddd --- /dev/null +++ b/smallboot-api/system/src/main/java/com/zhengqing/system/util/PasswordUtil.java @@ -0,0 +1,58 @@ +package com.zhengqing.system.util; + +import cn.hutool.crypto.SecureUtil; +import com.zhengqing.common.base.constant.AppConstant; +import lombok.extern.slf4j.Slf4j; + +import java.util.Objects; + +/** + *

加密工具

+ * + * @author zhengqingya + * @description + * @date 2023/1/30 14:29 + */ +@Slf4j +public class PasswordUtil { + + + /** + * 校验密码 + * + * @param password 未加密密码 + * @param encodePassword 加密后的密码 -- 指数据库中存储的密码 + * @return true:正确 false:错误 + * @author zhengqingya + * @date 2023/1/30 13:42 + */ + public static boolean isValidPassword(String password, String encodePassword) { + String decryptStr = SecureUtil.aes(AppConstant.AES_KEY).decryptStr(encodePassword); + return Objects.equals(password, decryptStr); + } + + /** + * 求加密后的密码 + * + * @param password 密码 + * @return 加密后的密码 + * @author zhengqingya + * @date 2023/1/30 14:23 + */ + public static String encodePassword(String password) { + return SecureUtil.aes(AppConstant.AES_KEY).encryptHex(password); + } + + /** + * 解密 + * + * @param encodePassword 加密后的密码 + * @return 加密后的密码 + * @author zhengqingya + * @date 2023/1/30 14:23 + */ + public static String decrypt(String encodePassword) { + return SecureUtil.aes(AppConstant.AES_KEY).decryptStr(encodePassword); + } + +} diff --git a/smallboot-api/system/src/test/java/com/zhengqing/system/AppTest.java b/smallboot-api/system/src/test/java/com/zhengqing/system/AppTest.java new file mode 100644 index 0000000..d0cb44d --- /dev/null +++ b/smallboot-api/system/src/test/java/com/zhengqing/system/AppTest.java @@ -0,0 +1,5 @@ +package com.zhengqing.system; + +public class AppTest { + +} diff --git a/smallboot-web/.editorconfig b/smallboot-web/.editorconfig new file mode 100644 index 0000000..3454886 --- /dev/null +++ b/smallboot-web/.editorconfig @@ -0,0 +1,14 @@ +# https://editorconfig.org +root = true + +[*] +charset = utf-8 +indent_style = space +indent_size = 2 +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true + +[*.md] +insert_final_newline = false +trim_trailing_whitespace = false diff --git a/smallboot-web/.env.dev b/smallboot-web/.env.dev new file mode 100644 index 0000000..fee2a11 --- /dev/null +++ b/smallboot-web/.env.dev @@ -0,0 +1,10 @@ +# 开发环境 +NODE_ENV='dev' + +# 为了防止意外地将一些环境变量泄漏到客户端,只有以 VITE_ 为前缀的变量才会暴露给经过 vite 处理的代码。 +# ts中通过`import.meta.env.VITE_APP_BASE_API`取值 +VITE_APP_PORT = 5173 +VITE_APP_BASE_API = '/dev-api' + +# 后端服务地址 +VITE_APP_SERVICE_API = 'http://localhost:888' diff --git a/smallboot-web/.env.prod b/smallboot-web/.env.prod new file mode 100644 index 0000000..b7be38b --- /dev/null +++ b/smallboot-web/.env.prod @@ -0,0 +1,10 @@ +# 生产环境 +NODE_ENV='prod' + +# 为了防止意外地将一些环境变量泄漏到客户端,只有以 VITE_ 为前缀的变量才会暴露给经过 vite 处理的代码。 +# ts中通过`import.meta.env.VITE_APP_BASE_API`取值 +VITE_APP_PORT = 5173 +VITE_APP_BASE_API = '/prod-api' + +# 后端服务地址 +VITE_APP_SERVICE_API = 'http://www.zhengqingya.com:888' diff --git a/smallboot-web/.eslintignore b/smallboot-web/.eslintignore new file mode 100644 index 0000000..db2fdd3 --- /dev/null +++ b/smallboot-web/.eslintignore @@ -0,0 +1,8 @@ +node_modules +*.md +.vscode +.idea +dist +/public +.eslintrc.js +src/assets \ No newline at end of file diff --git a/smallboot-web/.eslintrc.js b/smallboot-web/.eslintrc.js new file mode 100644 index 0000000..f293f6b --- /dev/null +++ b/smallboot-web/.eslintrc.js @@ -0,0 +1,26 @@ +module.exports = { + env: { + browser: true, + es2021: true, + node: true, + }, + globals: { + defineProps: 'readonly', + defineEmits: 'readonly', + defineExpose: 'readonly', + }, + parser: 'vue-eslint-parser', + extends: ['eslint:recommended', 'plugin:vue/vue3-essential', 'plugin:@typescript-eslint/recommended'], + parserOptions: { + ecmaVersion: 'latest', + parser: '@typescript-eslint/parser', + sourceType: 'module', + }, + plugins: ['vue', '@typescript-eslint'], + rules: { + 'vue/multi-word-component-names': 'off', + '@typescript-eslint/no-empty-function': 'off', // 关闭空方法检查 + '@typescript-eslint/no-explicit-any': 'off', // 关闭any类型的警告 + 'vue/no-v-model-argument': 'off', + }, +} diff --git a/smallboot-web/.gitignore b/smallboot-web/.gitignore new file mode 100644 index 0000000..a547bf3 --- /dev/null +++ b/smallboot-web/.gitignore @@ -0,0 +1,24 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/smallboot-web/.prettierignore b/smallboot-web/.prettierignore new file mode 100644 index 0000000..b737a15 --- /dev/null +++ b/smallboot-web/.prettierignore @@ -0,0 +1,4 @@ +/dist/* +/node_modules/** +**/*.svg +/public/* \ No newline at end of file diff --git a/smallboot-web/.prettierrc.js b/smallboot-web/.prettierrc.js new file mode 100644 index 0000000..2cc61a0 --- /dev/null +++ b/smallboot-web/.prettierrc.js @@ -0,0 +1,37 @@ +module.exports = { + // 一行最多 150 字符 + printWidth: 150, + // 使用 2 个空格缩进 + tabWidth: 2, + // 不使用缩进符,而使用空格 + useTabs: false, + // 行尾不需要有分号 + semi: false, + // 使用单引号 + singleQuote: true, + // 对象的 key 仅在必要时用引号 + quoteProps: 'as-needed', + // jsx 不使用单引号,而使用双引号 + jsxSingleQuote: false, + // 尾随逗号 + trailingComma: 'all', + // 大括号内的首尾需要空格 + bracketSpacing: true, + // jsx 标签的反尖括号需要换行 + jsxBracketSameLine: false, + // 箭头函数,只有一个参数的时候,也需要括号 + arrowParens: 'always', + // 每个文件格式化的范围是文件的全部内容 + rangeStart: 0, + rangeEnd: Infinity, + // 不需要写文件开头的 @prettier + requirePragma: false, + // 不需要自动在文件开头插入 @prettier + insertPragma: false, + // 使用默认的折行标准 + proseWrap: 'preserve', + // 根据显示样式决定 html 要不要折行 + htmlWhitespaceSensitivity: 'css', + // 换行符使用 lf + endOfLine: 'lf', +} diff --git a/smallboot-web/Docker/.dockerignore b/smallboot-web/Docker/.dockerignore new file mode 100644 index 0000000..3c3629e --- /dev/null +++ b/smallboot-web/Docker/.dockerignore @@ -0,0 +1 @@ +node_modules diff --git a/smallboot-web/Docker/Dockerfile b/smallboot-web/Docker/Dockerfile new file mode 100644 index 0000000..90e3374 --- /dev/null +++ b/smallboot-web/Docker/Dockerfile @@ -0,0 +1,17 @@ +# 拉取nginx基础镜像 +FROM nginx:1.21.1 + +# 维护者信息 +MAINTAINER zhengqingya + +# 将dist文件中的内容复制到 `/usr/share/nginx/html/` 这个目录下面 +COPY dist/ /usr/share/nginx/html/ +# 用本地配置文件来替换nginx镜像里的默认配置 +COPY Docker/nginx/nginx.conf /etc/nginx/nginx.conf + +# 对外暴漏的端口号 +# [注:EXPOSE指令只是声明容器运行时提供的服务端口,给读者看有哪些端口,在运行时只会开启程序自身的端口!!] +EXPOSE 80 + +# 启动nginx容器 +CMD ["nginx", "-g", "daemon off;"] diff --git a/smallboot-web/Docker/nginx/nginx.conf b/smallboot-web/Docker/nginx/nginx.conf new file mode 100644 index 0000000..2823487 --- /dev/null +++ b/smallboot-web/Docker/nginx/nginx.conf @@ -0,0 +1,69 @@ + +user nginx; +worker_processes 1; + +error_log /var/log/nginx/error.log warn; +pid /var/run/nginx.pid; + +events { + worker_connections 1024; +} + +http { + include /etc/nginx/mime.types; + default_type application/octet-stream; + + log_format main '$remote_addr - $remote_user [$time_local] "$request" ' + '$status $body_bytes_sent "$http_referer" ' + '"$http_user_agent" "$http_x_forwarded_for"'; + + access_log /var/log/nginx/access.log main; + + sendfile on; + #tcp_nopush on; + + keepalive_timeout 65; + + #gzip on; + + # include /etc/nginx/conf.d/*.conf; + + client_max_body_size 100m; # 限制上传文件大小 + + server { + listen 80; + server_name localhost; # 服务器地址或绑定域名 + + #charset koi8-r; + #access_log /var/log/nginx/host.access.log main; + + # ========================================================= + # ================== ↓↓↓↓↓↓ start ↓↓↓↓↓↓ ================== + # ========================================================= + + location / { + root /usr/share/nginx/html; + index index.html index.htm; + try_files $uri $uri/ /index.html; + } + + # ex: `http://127.0.0.1:80/prod-api/time` -> `xxx:888/time` + location ^~ /prod-api/ { + proxy_pass http://www.zhengqingya.com:888/; + } + + # ========================================================= + # ================== ↑↑↑↑↑↑ end ↑↑↑↑↑↑ ================== + # ========================================================= + + #error_page 404 /404.html; + + # redirect server error pages to the static page /50x.html + # + error_page 500 502 503 504 /50x.html; + location = /50x.html { + root /usr/share/nginx/html; + } + + } +} diff --git a/smallboot-web/README.md b/smallboot-web/README.md new file mode 100644 index 0000000..6683dcd --- /dev/null +++ b/smallboot-web/README.md @@ -0,0 +1,26 @@ +### 本地运行 + +```shell +# 安装依赖 +cnpm install + +# 运行 +cnpm run dev +``` + +### 部署 + +```shell +# 构建出dist文件夹,然后将打包后的文件放到nginx中... +cnpm run build:prod + +# 构建docker镜像 +docker build -f ./Docker/Dockerfile -t "registry.cn-hangzhou.aliyuncs.com/zhengqingya/smallboot-web:prod" . --no-cache + +# 推送到远程仓库 +docker push registry.cn-hangzhou.aliyuncs.com/zhengqingya/smallboot-web:prod + + +# 运行 +docker run -d --name web -p 80:80 --restart=always registry.cn-hangzhou.aliyuncs.com/zhengqingya/smallboot-web:prod +``` diff --git a/smallboot-web/deploy.sh b/smallboot-web/deploy.sh new file mode 100644 index 0000000..b0300fc --- /dev/null +++ b/smallboot-web/deploy.sh @@ -0,0 +1,23 @@ +#################################### +# @description 构建docker镜像 +# @params $? => 代表上一个命令执行后的退出状态: 0->成功,1->失败 +# @example => deploy.sh +# @author zhengqingya +# @date 2023/2/2 14:55 +#################################### + +# 在执行过程中若遇到使用了未定义的变量或命令返回值为非零,将直接报错退出 +set -eu + +rm -rf dist + +cnpm i + +# 构建出dist文件夹,然后将打包后的文件放到nginx中... +cnpm run build:prod + +# 构建docker镜像 +docker build -f ./Docker/Dockerfile -t "registry.cn-hangzhou.aliyuncs.com/zhengqingya/smallboot-web:prod" . --no-cache + +# 推送到远程仓库 +docker push registry.cn-hangzhou.aliyuncs.com/zhengqingya/smallboot-web:prod diff --git a/smallboot-web/index.html b/smallboot-web/index.html new file mode 100644 index 0000000..0d585c1 --- /dev/null +++ b/smallboot-web/index.html @@ -0,0 +1,14 @@ + + + + + + + SmallBoot + + + + +
+ + diff --git a/smallboot-web/package.json b/smallboot-web/package.json new file mode 100644 index 0000000..793de16 --- /dev/null +++ b/smallboot-web/package.json @@ -0,0 +1,43 @@ +{ + "name": "smallboot-web", + "private": true, + "version": "1.0.1", + "scripts": { + "dev": "vite --mode dev", + "prod": "vite --mode prod", + "build:prod": "vue-tsc --noEmit && vite build --mode prod", + "preview": "vite preview --port 8080 --mode prod", + "lint": "eslint --ext .js --ext .ts --ext .vue src", + "lint-fix": "eslint --ext .js --ext .ts --ext .vue src --fix", + "prettier": "prettier --write ." + }, + "dependencies": { + "@chenfengyuan/vue-qrcode": "^2.0.0", + "@element-plus/icons-vue": "^2.0.6", + "axios": "^0.27.2", + "element-plus": "^2.2.9", + "nprogress": "^0.2.0", + "path-browserify": "^1.0.1", + "path-to-regexp": "^6.2.1", + "pinia": "^2.0.16", + "vue": "^3.2.37", + "vue-router": "^4.1.2" + }, + "devDependencies": { + "@types/node": "^18.0.5", + "@types/nprogress": "^0.2.0", + "@types/path-browserify": "^1.0.0", + "@typescript-eslint/eslint-plugin": "^5.30.6", + "@typescript-eslint/parser": "^5.30.6", + "@vitejs/plugin-vue": "^3.0.0", + "eslint": "^8.20.0", + "eslint-config-prettier": "^8.5.0", + "eslint-plugin-prettier": "^4.2.1", + "eslint-plugin-vue": "^9.2.0", + "prettier": "^2.7.1", + "sass": "^1.53.0", + "typescript": "^4.6.4", + "vite": "^3.0.0", + "vue-tsc": "^0.38.4" + } +} diff --git a/smallboot-web/public/favicon.ico b/smallboot-web/public/favicon.ico new file mode 100644 index 0000000..34b63ac Binary files /dev/null and b/smallboot-web/public/favicon.ico differ diff --git a/smallboot-web/src/App.vue b/smallboot-web/src/App.vue new file mode 100644 index 0000000..7fa4e50 --- /dev/null +++ b/smallboot-web/src/App.vue @@ -0,0 +1,9 @@ + + + + + diff --git a/smallboot-web/src/api/index.ts b/smallboot-web/src/api/index.ts new file mode 100644 index 0000000..9ba7297 --- /dev/null +++ b/smallboot-web/src/api/index.ts @@ -0,0 +1,16 @@ +// 拿到所有api +const modulesFiles = import.meta.globEager('./*/*.*') +const modules: any = {} +for (const key in modulesFiles) { + const moduleName = key.replace(/(.*\/)*([^.]+).*/gi, '$2') + const value: any = modulesFiles[key] + if (value.default) { + // 兼容js + modules[moduleName] = value.default + } else { + // 兼容ts + modules[moduleName] = value + } +} +// console.log(666, modules); +export default modules diff --git a/smallboot-web/src/api/system/sys_dict.js b/smallboot-web/src/api/system/sys_dict.js new file mode 100644 index 0000000..1b1a52a --- /dev/null +++ b/smallboot-web/src/api/system/sys_dict.js @@ -0,0 +1,43 @@ +import request from '@/utils/request' + +const BASE_API = '/web/api/system/dict' + +export default { + listFromCacheByCode(code) { + return request({ + url: BASE_API + '/listFromCacheByCode', + method: 'get', + params: { code: code }, + }) + }, + listByCode(code) { + return request({ + url: BASE_API + '/listByCode', + method: 'get', + params: { code: code }, + }) + }, + add(data) { + return request({ + url: BASE_API, + method: 'post', + data, + }) + }, + update(data) { + return request({ + url: BASE_API, + method: 'put', + data, + }) + }, + delete(id) { + return request({ + url: BASE_API, + method: 'delete', + params: { + id: id, + }, + }) + }, +} diff --git a/smallboot-web/src/api/system/sys_dict_type.js b/smallboot-web/src/api/system/sys_dict_type.js new file mode 100644 index 0000000..4fda5ac --- /dev/null +++ b/smallboot-web/src/api/system/sys_dict_type.js @@ -0,0 +1,35 @@ +import request from '@/utils/request' + +const BASE_API = '/web/api/system/dict/type' + +export default { + list() { + return request({ + url: BASE_API + '/list', + method: 'get', + }) + }, + add(data) { + return request({ + url: BASE_API, + method: 'post', + data, + }) + }, + update(data) { + return request({ + url: BASE_API, + method: 'put', + data, + }) + }, + delete(id) { + return request({ + url: BASE_API, + method: 'delete', + params: { + id: id, + }, + }) + }, +} diff --git a/smallboot-web/src/api/system/sys_login.ts b/smallboot-web/src/api/system/sys_login.ts new file mode 100644 index 0000000..fb422e0 --- /dev/null +++ b/smallboot-web/src/api/system/sys_login.ts @@ -0,0 +1,32 @@ +import request from '@/utils/request' +import { AxiosPromise } from 'axios' +import { Captcha, LoginFormData, LoginResponseData } from '@/types/api/system/login' + +// 获取验证码 +export function getCaptcha(): AxiosPromise { + return request({ + url: '/captcha?t=' + new Date().getTime().toString(), + method: 'get', + }) +} + +// 登录 +export function login(data: LoginFormData): AxiosPromise { + return request({ + url: '/auth/login', + method: 'post', + data, + // headers: { + // // 客户端信息Base64明文:web:123456 + // Authorization: 'Basic d2ViOjEyMzQ1Ng==', + // }, + }) +} + +// 注销 +export function logout() { + return request({ + url: '/auth/logout', + method: 'delete', + }) +} diff --git a/smallboot-web/src/api/system/sys_menu.js b/smallboot-web/src/api/system/sys_menu.js new file mode 100644 index 0000000..7c61fa6 --- /dev/null +++ b/smallboot-web/src/api/system/sys_menu.js @@ -0,0 +1,80 @@ +import request from '@/utils/request' + +const BASE_API = '/web/api/system/menu' + +export default { + menuTree() { + return request({ + url: BASE_API + '/menuTree', + method: 'get', + params: { systemSource: 0 }, + }) + }, + add(data) { + return request({ + url: BASE_API, + method: 'post', + data, + }) + }, + update(data) { + return request({ + url: BASE_API, + method: 'put', + data, + }) + }, + delete(menuId) { + return request({ + url: BASE_API, + method: 'delete', + params: { + menuId: menuId, + }, + }) + }, + /** + * 获取菜单关联按钮权限信息 + */ + getPermListByMenuId(id) { + return request({ + url: BASE_API + '/getPermListByMenuId', + method: 'get', + params: { + menuId: id, + }, + }) + }, + /** + * 删除菜单关联按钮权限 + */ + deleteMenuReBtnPerm(id) { + return request({ + url: BASE_API + '/deleteMenuReBtnPerm', + method: 'delete', + params: { + id: id, + }, + }) + }, + /** + * 新增菜单关联按钮权限 + */ + addMenuReBtnPerm(data) { + return request({ + url: BASE_API + '/addMenuReBtnPerm', + method: 'post', + data, + }) + }, + /** + * 更新菜单关联按钮权限 + */ + updateMenuReBtnPerm(data) { + return request({ + url: BASE_API + '/updateMenuReBtnPerm', + method: 'put', + data, + }) + }, +} diff --git a/smallboot-web/src/api/system/sys_oauth.js b/smallboot-web/src/api/system/sys_oauth.js new file mode 100644 index 0000000..fe1da6c --- /dev/null +++ b/smallboot-web/src/api/system/sys_oauth.js @@ -0,0 +1,33 @@ +import request from '@/utils/request' + +const BASE_API = '/web/api/system/oauth' + +export default { + thirdpartOauth(oauthType) { + return request({ + url: BASE_API + '/' + oauthType, + method: 'get', + }) + }, + bindThirdPart(data) { + return request({ + url: BASE_API + '/bindThirdPart', + method: 'post', + data, + }) + }, + getOauthDataList(params) { + return request({ + url: BASE_API + '/getOauthDataList', + method: 'get', + params: params, + }) + }, + removeBind(data) { + return request({ + url: BASE_API + '/removeBind', + method: 'post', + data, + }) + }, +} diff --git a/smallboot-web/src/api/system/sys_role.js b/smallboot-web/src/api/system/sys_role.js new file mode 100644 index 0000000..1c5debf --- /dev/null +++ b/smallboot-web/src/api/system/sys_role.js @@ -0,0 +1,91 @@ +import request from '@/utils/request' + +const BASE_API = '/web/api/system/role' + +export default { + listPage(query, headers) { + return request({ + url: BASE_API + '/listPage', + method: 'get', + params: query, + headers, + }) + }, + list(query) { + return request({ + url: BASE_API + '/list', + method: 'get', + params: query, + }) + }, + detail(id) { + return request({ + url: BASE_API + '/detail', + method: 'get', + params: { roleId: id }, + }) + }, + add(data) { + return request({ + url: BASE_API, + method: 'post', + data, + }) + }, + update(data) { + return request({ + url: BASE_API, + method: 'put', + data, + }) + }, + delete(id) { + return request({ + url: BASE_API, + method: 'delete', + params: { roleId: id }, + }) + }, + updateStatus(id, status) { + return request({ + url: BASE_API + '/updateStatus', + method: 'post', + data: { roleId: id, status: status }, + }) + }, + + /** + * 获取角色管理 页面中的权限按钮被选中的按钮 + */ + getPermissionBtnsByRoleIdAndMenuId(data) { + return request({ + url: BASE_API + '/getPermissionBtnsByRoleIdAndMenuId', + method: 'get', + params: data, + }) + }, + /** + * 保存角色管理页面中的权限按钮被选中的按钮 + */ + savePermissionBtnIds(data) { + return request({ + url: BASE_API + '/saveRoleMenuBtnIds', + method: 'post', + data, + }) + }, + savePermissionMenuIds(data) { + return request({ + url: BASE_API + '/saveRoleMenuIds', + method: 'post', + data, + }) + }, + saveRolePermission(data) { + return request({ + url: BASE_API + '/saveRolePermission', + method: 'post', + data, + }) + }, +} diff --git a/smallboot-web/src/api/system/sys_user.js b/smallboot-web/src/api/system/sys_user.js new file mode 100644 index 0000000..42accbb --- /dev/null +++ b/smallboot-web/src/api/system/sys_user.js @@ -0,0 +1,81 @@ +import request from '@/utils/request' + +const BASE_API = '/web/api/system/user' + +export default { + getUserPerm() { + return request({ + url: BASE_API + '/getUserPerm', + method: 'get', + // params: { systemSource: 0 } + }) + }, + listPage(query, headers) { + return request({ + url: BASE_API + '/listPage', + method: 'get', + params: query, + headers, + }) + }, + add(data) { + return request({ + url: BASE_API, + method: 'post', + data, + }) + }, + update(data) { + return request({ + url: BASE_API, + method: 'put', + data, + }) + }, + delete(id) { + return request({ + url: BASE_API, + method: 'delete', + params: { userId: id }, + }) + }, + updateStatus(id, status) { + return request({ + url: BASE_API + '/updateStatus', + method: 'post', + data: { userId: id, status: status }, + }) + }, + resetPassword(data) { + return request({ + url: BASE_API + '/resetPassword', + method: 'get', + params: data, + }) + }, + getUserInfoById(userId) { + return request({ + url: BASE_API + '', + method: 'get', + params: { + userId: userId, + }, + }) + }, + // 保存用户角色 + saveRoleIds(data) { + return request({ + url: BASE_API + '/saveRoleIds', + method: 'post', + data: data, + }) + }, + // 修改密码 + updatePassword(data) { + return request({ + url: BASE_API + '/updatePassword', + method: 'put', + data: data, + }) + }, +} diff --git a/smallboot-web/src/api/system/sys_user_perm.ts b/smallboot-web/src/api/system/sys_user_perm.ts new file mode 100644 index 0000000..81648c5 --- /dev/null +++ b/smallboot-web/src/api/system/sys_user_perm.ts @@ -0,0 +1,12 @@ +import request from '@/utils/request' +import { AxiosPromise } from 'axios' +import { UserInfo } from '@/types/api/system/user' + +const BASE_API = '/web/api/system/user' + +export function getUserPerm(): AxiosPromise { + return request({ + url: BASE_API + '/getUserPerm', + method: 'get', + }) +} diff --git a/smallboot-web/src/components/Breadcrumb/index.vue b/smallboot-web/src/components/Breadcrumb/index.vue new file mode 100644 index 0000000..2b49577 --- /dev/null +++ b/smallboot-web/src/components/Breadcrumb/index.vue @@ -0,0 +1,93 @@ + + + + + diff --git a/smallboot-web/src/components/Hamburger/index.vue b/smallboot-web/src/components/Hamburger/index.vue new file mode 100644 index 0000000..9f55066 --- /dev/null +++ b/smallboot-web/src/components/Hamburger/index.vue @@ -0,0 +1,38 @@ + + + + + diff --git a/smallboot-web/src/components/base/BaseCellItem.vue b/smallboot-web/src/components/base/BaseCellItem.vue new file mode 100644 index 0000000..c98c10f --- /dev/null +++ b/smallboot-web/src/components/base/BaseCellItem.vue @@ -0,0 +1,80 @@ + + + diff --git a/smallboot-web/src/components/base/BaseDeleteBtn.vue b/smallboot-web/src/components/base/BaseDeleteBtn.vue new file mode 100644 index 0000000..b6a5541 --- /dev/null +++ b/smallboot-web/src/components/base/BaseDeleteBtn.vue @@ -0,0 +1,28 @@ + + + diff --git a/smallboot-web/src/components/base/BaseDialog.vue b/smallboot-web/src/components/base/BaseDialog.vue new file mode 100644 index 0000000..51a81ed --- /dev/null +++ b/smallboot-web/src/components/base/BaseDialog.vue @@ -0,0 +1,22 @@ + + + diff --git a/smallboot-web/src/components/base/BaseFooter.vue b/smallboot-web/src/components/base/BaseFooter.vue new file mode 100644 index 0000000..a279361 --- /dev/null +++ b/smallboot-web/src/components/base/BaseFooter.vue @@ -0,0 +1,45 @@ + + + diff --git a/smallboot-web/src/components/base/BaseHeader.vue b/smallboot-web/src/components/base/BaseHeader.vue new file mode 100644 index 0000000..dc9fe56 --- /dev/null +++ b/smallboot-web/src/components/base/BaseHeader.vue @@ -0,0 +1,40 @@ + + + diff --git a/smallboot-web/src/components/base/BaseNoData.vue b/smallboot-web/src/components/base/BaseNoData.vue new file mode 100644 index 0000000..c2a058c --- /dev/null +++ b/smallboot-web/src/components/base/BaseNoData.vue @@ -0,0 +1,19 @@ + + diff --git a/smallboot-web/src/components/base/BasePagination.vue b/smallboot-web/src/components/base/BasePagination.vue new file mode 100644 index 0000000..7b7f5d2 --- /dev/null +++ b/smallboot-web/src/components/base/BasePagination.vue @@ -0,0 +1,98 @@ + + + + + diff --git a/smallboot-web/src/components/base/BaseTable.vue b/smallboot-web/src/components/base/BaseTable.vue new file mode 100644 index 0000000..842a172 --- /dev/null +++ b/smallboot-web/src/components/base/BaseTable.vue @@ -0,0 +1,28 @@ + + diff --git a/smallboot-web/src/components/base/BaseTableCell.vue b/smallboot-web/src/components/base/BaseTableCell.vue new file mode 100644 index 0000000..2ef653a --- /dev/null +++ b/smallboot-web/src/components/base/BaseTableCell.vue @@ -0,0 +1,49 @@ + + + diff --git a/smallboot-web/src/components/base/BaseTableP.vue b/smallboot-web/src/components/base/BaseTableP.vue new file mode 100644 index 0000000..48e42d6 --- /dev/null +++ b/smallboot-web/src/components/base/BaseTableP.vue @@ -0,0 +1,192 @@ + + + diff --git a/smallboot-web/src/components/base/BaseTitleCard.vue b/smallboot-web/src/components/base/BaseTitleCard.vue new file mode 100644 index 0000000..fa162cf --- /dev/null +++ b/smallboot-web/src/components/base/BaseTitleCard.vue @@ -0,0 +1,74 @@ + + + diff --git a/smallboot-web/src/components/base/BaseUpload.vue b/smallboot-web/src/components/base/BaseUpload.vue new file mode 100644 index 0000000..09f889b --- /dev/null +++ b/smallboot-web/src/components/base/BaseUpload.vue @@ -0,0 +1,50 @@ + + + diff --git a/smallboot-web/src/components/base/BaseUploadButton.vue b/smallboot-web/src/components/base/BaseUploadButton.vue new file mode 100644 index 0000000..1ca4774 --- /dev/null +++ b/smallboot-web/src/components/base/BaseUploadButton.vue @@ -0,0 +1,80 @@ + + diff --git a/smallboot-web/src/components/base/BaseWraper.vue b/smallboot-web/src/components/base/BaseWraper.vue new file mode 100644 index 0000000..5bd466d --- /dev/null +++ b/smallboot-web/src/components/base/BaseWraper.vue @@ -0,0 +1,27 @@ + + + diff --git a/smallboot-web/src/components/index.ts b/smallboot-web/src/components/index.ts new file mode 100644 index 0000000..7340bd4 --- /dev/null +++ b/smallboot-web/src/components/index.ts @@ -0,0 +1,11 @@ +const modulesFiles = import.meta.globEager('./*/*.vue') + +const modules: any = {} +for (const key in modulesFiles) { + const moduleName = key.replace(/(.*\/)*([^.]+).*/gi, '$2') + const value: any = modulesFiles[key] + modules[moduleName] = value.default +} + +// console.log(666, modules); +export default modules diff --git a/smallboot-web/src/directive/index.ts b/smallboot-web/src/directive/index.ts new file mode 100644 index 0000000..b5ea752 --- /dev/null +++ b/smallboot-web/src/directive/index.ts @@ -0,0 +1 @@ +export { hasPerm, hasRole } from './permission' diff --git a/smallboot-web/src/directive/permission/index.ts b/smallboot-web/src/directive/permission/index.ts new file mode 100644 index 0000000..4e82bc3 --- /dev/null +++ b/smallboot-web/src/directive/permission/index.ts @@ -0,0 +1,83 @@ +import useStore from '@/store' +import { Directive, DirectiveBinding } from 'vue' + +// 自定义权限指令`v-hasPerm` `v-hasRole` + +/** + * 按钮权限校验 + * array : v-hasPerm="['sys:user:add','sys:user:edit']" + * single : v-hasPerm="'sys:user:add'" + */ +export const hasPerm: Directive = { + mounted(el: HTMLElement, binding: DirectiveBinding) { + const { user } = useStore() + const { value } = binding + + // 当前路由 + const currentRouteUrl = window.location.hash.replace('#/', '') + console.log('当前路由:', currentRouteUrl) + const btnPermList = getBtnPermList(currentRouteUrl, user.permissionTreeList, []) + // console.log('拥有的按钮权限:', btnPermList) + if (value) { + // DOM绑定需要的按钮权限标识 + const requiredPerms = value instanceof Array ? value : [value] + // console.log('需要的按钮权限:', requiredPerms) + const hasPerm = btnPermList.some((btnPerm) => { + return requiredPerms.includes(btnPerm) + }) + if (!hasPerm) { + el.parentNode && el.parentNode.removeChild(el) + } + } else { + throw new Error("need perms! Like v-has-perm=\"['sys:user:add','sys:user:edit']\"") + } + }, +} + +/** + * 获取当前路由下的按钮权限 + * @param currentRouteUrl 当前路由url ex: system/user/index + * @param permissionTreeList 权限菜单树 + * @param btnPermList 按钮权限 + * @returns 按钮权限 + */ +function getBtnPermList(currentRouteUrl: string, permissionTreeList: any, btnPermList: Array): Array { + if (permissionTreeList) { + permissionTreeList.forEach((e: { meta: any; component: string; children: any }) => { + if (e.component === currentRouteUrl || e.component === currentRouteUrl + '/index') { + e.meta.btnPermList.forEach((btnPerm: string) => { + btnPermList.push(btnPerm) + }) + } + const childList = e.children + if (childList) { + getBtnPermList(currentRouteUrl, childList, btnPermList) + } else { + return btnPermList + } + }) + } + return btnPermList +} + +/** + * 角色权限校验 + */ +export const hasRole: Directive = { + mounted(el: HTMLElement, binding: DirectiveBinding) { + const { value } = binding + if (value) { + // DOM绑定需要的角色编码 + const requiredRoles = value + const { user } = useStore() + const hasRole = user.roleNames.some((perm) => { + return requiredRoles.includes(perm) + }) + if (!hasRole) { + el.parentNode && el.parentNode.removeChild(el) + } + } else { + throw new Error("need roles! Like v-has-role=\"['admin','test']\"") + } + }, +} diff --git a/smallboot-web/src/layout/components/AppMain.vue b/smallboot-web/src/layout/components/AppMain.vue new file mode 100644 index 0000000..8adc5f9 --- /dev/null +++ b/smallboot-web/src/layout/components/AppMain.vue @@ -0,0 +1,51 @@ + + + + + + + diff --git a/smallboot-web/src/layout/components/Navbar.vue b/smallboot-web/src/layout/components/Navbar.vue new file mode 100644 index 0000000..54c138f --- /dev/null +++ b/smallboot-web/src/layout/components/Navbar.vue @@ -0,0 +1,152 @@ + + + + diff --git a/smallboot-web/src/layout/components/Sidebar/Link.vue b/smallboot-web/src/layout/components/Sidebar/Link.vue new file mode 100644 index 0000000..53ec1ce --- /dev/null +++ b/smallboot-web/src/layout/components/Sidebar/Link.vue @@ -0,0 +1,45 @@ + + + diff --git a/smallboot-web/src/layout/components/Sidebar/Logo.vue b/smallboot-web/src/layout/components/Sidebar/Logo.vue new file mode 100644 index 0000000..184c410 --- /dev/null +++ b/smallboot-web/src/layout/components/Sidebar/Logo.vue @@ -0,0 +1,84 @@ + + + + + diff --git a/smallboot-web/src/layout/components/Sidebar/SidebarItem.vue b/smallboot-web/src/layout/components/Sidebar/SidebarItem.vue new file mode 100644 index 0000000..76712fa --- /dev/null +++ b/smallboot-web/src/layout/components/Sidebar/SidebarItem.vue @@ -0,0 +1,96 @@ + + + + + diff --git a/smallboot-web/src/layout/components/Sidebar/index.vue b/smallboot-web/src/layout/components/Sidebar/index.vue new file mode 100644 index 0000000..5021243 --- /dev/null +++ b/smallboot-web/src/layout/components/Sidebar/index.vue @@ -0,0 +1,44 @@ + + + diff --git a/smallboot-web/src/layout/components/TagsView/ScrollPane.vue b/smallboot-web/src/layout/components/TagsView/ScrollPane.vue new file mode 100644 index 0000000..e59f5a4 --- /dev/null +++ b/smallboot-web/src/layout/components/TagsView/ScrollPane.vue @@ -0,0 +1,107 @@ + + + + + diff --git a/smallboot-web/src/layout/components/TagsView/index.vue b/smallboot-web/src/layout/components/TagsView/index.vue new file mode 100644 index 0000000..53c0fe6 --- /dev/null +++ b/smallboot-web/src/layout/components/TagsView/index.vue @@ -0,0 +1,333 @@ + + + + + diff --git a/smallboot-web/src/layout/components/index.ts b/smallboot-web/src/layout/components/index.ts new file mode 100644 index 0000000..91c52c6 --- /dev/null +++ b/smallboot-web/src/layout/components/index.ts @@ -0,0 +1,3 @@ +export { default as Navbar } from './Navbar.vue' +export { default as AppMain } from './AppMain.vue' +export { default as TagsView } from './TagsView/index.vue' diff --git a/smallboot-web/src/layout/index.vue b/smallboot-web/src/layout/index.vue new file mode 100644 index 0000000..578bcdb --- /dev/null +++ b/smallboot-web/src/layout/index.vue @@ -0,0 +1,95 @@ + + + + + diff --git a/smallboot-web/src/layout/parentView.vue b/smallboot-web/src/layout/parentView.vue new file mode 100644 index 0000000..4381952 --- /dev/null +++ b/smallboot-web/src/layout/parentView.vue @@ -0,0 +1,13 @@ + + + diff --git a/smallboot-web/src/main.ts b/smallboot-web/src/main.ts new file mode 100644 index 0000000..ce1b043 --- /dev/null +++ b/smallboot-web/src/main.ts @@ -0,0 +1,55 @@ +import { createApp, Directive } from 'vue' +import App from './App.vue' + +const app = createApp(App) + +// ****** ↓↓↓ 路由 ↓↓↓ ****** +import router from '@/router' +app.use(router) + +// ****** ↓↓↓ element-plus ↓↓↓ ****** +import ElementPlus from 'element-plus' +import 'element-plus/dist/index.css' +import * as ElementPlusIconsVue from '@element-plus/icons-vue' +// 注册所有图标 +for (const [key, component] of Object.entries(ElementPlusIconsVue)) { + app.component(key, component) +} +app.use(ElementPlus) + +// 自定义样式 +import '@/styles/index.scss' + +// pinia +import { createPinia } from 'pinia' +const pinia = createPinia() +app.use(pinia) + +// store +import useStore from '@/store' +app.config.globalProperties.$store = useStore() + +// 配置全局api +import api from '@/api' +app.config.globalProperties.$api = api + +// 全局组件注册 +import myComponent from '@/components/index' +Object.keys(myComponent).forEach((key) => { + app.component(key, myComponent[key]) +}) + +// 抽取公用的实例 - 操作成功与失败消息提醒内容等 +import mixin from '@/utils/mixin' +app.mixin(mixin) + +// 路由权限 +import '@/permission' + +// 自定义指令(按钮权限) +import * as directive from '@/directive' +Object.keys(directive).forEach((key) => { + app.directive(key, (directive as { [key: string]: Directive })[key]) +}) + +app.mount('#app') diff --git a/smallboot-web/src/permission.ts b/smallboot-web/src/permission.ts new file mode 100644 index 0000000..2a24a09 --- /dev/null +++ b/smallboot-web/src/permission.ts @@ -0,0 +1,59 @@ +import router from '@/router' +import { ElMessage } from 'element-plus' +import useStore from '@/store' +import NProgress from 'nprogress' +import 'nprogress/nprogress.css' +NProgress.configure({ showSpinner: false }) // 进度环显示/隐藏 + +// 白名单路由 +const whiteList = ['/login', '/auth-redirect', '/bus/model/detail'] + +router.beforeEach(async (to, from, next) => { + NProgress.start() + const { user, permission } = useStore() + const hasToken = user.tokenValue + if (hasToken) { + // 登录成功,跳转到首页 + if (to.path === '/login') { + next({ path: '/' }) + NProgress.done() + } else { + const hasGetUserInfo = user.roleNames.length > 0 + if (hasGetUserInfo) { + if (to.matched.length === 0) { + from.name ? next({ name: from.name as any }) : next('/401') + } else { + next() + } + } else { + try { + await user.getUserInfo() + const roleNames = user.roleNames + const accessRoutes: any = await permission.generateRoutes(roleNames) + accessRoutes.forEach((route: any) => { + router.addRoute(route) + }) + next({ ...to, replace: true }) + } catch (error) { + // 移除 token 并跳转登录页 + await user.resetToken() + ElMessage.error("错误:" + (error as any) || 'Has Error') + next(`/login?redirect=${to.path}`) + NProgress.done() + } + } + } + } else { + // 未登录可以访问白名单页面(登录页面) + if (whiteList.indexOf(to.path) !== -1) { + next() + } else { + next(`/login?redirect=${to.path}`) + NProgress.done() + } + } +}) + +router.afterEach(() => { + NProgress.done() +}) diff --git a/smallboot-web/src/router/index.ts b/smallboot-web/src/router/index.ts new file mode 100644 index 0000000..cf1eaa6 --- /dev/null +++ b/smallboot-web/src/router/index.ts @@ -0,0 +1,41 @@ +import { createRouter, createWebHashHistory, RouteRecordRaw } from 'vue-router' +import useStore from '@/store' + +// 静态路由 +export const constantRoutes: Array = [ + { + path: '/login', + component: () => import('@/views/login/index.vue'), + }, + { + path: '/test', + component: () => import('@/views/test/index.vue'), + }, + { + path: '/404', + component: () => import('@/views/error-page/404.vue'), + }, + { + path: '/401', + component: () => import('@/views/error-page/401.vue'), + }, +] + +// 创建路由 +const router = createRouter({ + history: createWebHashHistory(), + routes: constantRoutes as RouteRecordRaw[], +}) + +// 重置路由 +export function resetRouter() { + const { permission } = useStore() + permission.routes.forEach((route) => { + const name = route.name + if (name && router.hasRoute(name)) { + router.removeRoute(name) + } + }) +} + +export default router diff --git a/smallboot-web/src/store/index.ts b/smallboot-web/src/store/index.ts new file mode 100644 index 0000000..a258523 --- /dev/null +++ b/smallboot-web/src/store/index.ts @@ -0,0 +1,15 @@ +import useAppStore from './modules/app' +import useUserStore from './modules/user' +import usePermissionStore from './modules/permission' +import useSettingStore from './modules/settings' +import useTagsViewStore from './modules/tagsView' + +const useStore = () => ({ + app: useAppStore(), + user: useUserStore(), + permission: usePermissionStore(), + setting: useSettingStore(), + tagsView: useTagsViewStore(), +}) + +export default useStore diff --git a/smallboot-web/src/store/modules/app.ts b/smallboot-web/src/store/modules/app.ts new file mode 100644 index 0000000..bbe04e0 --- /dev/null +++ b/smallboot-web/src/store/modules/app.ts @@ -0,0 +1,40 @@ +import { AppState } from '@/types/store/app' +import { localStorage } from '@/utils/storage' +import { defineStore } from 'pinia' + +const useAppStore = defineStore({ + id: 'app', + state: (): AppState => ({ + name: localStorage.get('name') || 'Small Tools', + device: 'desktop', + sidebar: { + opened: localStorage.get('sidebarStatus') ? !!+localStorage.get('sidebarStatus') : true, + withoutAnimation: false, + }, + }), + actions: { + setName(name: string) { + this.name = name + localStorage.set('name', name) + }, + toggleSidebar() { + this.sidebar.opened = !this.sidebar.opened + this.sidebar.withoutAnimation = false + if (this.sidebar.opened) { + localStorage.set('sidebarStatus', 1) + } else { + localStorage.set('sidebarStatus', 0) + } + }, + closeSideBar(withoutAnimation: any) { + localStorage.set('sidebarStatus', 0) + this.sidebar.opened = false + this.sidebar.withoutAnimation = withoutAnimation + }, + toggleDevice(device: string) { + this.device = device + }, + }, +}) + +export default useAppStore diff --git a/smallboot-web/src/store/modules/permission.ts b/smallboot-web/src/store/modules/permission.ts new file mode 100644 index 0000000..a9069d7 --- /dev/null +++ b/smallboot-web/src/store/modules/permission.ts @@ -0,0 +1,60 @@ +import { PermissionState } from '@/types/store/permission' +import { RouteRecordRaw } from 'vue-router' +import { defineStore } from 'pinia' +import { constantRoutes } from '@/router' +import useStore from '@/store' + +const modules = import.meta.glob('../../views/**/**.vue') +export const Layout = () => import('@/layout/index.vue') +export const parentView = () => import('@/layout/parentView.vue') + +export const filterAsyncRoutes = (routes: RouteRecordRaw[], roleNames: string[]) => { + const res: RouteRecordRaw[] = [] + routes.forEach((route) => { + const tmp = { ...route } as any + if (tmp.component === 'Layout') { + tmp.component = Layout + } else if (tmp.component === 'parentView') { + tmp.component = parentView + } else { + const component = modules[`../../views/${tmp.component}.vue`] as any + if (component) { + tmp.component = modules[`../../views/${tmp.component}.vue`] + } else { + tmp.component = modules[`../../views/error-page/404.vue`] + } + } + res.push(tmp) + if (tmp.children) { + tmp.children = filterAsyncRoutes(tmp.children, roleNames) + } + }) + return res +} + +/** + * 侧边栏权限路由 + */ +const usePermissionStore = defineStore({ + id: 'permission', + state: (): PermissionState => ({ + routes: [], + addRoutes: [], + }), + actions: { + setRoutes(routes: RouteRecordRaw[]) { + this.addRoutes = routes + this.routes = constantRoutes.concat(routes) + }, + generateRoutes(roleNames: string[]) { + const { user } = useStore() + const accessedRoutes = filterAsyncRoutes(user.permissionTreeList, roleNames) + return new Promise((resolve, reject) => { + this.setRoutes(accessedRoutes) + resolve(accessedRoutes) + }) + }, + }, +}) + +export default usePermissionStore diff --git a/smallboot-web/src/store/modules/settings.ts b/smallboot-web/src/store/modules/settings.ts new file mode 100644 index 0000000..010e89c --- /dev/null +++ b/smallboot-web/src/store/modules/settings.ts @@ -0,0 +1,13 @@ +import { defineStore } from 'pinia' +import { SettingState } from '@/types/store/setting' +import { localStorage } from '@/utils/storage' + +export const useSettingStore = defineStore({ + id: 'setting', + state: (): SettingState => ({ + // tagsView: localStorage.get('tagsView') != null ? localStorage.get('tagsView') : true, + tagsView: false, + }), +}) + +export default useSettingStore diff --git a/smallboot-web/src/store/modules/tagsView.ts b/smallboot-web/src/store/modules/tagsView.ts new file mode 100644 index 0000000..46a17a5 --- /dev/null +++ b/smallboot-web/src/store/modules/tagsView.ts @@ -0,0 +1,177 @@ +import { defineStore } from 'pinia' +import { TagsViewState } from '@/types/store/tagsview' + +const useTagsViewStore = defineStore({ + id: 'tagsView', + state: (): TagsViewState => ({ + visitedViews: [], + cachedViews: [], // keepAlive 缓存页面 + }), + actions: { + addVisitedView(view: any) { + if (this.visitedViews.some((v) => v.path === view.path)) return + if (view.meta && view.meta.affix) { + this.visitedViews.unshift( + Object.assign({}, view, { + title: view.meta?.title || 'no-name', + }), + ) + } else { + this.visitedViews.push( + Object.assign({}, view, { + title: view.meta?.title || 'no-name', + }), + ) + } + }, + addCachedView(view: any) { + if (this.cachedViews.includes(view.name)) return + if (view.meta.keepAlive) { + this.cachedViews.push(view.name) + } + }, + delVisitedView(view: any) { + return new Promise((resolve) => { + for (const [i, v] of this.visitedViews.entries()) { + if (v.path === view.path) { + this.visitedViews.splice(i, 1) + break + } + } + resolve([...this.visitedViews]) + }) + }, + delCachedView(view: any) { + return new Promise((resolve) => { + const index = this.cachedViews.indexOf(view.name) + index > -1 && this.cachedViews.splice(index, 1) + resolve([...this.cachedViews]) + }) + }, + delOtherVisitedViews(view: any) { + return new Promise((resolve) => { + this.visitedViews = this.visitedViews.filter((v) => { + return v.meta?.affix || v.path === view.path + }) + resolve([...this.visitedViews]) + }) + }, + delOtherCachedViews(view: any) { + return new Promise((resolve) => { + const index = this.cachedViews.indexOf(view.name) + if (index > -1) { + this.cachedViews = this.cachedViews.slice(index, index + 1) + } else { + // if index = -1, there is no cached tags + this.cachedViews = [] + } + resolve([...this.cachedViews]) + }) + }, + + updateVisitedView(view: any) { + for (let v of this.visitedViews) { + if (v.path === view.path) { + v = Object.assign(v, view) + break + } + } + }, + addView(view: any) { + this.addVisitedView(view) + this.addCachedView(view) + }, + delView(view: any) { + return new Promise((resolve) => { + this.delVisitedView(view) + this.delCachedView(view) + resolve({ + visitedViews: [...this.visitedViews], + cachedViews: [...this.cachedViews], + }) + }) + }, + delOtherViews(view: any) { + return new Promise((resolve) => { + this.delOtherVisitedViews(view) + this.delOtherCachedViews(view) + resolve({ + visitedViews: [...this.visitedViews], + cachedViews: [...this.cachedViews], + }) + }) + }, + delLeftViews(view: any) { + return new Promise((resolve) => { + const currIndex = this.visitedViews.findIndex((v) => v.path === view.path) + if (currIndex === -1) { + return + } + this.visitedViews = this.visitedViews.filter((item, index) => { + // affix:true 固定tag,例如“首页” + if (index >= currIndex || (item.meta && item.meta.affix)) { + return true + } + + const cacheIndex = this.cachedViews.indexOf(item.name as string) + if (cacheIndex > -1) { + this.cachedViews.splice(cacheIndex, 1) + } + return false + }) + resolve({ + visitedViews: [...this.visitedViews], + }) + }) + }, + delRightViews(view: any) { + return new Promise((resolve) => { + const currIndex = this.visitedViews.findIndex((v) => v.path === view.path) + if (currIndex === -1) { + return + } + this.visitedViews = this.visitedViews.filter((item, index) => { + // affix:true 固定tag,例如“首页” + if (index <= currIndex || (item.meta && item.meta.affix)) { + return true + } + + const cacheIndex = this.cachedViews.indexOf(item.name as string) + if (cacheIndex > -1) { + this.cachedViews.splice(cacheIndex, 1) + } + return false + }) + resolve({ + visitedViews: [...this.visitedViews], + }) + }) + }, + delAllViews() { + return new Promise((resolve) => { + const affixTags = this.visitedViews.filter((tag) => tag.meta?.affix) + this.visitedViews = affixTags + this.cachedViews = [] + resolve({ + visitedViews: [...this.visitedViews], + cachedViews: [...this.cachedViews], + }) + }) + }, + delAllVisitedViews() { + return new Promise((resolve) => { + const affixTags = this.visitedViews.filter((tag) => tag.meta?.affix) + this.visitedViews = affixTags + resolve([...this.visitedViews]) + }) + }, + delAllCachedViews() { + return new Promise((resolve) => { + this.cachedViews = [] + resolve([...this.cachedViews]) + }) + }, + }, +}) + +export default useTagsViewStore diff --git a/smallboot-web/src/store/modules/user.ts b/smallboot-web/src/store/modules/user.ts new file mode 100644 index 0000000..dce61c5 --- /dev/null +++ b/smallboot-web/src/store/modules/user.ts @@ -0,0 +1,112 @@ +import { defineStore } from 'pinia' +import { LoginFormData } from '@/types/api/system/login' +import { UserState } from '@/types/store/user' + +import { localStorage } from '@/utils/storage' +import { login, logout } from '@/api/system/sys_login' +import { getUserPerm } from '@/api/system/sys_user_perm' +import { resetRouter } from '@/router' + + + +const useUserStore = defineStore({ + id: 'user', + state: (): UserState => ({ + userId: 0, + openId: '', + tokenName: localStorage.get('tokenName') || 'Authorization-smallboot', + tokenValue: localStorage.get('tokenValue') || '', + nickname: '', + avatarUrl: '', + roleNames: [], + permissionTreeList: [], + }), + actions: { + async RESET_STATE() { + this.$reset() + }, + /** + * 登录 + */ + login(loginData: LoginFormData) { + const { username, password, code, uuid } = loginData + return new Promise((resolve, reject) => { + login({ + username: username.trim(), + password: password.trim(), + grant_type: 'captcha', + code: code, + uuid: uuid, + }) + .then((response) => { + const { tokenName, tokenValue } = response.data + localStorage.set('tokenName', tokenName) + localStorage.set('tokenValue', tokenValue) + this.tokenName = tokenName + this.tokenValue = tokenValue + resolve(response) + }) + .catch((error) => { + reject(error) + }) + }) + }, + /** + * 获取用户信息(昵称、头像、角色集合、权限集合) + */ + getUserInfo() { + return new Promise((resolve, reject) => { + getUserPerm() + .then(({ data }: any) => { + if (!data) { + return reject('Verification failed, please Login again.') + } + const { userId, openId, nickname, avatarUrl, roleNames, permissionTreeList } = data + this.userId = userId + this.openId = openId + this.nickname = nickname + this.avatarUrl = avatarUrl + this.roleNames = roleNames + this.permissionTreeList = permissionTreeList + resolve(data) + }) + .catch((error: any) => { + reject(error) + }) + }) + }, + + /** + * 注销 + */ + logout() { + return new Promise((resolve, reject) => { + logout() + .then(() => { + localStorage.remove('tokenName') + localStorage.remove('tokenValue') + this.RESET_STATE() + resetRouter() + resolve(null) + }) + .catch((error) => { + reject(error) + }) + }) + }, + + /** + * 清除 Token + */ + resetToken() { + return new Promise((resolve) => { + localStorage.remove('tokenName') + localStorage.remove('tokenValue') + this.RESET_STATE() + resolve(null) + }) + }, + }, +}) + +export default useUserStore diff --git a/smallboot-web/src/styles/app-theme.scss b/smallboot-web/src/styles/app-theme.scss new file mode 100644 index 0000000..c56cb85 --- /dev/null +++ b/smallboot-web/src/styles/app-theme.scss @@ -0,0 +1,4 @@ +$dark_main_color: #021b32; +$dark_table_th_bgcolor: #00284c; +$dark_table_td_color: #7189c8; +$dark_table_border_color: #1d2f5e; diff --git a/smallboot-web/src/styles/element-plus-theme.scss b/smallboot-web/src/styles/element-plus-theme.scss new file mode 100644 index 0000000..951d325 --- /dev/null +++ b/smallboot-web/src/styles/element-plus-theme.scss @@ -0,0 +1,138 @@ +// ****** ↓↓↓ 覆盖 element-plus 的样式 ↓↓↓ ****** +:root { + // 设置自定义颜色 + --el-color-white: #021b32; + --el-color-black: #012447; + --el-color-primary: #409eff; + --el-color-primary-light-3: #4192e4; + --el-color-primary-light-5: #0b7ef1; + --el-color-primary-light-7: #154270; + --el-color-primary-light-8: #d9ecff; + --el-color-primary-light-9: #3281cf; + --el-color-primary-dark-2: #409eff; + --el-color-success: #67c23a; + --el-color-success-light-3: #499038; + --el-color-success-light-5: #499038; + --el-color-success-light-7: #499038; + --el-color-success-light-8: #e1f3d8; + --el-color-success-light-9: #0233; + --el-color-success-dark-2: #499038; + --el-color-warning: #e6a23c; + --el-color-warning-light-3: #9c7835; + --el-color-warning-light-5: #745371; + --el-color-warning-light-7: #464435; + --el-color-warning-light-8: #faecd8; + --el-color-warning-light-9: #192933; + --el-color-warning-dark-2: #f5990e; + --el-color-danger: #f56c6c; + --el-color-danger-light-3: #NaN545; + --el-color-danger-light-5: #7444; + --el-color-danger-light-7: #43343; + --el-color-danger-light-8: #fde2e2; + --el-color-danger-light-9: #12338; + --el-color-danger-dark-2: #NaN66; + --el-color-error: #f56c6c; + --el-color-error-light-3: #NaN545; + --el-color-error-light-5: #7444; + --el-color-error-light-7: #43343; + --el-color-error-light-8: #fde2e2; + --el-color-error-light-9: #12338; + --el-color-error-dark-2: #NaN66; + --el-color-info: #909399; + --el-color-info-light-3: #6567; + --el-color-info-light-5: #495766; + --el-color-info-light-7: #2351; + --el-color-info-light-8: #e9e9eb; + --el-color-info-light-9: #10273; + --el-color-info-dark-2: #909399; + --el-bg-color: #021b32; + --el-bg-color-page: #021b32; + --el-bg-color-overlay: #021b32; + --el-text-color-primary: #ffffff; + --el-text-color-regular: #ffffff; + --el-text-color-placeholder: #c0c4cc; + --el-text-color-secondary: #ffffff; + --el-text-color-disabled: #c0c4cc; + --el-border-color: #388de0; + --el-border-color-light: #388de0; + --el-border-color-lighter: #58a9f5; + --el-border-color-extra-light: #86b3f9; + --el-border-color-dark: #d4d7de; + --el-border-color-darker: #cdd0d6; + --el-fill-color: #012447; + --el-fill-color-light: #021b32; + --el-fill-color-lighter: #021b32; + --el-fill-color-extra-light: #021b32; + --el-fill-color-dark: #021b32; + --el-fill-color-darker: #021b32; + --el-fill-color-blank: #021b32; + --el-box-shadow: 0px 12px 32px 4px rgba(0, 0, 0, 0.04), 0px 8px 20px rgba(0, 0, 0, 0.08); + --el-box-shadow-light: 0px 0px 12px rgba(0, 0, 0, 0.12); + --el-box-shadow-lighter: 0px 0px 6px rgba(0, 0, 0, 0.12); + --el-box-shadow-dark: 0px 16px 48px 16px rgba(0, 0, 0, 0.08), 0px 12px 32px rgba(0, 0, 0, 0.12), 0px 8px 16px -8px rgba(0, 0, 0, 0.16); + --el-disabled-bg-color: var(--el-fill-color-light); + --el-disabled-text-color: var(--el-text-color-placeholder); + --el-disabled-border-color: var(--el-border-color-light); + --el-overlay-color: rgba(0, 0, 0, 0.8); + --el-overlay-color-light: rgba(0, 0, 0, 0.7); + --el-overlay-color-lighter: rgba(0, 0, 0, 0.5); + --el-mask-color: rgba(255, 255, 255, 0.9); + --el-mask-color-extra-light: rgba(255, 255, 255, 0.3); + --el-border-width: 1px; + --el-border-style: solid; + --el-border-color-hover: var(--el-text-color-disabled); + --el-border: var(--el-border-width) var(--el-border-style) var(--el-border-color); + --el-svg-monochrome-grey: var(--el-border-color); + --el-color-primary-light-6: #1484; + --el-color-primary-light-4: #276NaN; + --el-color-primary-light-2: #3484NaN; + --el-color-primary-light-1: #391NaN; + --el-color-success-light-6: #2535; + --el-color-success-light-4: #3737; + --el-color-success-light-2: #53NaN38; + --el-color-success-light-1: #5NaN39; + --el-color-warning-light-6: #55136; + --el-color-warning-light-4: #8638; + --el-color-warning-light-2: #NaN873; + --el-color-warning-light-1: #NaN953; + --el-color-danger-light-6: #63349; + --el-color-danger-light-4: #94455; + --el-color-danger-light-2: #NaN560; + --el-color-danger-light-1: #NaN6466; + --el-color-error-light-6: #63349; + --el-color-error-light-4: #94455; + --el-color-error-light-2: #NaN560; + --el-color-error-light-1: #NaN6466; + --el-color-info-light-6: #345; + --el-color-info-light-4: #576370; + --el-color-info-light-2: #74784; + --el-color-info-light-1: #82878; +} + +// 按钮 +.el-button--text { + // background-color: #8f6732 !important; + margin-left: 3px; + border: none !important; +} +.el-button--primary { + // --el-button-hover-text-color: var(--el-color-primary); + // --el-button-hover-bg-color: var(--el-color-primary); + // --el-button-hover-link-text-color:var(--el-border-color-dark); +} + +// +.el-button.is-link { + margin-left: 3px; + color: #409eff; +} + +// 加载 +.el-loading-mask { + background-color: rgb(72 108 131 / 90%); +} + +// 选中行背景色值 +.el-table__body tr.current-row td { + background-color: #2d928ab5 !important; +} diff --git a/smallboot-web/src/styles/index.scss b/smallboot-web/src/styles/index.scss new file mode 100644 index 0000000..080a7c2 --- /dev/null +++ b/smallboot-web/src/styles/index.scss @@ -0,0 +1,68 @@ +@import './variables.module'; +@import './mixin.scss'; +@import './element-plus-theme'; +@import './sidebar.scss'; +@import './app-theme'; + +body { + margin: 0; + padding: 0; + height: 100%; + -moz-osx-font-smoothing: grayscale; + -webkit-font-smoothing: antialiased; + text-rendering: optimizeLegibility; + font-family: Helvetica Neue, Helvetica, PingFang SC, Hiragino Sans GB, Microsoft YaHei, Arial, sans-serif; + background-color: #021b32; +} + +label { + font-weight: 700; +} + +html { + height: 100%; + box-sizing: border-box; +} + +#app { + height: 100%; +} + +*, +*:before, +*:after { + box-sizing: inherit; +} + +a:focus, +a:active { + outline: none; +} + +a, +a:focus, +a:hover { + cursor: pointer; + color: inherit; + text-decoration: none; +} + +div:focus { + outline: none; +} + +.clearfix { + &:after { + visibility: hidden; + display: block; + font-size: 0; + content: ' '; + clear: both; + height: 0; + } +} + +// main-container global css +.app-container { + padding: 20px; +} diff --git a/smallboot-web/src/styles/mixin.scss b/smallboot-web/src/styles/mixin.scss new file mode 100644 index 0000000..3ca7168 --- /dev/null +++ b/smallboot-web/src/styles/mixin.scss @@ -0,0 +1,28 @@ +@mixin clearfix { + &:after { + content: ''; + display: table; + clear: both; + } +} + +@mixin scrollBar { + &::-webkit-scrollbar-track-piece { + background: #d3dce6; + } + + &::-webkit-scrollbar { + width: 6px; + } + + &::-webkit-scrollbar-thumb { + background: #99a9bf; + border-radius: 20px; + } +} + +@mixin relative { + position: relative; + width: 100%; + height: 100%; +} diff --git a/smallboot-web/src/styles/sidebar.scss b/smallboot-web/src/styles/sidebar.scss new file mode 100644 index 0000000..ba214ff --- /dev/null +++ b/smallboot-web/src/styles/sidebar.scss @@ -0,0 +1,227 @@ +#app { + .main-container { + min-height: 100%; + transition: margin-left 0.28s; + margin-left: $sideBarWidth; + position: relative; + } + + .sidebar-container { + transition: width 0.28s; + width: $sideBarWidth !important; + background-color: $menuBg; + height: 100%; + position: fixed; + font-size: 0px; + top: 0; + bottom: 0; + left: 0; + z-index: 1001; + overflow: hidden; + + // reset element-ui css + .horizontal-collapse-transition { + transition: 0s width ease-in-out, 0s padding-left ease-in-out, 0s padding-right ease-in-out; + } + + .scrollbar-wrapper { + overflow-x: hidden !important; + } + + .el-scrollbar__bar.is-vertical { + right: 0px; + } + + .el-scrollbar { + height: 100%; + } + + &.has-logo { + .el-scrollbar { + height: calc(100% - 50px); + } + } + + .is-horizontal { + display: none; + } + + a { + display: inline-block; + width: 100%; + overflow: hidden; + } + + .svg-icon { + margin-right: 16px; + } + + .sub-el-icon { + margin-right: 12px; + margin-left: -2px; + } + + .el-menu { + border: none; + height: 100%; + width: 100% !important; + } + + // menu hover + .submenu-title-noDropdown, + .el-sub-menu__title { + &:hover { + background-color: $menuHover !important; + } + } + + .is-active > .el-sub-menu__title { + color: $subMenuActiveText !important; + } + + & .nest-menu .el-sub-menu > .el-sub-menu__title, + & .el-sub-menu .el-menu-item { + min-width: $sideBarWidth !important; + background-color: $subMenuBg !important; + + &:hover { + background-color: $subMenuHover !important; + } + } + } + + .hideSidebar { + .sidebar-container { + width: 54px !important; + .svg-icon { + margin-right: 0px; + } + } + + .main-container { + margin-left: 54px; + } + + .submenu-title-noDropdown { + padding: 0 !important; + position: relative; + + .el-tooltip { + padding: 0 !important; + + .svg-icon { + margin-left: 20px; + } + + .sub-el-icon { + margin-left: 19px; + } + } + } + + .el-sub-menu { + overflow: hidden; + + & > .el-sub-menu__title { + padding: 0 !important; + + .svg-icon { + margin-left: 20px; + } + + .sub-el-icon { + margin-left: 19px; + } + + .el-sub-menu__icon-arrow { + display: none; + } + } + } + + .el-menu--collapse { + .el-sub-menu { + & > .el-sub-menu__title { + & > span { + height: 0; + width: 0; + overflow: hidden; + visibility: hidden; + display: inline-block; + } + } + } + } + } + + .el-menu--collapse .el-menu .el-sub-menu { + min-width: $sideBarWidth !important; + } + + // mobile responsive + .mobile { + .main-container { + margin-left: 0px; + } + + .sidebar-container { + transition: transform 0.28s; + width: $sideBarWidth !important; + } + + &.hideSidebar { + .sidebar-container { + pointer-events: none; + transition-duration: 0.3s; + transform: translate3d(-$sideBarWidth, 0, 0); + } + } + } + + .withoutAnimation { + .main-container, + .sidebar-container { + transition: none; + } + } +} + +// when menu collapsed +.el-menu--vertical { + & > .el-menu { + .svg-icon { + margin-right: 16px; + } + .sub-el-icon { + margin-right: 12px; + margin-left: -2px; + } + } + + .nest-menu .el-sub-menu > .el-sub-menu__title, + .el-menu-item { + &:hover { + // you can use $subMenuHover + background-color: $menuHover !important; + } + } + + // the scroll bar appears when the subMenu is too long + > .el-menu--popup { + max-height: 100vh; + overflow-y: auto; + + &::-webkit-scrollbar-track-piece { + background: #d3dce6; + } + + &::-webkit-scrollbar { + width: 6px; + } + + &::-webkit-scrollbar-thumb { + background: #99a9bf; + border-radius: 20px; + } + } +} diff --git a/smallboot-web/src/styles/variables.module.scss b/smallboot-web/src/styles/variables.module.scss new file mode 100644 index 0000000..4e078da --- /dev/null +++ b/smallboot-web/src/styles/variables.module.scss @@ -0,0 +1,24 @@ +// sidebar +$menuText: #bfcbd9; +$menuActiveText: #409eff; +$subMenuActiveText: #f4f4f5; + +// 菜单栏背景色 +$menuBg: #021b32; +$menuHover: #263445; + +$subMenuBg: #00284c; +$subMenuHover: $subMenuBg; + +$sideBarWidth: 210px; + +:export { + menuText: $menuText; + menuActiveText: $menuActiveText; + subMenuActiveText: $subMenuActiveText; + menuBg: $menuBg; + menuHover: $menuHover; + subMenuBg: $subMenuBg; + subMenuHover: $subMenuHover; + sideBarWidth: $sideBarWidth; +} diff --git a/smallboot-web/src/types/api/system/login.d.ts b/smallboot-web/src/types/api/system/login.d.ts new file mode 100644 index 0000000..1c4f944 --- /dev/null +++ b/smallboot-web/src/types/api/system/login.d.ts @@ -0,0 +1,20 @@ +// 验证码类型声明 +export interface Captcha { + img: string + uuid: string +} + +export interface LoginFormData { + username: string + password: string + grant_type: string + code: string + uuid: string +} + +export interface LoginResponseData { + token_type: string + access_token: string + tokenName: string + tokenValue: string +} diff --git a/smallboot-web/src/types/api/system/user.d.ts b/smallboot-web/src/types/api/system/user.d.ts new file mode 100644 index 0000000..0dbfaf6 --- /dev/null +++ b/smallboot-web/src/types/api/system/user.d.ts @@ -0,0 +1,8 @@ +export interface UserInfo { + userId: number + openId: string + nickname: string + avatarUrl: string + roleNames: string[] + permissionTreeList: [] +} diff --git a/smallboot-web/src/types/store/app.d.ts b/smallboot-web/src/types/store/app.d.ts new file mode 100644 index 0000000..90cb154 --- /dev/null +++ b/smallboot-web/src/types/store/app.d.ts @@ -0,0 +1,8 @@ +export interface AppState { + name: string + device: string + sidebar: { + opened: boolean + withoutAnimation: boolean + } +} diff --git a/smallboot-web/src/types/store/permission.d.ts b/smallboot-web/src/types/store/permission.d.ts new file mode 100644 index 0000000..b0cd3dd --- /dev/null +++ b/smallboot-web/src/types/store/permission.d.ts @@ -0,0 +1,4 @@ +export interface PermissionState { + routes: RouteRecordRaw[] + addRoutes: RouteRecordRaw[] +} diff --git a/smallboot-web/src/types/store/setting.d.ts b/smallboot-web/src/types/store/setting.d.ts new file mode 100644 index 0000000..dd3189f --- /dev/null +++ b/smallboot-web/src/types/store/setting.d.ts @@ -0,0 +1,3 @@ +export interface SettingState { + tagsView: boolean +} diff --git a/smallboot-web/src/types/store/tagsview.d.ts b/smallboot-web/src/types/store/tagsview.d.ts new file mode 100644 index 0000000..0164ac9 --- /dev/null +++ b/smallboot-web/src/types/store/tagsview.d.ts @@ -0,0 +1,10 @@ +import { RouteLocationNormalized } from 'vue-router' + +export interface TagView extends Partial { + title?: string +} + +export interface TagsViewState { + visitedViews: TagView[] + cachedViews: string[] +} diff --git a/smallboot-web/src/types/store/user.d.ts b/smallboot-web/src/types/store/user.d.ts new file mode 100644 index 0000000..6708b8a --- /dev/null +++ b/smallboot-web/src/types/store/user.d.ts @@ -0,0 +1,10 @@ +export interface UserState { + userId: number + openId: string + tokenName: string + tokenValue: string + nickname: string + avatarUrl: string + roleNames: string[] + permissionTreeList: [] +} diff --git a/smallboot-web/src/utils/index.ts b/smallboot-web/src/utils/index.ts new file mode 100644 index 0000000..1e6d99c --- /dev/null +++ b/smallboot-web/src/utils/index.ts @@ -0,0 +1,31 @@ +export function hasClass(ele: HTMLElement, cls: string) { + return !!ele.className.match(new RegExp('(\\s|^)' + cls + '(\\s|$)')) +} + +export function addClass(ele: HTMLElement, cls: string) { + if (!hasClass(ele, cls)) ele.className += ' ' + cls +} + +export function removeClass(ele: HTMLElement, cls: string) { + if (hasClass(ele, cls)) { + const reg = new RegExp('(\\s|^)' + cls + '(\\s|$)') + ele.className = ele.className.replace(reg, ' ') + } +} + +export function mix(color1: string, color2: string, weight: number) { + weight = Math.max(Math.min(Number(weight), 1), 0) + const r1 = parseInt(color1.substring(1, 3), 16) + const g1 = parseInt(color1.substring(3, 5), 16) + const b1 = parseInt(color1.substring(5, 7), 16) + const r2 = parseInt(color2.substring(1, 3), 16) + const g2 = parseInt(color2.substring(3, 5), 16) + const b2 = parseInt(color2.substring(5, 7), 16) + const r = Math.round(r1 * (1 - weight) + r2 * weight) + const g = Math.round(g1 * (1 - weight) + g2 * weight) + const b = Math.round(b1 * (1 - weight) + b2 * weight) + const rStr = ('0' + (r || 0).toString(16)).slice(-2) + const gStr = ('0' + (g || 0).toString(16)).slice(-2) + const bStr = ('0' + (b || 0).toString(16)).slice(-2) + return '#' + rStr + gStr + bStr +} diff --git a/smallboot-web/src/utils/mixin.js b/smallboot-web/src/utils/mixin.js new file mode 100644 index 0000000..fe16ad4 --- /dev/null +++ b/smallboot-web/src/utils/mixin.js @@ -0,0 +1,24 @@ +// 抽取公用的实例 - 操作成功与失败消息提醒内容等 +export default { + methods: { + // 操作成功消息提醒内容 + submitOk(msg, cb) { + this.$notify({ + title: '成功', + message: msg, + type: 'success', + duration: 2000, + onClose: function () { + cb && cb() + }, + }) + }, + // 操作失败消息提醒内容 + submitFail(msg) { + this.$message({ + message: msg, + type: 'error', + }) + }, + }, +} diff --git a/smallboot-web/src/utils/request.ts b/smallboot-web/src/utils/request.ts new file mode 100644 index 0000000..7a09640 --- /dev/null +++ b/smallboot-web/src/utils/request.ts @@ -0,0 +1,92 @@ +import axios, { AxiosRequestConfig, AxiosResponse } from 'axios' +import { ElMessage, ElMessageBox } from 'element-plus' +import { localStorage } from '@/utils/storage' +import useStore from '@/store' + +// 创建axios实例 +const service = axios.create({ + baseURL: import.meta.env.VITE_APP_BASE_API, + // 请求超时时间:50s + timeout: 50000, + headers: { 'Content-Type': 'application/json;charset=utf-8' }, +}) + +// 请求拦截器 +service.interceptors.request.use( + (config: AxiosRequestConfig) => { + if (!config.headers) { + throw new Error(`Expected 'config' and 'config.headers' not to be undefined`) + } + const { user } = useStore() + if (user.tokenValue) { + // 授权认证 + config.headers[user.tokenName] = user.tokenValue + } + // 租户ID + config.headers['TENANT_ID'] = '1' + return config + }, + (error) => { + return Promise.reject(error) + }, +) + +// 响应拦截器 +service.interceptors.response.use( + (response: AxiosResponse) => { + const res = response.data + const { code, msg } = res + if (code === 200) { + return res + } else { + // token过期 + if (code === -1) { + ElMessageBox.confirm('您的登录账号已失效,请重新登录', { + confirmButtonText: '再次登录', + cancelButtonText: '取消', + type: 'warning', + }).then(() => { + // 清除浏览器全部缓存 + localStorage.clear() + // 跳转登录页 + window.location.href = '/' + location.reload() + }) + } else { + ElMessage({ + message: msg || '系统出错', + type: 'error', + duration: 5 * 1000, + }) + } + return Promise.reject(new Error(msg || 'Error')) + } + }, + (error) => { + const { msg } = error.response.data + // 未认证 + if (error.response.status === 401) { + ElMessageBox.confirm('您的登录账号已失效,请重新登录', { + confirmButtonText: '再次登录', + cancelButtonText: '取消', + type: 'warning', + }).then(() => { + // 清除浏览器全部缓存 + localStorage.clear() + // 跳转登录页 + window.location.href = '/' + location.reload() + }) + } else { + ElMessage({ + message: '网络异常,请稍后再试!', + type: 'error', + duration: 5 * 1000, + }) + return Promise.reject(new Error(msg || 'Error')) + } + }, +) + +// 导出实例 +export default service diff --git a/smallboot-web/src/utils/scroll-to.ts b/smallboot-web/src/utils/scroll-to.ts new file mode 100644 index 0000000..615ae4f --- /dev/null +++ b/smallboot-web/src/utils/scroll-to.ts @@ -0,0 +1,65 @@ +const easeInOutQuad = (t: number, b: number, c: number, d: number) => { + t /= d / 2 + if (t < 1) { + return (c / 2) * t * t + b + } + t-- + return (-c / 2) * (t * (t - 2) - 1) + b +} + +// requestAnimationFrame for Smart Animating http://goo.gl/sx5sts +const requestAnimFrame = (function () { + return ( + window.requestAnimationFrame || + (window as any).webkitRequestAnimationFrame || + (window as any).mozRequestAnimationFrame || + function (callback) { + window.setTimeout(callback, 1000 / 60) + } + ) +})() + +/** + * Because it's so fucking difficult to detect the scrolling element, just move them all + * @param {number} amount + */ +const move = (amount: number) => { + document.documentElement.scrollTop = amount + ;(document.body.parentNode as HTMLElement).scrollTop = amount + document.body.scrollTop = amount +} + +const position = () => { + return document.documentElement.scrollTop || (document.body.parentNode as HTMLElement).scrollTop || document.body.scrollTop +} + +/** + * @param {number} to + * @param {number} duration + * @param {Function} callback + */ +export const scrollTo = (to: number, duration: number, callback?: any) => { + const start = position() + const change = to - start + const increment = 20 + let currentTime = 0 + duration = typeof duration === 'undefined' ? 500 : duration + const animateScroll = function () { + // increment the time + currentTime += increment + // find the value with the quadratic in-out easing function + const val = easeInOutQuad(currentTime, start, change, duration) + // move the document.body + move(val) + // do the animation unless its over + if (currentTime < duration) { + requestAnimFrame(animateScroll) + } else { + if (callback && typeof callback === 'function') { + // the animation is done so lets callback + callback() + } + } + } + animateScroll() +} diff --git a/smallboot-web/src/utils/storage.ts b/smallboot-web/src/utils/storage.ts new file mode 100644 index 0000000..6a272b4 --- /dev/null +++ b/smallboot-web/src/utils/storage.ts @@ -0,0 +1,37 @@ +/** + * window.localStorage => 浏览器永久存储,用于长久保存整个网站的数据,保存的数据没有过期时间,直到手动去删除。 + */ +export const localStorage = { + set(key: string, val: any) { + window.localStorage.setItem(key, JSON.stringify(val)) + }, + get(key: string) { + const json: any = window.localStorage.getItem(key) + return JSON.parse(json) + }, + remove(key: string) { + window.localStorage.removeItem(key) + }, + clear() { + window.localStorage.clear() + }, +} + +/** + * window.sessionStorage => 浏览器本地存储,数据保存在当前会话中,在关闭窗口或标签页之后将会删除这些数据。 + */ +export const sessionStorage = { + set(key: string, val: any) { + window.sessionStorage.setItem(key, JSON.stringify(val)) + }, + get(key: string) { + const json: any = window.sessionStorage.getItem(key) + return JSON.parse(json) + }, + remove(key: string) { + window.sessionStorage.removeItem(key) + }, + clear() { + window.sessionStorage.clear() + }, +} diff --git a/smallboot-web/src/utils/validate.ts b/smallboot-web/src/utils/validate.ts new file mode 100644 index 0000000..eee85c5 --- /dev/null +++ b/smallboot-web/src/utils/validate.ts @@ -0,0 +1,4 @@ +export function isExternal(path: string) { + const isExternal = /^(https?:|http?:|mailto:|tel:)/.test(path) + return isExternal +} diff --git a/smallboot-web/src/views/dashboard/index.vue b/smallboot-web/src/views/dashboard/index.vue new file mode 100644 index 0000000..839d8c1 --- /dev/null +++ b/smallboot-web/src/views/dashboard/index.vue @@ -0,0 +1,16 @@ + + + diff --git a/smallboot-web/src/views/error-page/401.vue b/smallboot-web/src/views/error-page/401.vue new file mode 100644 index 0000000..b9a5f89 --- /dev/null +++ b/smallboot-web/src/views/error-page/401.vue @@ -0,0 +1,4 @@ +\ + diff --git a/smallboot-web/src/views/error-page/404.vue b/smallboot-web/src/views/error-page/404.vue new file mode 100644 index 0000000..07e8696 --- /dev/null +++ b/smallboot-web/src/views/error-page/404.vue @@ -0,0 +1,3 @@ + diff --git a/smallboot-web/src/views/login/index.vue b/smallboot-web/src/views/login/index.vue new file mode 100644 index 0000000..7bf6eea --- /dev/null +++ b/smallboot-web/src/views/login/index.vue @@ -0,0 +1,370 @@ + + + + + + + diff --git a/smallboot-web/src/views/system/dict/edit-dict-type.vue b/smallboot-web/src/views/system/dict/edit-dict-type.vue new file mode 100644 index 0000000..da23632 --- /dev/null +++ b/smallboot-web/src/views/system/dict/edit-dict-type.vue @@ -0,0 +1,67 @@ + + + diff --git a/smallboot-web/src/views/system/dict/edit-dict.vue b/smallboot-web/src/views/system/dict/edit-dict.vue new file mode 100644 index 0000000..a21850d --- /dev/null +++ b/smallboot-web/src/views/system/dict/edit-dict.vue @@ -0,0 +1,83 @@ + + + diff --git a/smallboot-web/src/views/system/dict/index.vue b/smallboot-web/src/views/system/dict/index.vue new file mode 100644 index 0000000..e189d63 --- /dev/null +++ b/smallboot-web/src/views/system/dict/index.vue @@ -0,0 +1,138 @@ + + + diff --git a/smallboot-web/src/views/system/menu/edit.vue b/smallboot-web/src/views/system/menu/edit.vue new file mode 100644 index 0000000..705bdc3 --- /dev/null +++ b/smallboot-web/src/views/system/menu/edit.vue @@ -0,0 +1,167 @@ + + + diff --git a/smallboot-web/src/views/system/menu/index.vue b/smallboot-web/src/views/system/menu/index.vue new file mode 100644 index 0000000..73a1156 --- /dev/null +++ b/smallboot-web/src/views/system/menu/index.vue @@ -0,0 +1,189 @@ + + + diff --git a/smallboot-web/src/views/system/personal-center/index.vue b/smallboot-web/src/views/system/personal-center/index.vue new file mode 100644 index 0000000..888a40e --- /dev/null +++ b/smallboot-web/src/views/system/personal-center/index.vue @@ -0,0 +1,154 @@ + + + + + diff --git a/smallboot-web/src/views/system/role/form.vue b/smallboot-web/src/views/system/role/form.vue new file mode 100644 index 0000000..e82c1e8 --- /dev/null +++ b/smallboot-web/src/views/system/role/form.vue @@ -0,0 +1,193 @@ + + + diff --git a/smallboot-web/src/views/system/role/list.vue b/smallboot-web/src/views/system/role/list.vue new file mode 100644 index 0000000..b57de19 --- /dev/null +++ b/smallboot-web/src/views/system/role/list.vue @@ -0,0 +1,103 @@ + + + diff --git a/smallboot-web/src/views/system/user/index.vue b/smallboot-web/src/views/system/user/index.vue new file mode 100644 index 0000000..23bd53b --- /dev/null +++ b/smallboot-web/src/views/system/user/index.vue @@ -0,0 +1,205 @@ + + + diff --git a/smallboot-web/src/views/system/user/rolePermission.vue b/smallboot-web/src/views/system/user/rolePermission.vue new file mode 100644 index 0000000..2110bad --- /dev/null +++ b/smallboot-web/src/views/system/user/rolePermission.vue @@ -0,0 +1,58 @@ + + + diff --git a/smallboot-web/src/views/test/index.vue b/smallboot-web/src/views/test/index.vue new file mode 100644 index 0000000..bcd8100 --- /dev/null +++ b/smallboot-web/src/views/test/index.vue @@ -0,0 +1,50 @@ + + + diff --git a/smallboot-web/src/vite-env.d.ts b/smallboot-web/src/vite-env.d.ts new file mode 100644 index 0000000..97aee98 --- /dev/null +++ b/smallboot-web/src/vite-env.d.ts @@ -0,0 +1,14 @@ +/// + +declare module '*.vue' { + import type { DefineComponent } from 'vue' + const component: DefineComponent<{}, {}, any> + export default component +} + +import { Store } from '@/store' +declare module '@vue/runtime-core' { + interface ComponentCustomProperties { + $store: Store + } +} diff --git a/smallboot-web/tsconfig.json b/smallboot-web/tsconfig.json new file mode 100644 index 0000000..f9131b3 --- /dev/null +++ b/smallboot-web/tsconfig.json @@ -0,0 +1,30 @@ +{ + "compilerOptions": { + "target": "ESNext", + "useDefineForClassFields": true, + "module": "ESNext", + "moduleResolution": "Node", + "strict": true, + "jsx": "preserve", + "sourceMap": true, + "resolveJsonModule": true, + "isolatedModules": true, + "esModuleInterop": true, + "lib": ["ESNext", "DOM"], + "skipLibCheck": true, + "baseUrl": ".", + "paths": { + "@/*": ["src/*"] + }, + // Volar支持,指定全局组件类型 + "types": ["element-plus/global"], + // 允许编译器编译JS文件 + "allowJs": true, + // 允许在JS文件中报错,通常与allowJS一起使用 + // "checkJs": true + "allowSyntheticDefaultImports": true // 允许引入没有默认导出的模块 + }, + "include": ["src/**/*"], + "exclude": ["node_modules"], + "references": [{ "path": "./tsconfig.node.json" }] +} diff --git a/smallboot-web/tsconfig.node.json b/smallboot-web/tsconfig.node.json new file mode 100644 index 0000000..9d31e2a --- /dev/null +++ b/smallboot-web/tsconfig.node.json @@ -0,0 +1,9 @@ +{ + "compilerOptions": { + "composite": true, + "module": "ESNext", + "moduleResolution": "Node", + "allowSyntheticDefaultImports": true + }, + "include": ["vite.config.ts"] +} diff --git a/smallboot-web/vite.config.ts b/smallboot-web/vite.config.ts new file mode 100644 index 0000000..7016182 --- /dev/null +++ b/smallboot-web/vite.config.ts @@ -0,0 +1,45 @@ +import { defineConfig, loadEnv } from 'vite' +import vue from '@vitejs/plugin-vue' +import * as path from 'path' + +export default defineConfig(({ mode }) => { + // 获取`.env`环境配置文件 + const env = loadEnv(mode, process.cwd()) + + return { + plugins: [vue()], + // 反向代理解决跨域问题 + server: { + // host: 'localhost', // 只能本地访问 + host: '0.0.0.0', // 局域网别人也可访问 + port: Number(env.VITE_APP_PORT), + // 运行时自动打开浏览器 + // open: true, + proxy: { + [env.VITE_APP_BASE_API]: { + target: env.VITE_APP_SERVICE_API, + changeOrigin: true, + rewrite: (path) => path.replace(new RegExp('^' + env.VITE_APP_BASE_API), ''), + }, + }, + }, + resolve: { + // 配置路径别名 + alias: [ + // @代替src + { + find: '@', + replacement: path.resolve('./src'), + }, + ], + }, + // 引入scss全局变量 + css: { + preprocessorOptions: { + scss: { + additionalData: `@import "@/styles/app-theme.scss";`, + }, + }, + }, + } +})