Commit 5091da53 authored by tanyang's avatar tanyang

20局项目初始化

parents
Pipeline #380 failed with stages

Too many changes to show.

To preserve performance only 1000 of 1000+ files are displayed.

######################################################################
# Build Tools
.gradle
/build/
!gradle/wrapper/gradle-wrapper.jar
target/
!.mvn/wrapper/maven-wrapper.jar
######################################################################
# IDE
### STS ###
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
application-pro.yml
application-dev.yml
### IntelliJ IDEA ###
.idea
*.iws
*.iml
*.ipr
### NetBeans ###
nbproject/private/
build/*
nbbuild/
dist/
nbdist/
.nb-gradle/
######################################################################
# Others
*.log
*.xml.versionsBackup
*.swp
!*/build/*.java
!*/build/*.html
!*/build/*.xml
**nacos/logs/
**nacos/bin/logs/
**nacos/bin/work/
**nacos/data/derby-data/log/
**nacos/data/derby-data/tmp/
**nacos/data/derby-data/seg0/
*.dat
*.heartBeat.txt
/dsk-open
/catalina.base_IS_UNDEFINED
/*.rar
- - -
## 平台简介
[![底层框架RuoYi-Vue-Plus]](https://plus-doc.dromara.org/#/ruoyi-vue-plus/home)
[![使用说明文档]](https://plus-doc.dromara.org/#/ruoyi-vue-plus/home)
> 本项目基于RuoYi-Vue-Plus 4.x 改造而成,新增了多租户相关功能,使用说明请参考RuoYi-Vue-Plus 4.x相关文档。
> 前端项目是以原ruoyi-ui基础的基础上兼容本项目后端接口开发的
> 后端开发说明: 业务相关代码全部放到dsk-module下的项目里面,非必要不要改动其他模块代码
>
>
>
version: "3"
services:
dsk-operate-sys:
image: swr.cn-north-4.myhuaweicloud.com/dsk/java:8u111-jdk-alpine-creditreport
container_name: dsk-operate-sys
network_mode: host
restart: always
working_dir: /app
environment:
TZ: Asia/Shanghai
volumes:
- ./dsk-operate-sys:/app
- /etc/localtime:/etc/localtime
command: "java -jar dsk-operate-sys.jar"
nginx:
image: nginx:perl
container_name: nginx
restart: always
network_mode: host
working_dir: /app
environment:
TZ: Asia/Shanghai
volumes:
- /etc/localtime:/etc/localtime
- ./dsk-operate-ui/dist/:/www/szh/web/dist/
- ./dsk-operate-ui/operate.conf:/etc/nginx/conf.d/operate.conf
- ./dsk-operate-ui/log:/var/log/nginx
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>dsk-operate-sys</artifactId>
<groupId>com.dsk</groupId>
<version>4.8.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<packaging>jar</packaging>
<artifactId>dsk-admin</artifactId>
<description>
web服务入口
</description>
<dependencies>
<!-- spring-boot-devtools -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<optional>true</optional> <!-- 表示依赖不会传递 -->
</dependency>
<!-- Mysql驱动包 -->
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
</dependency>
<!-- &lt;!&ndash; Oracle &ndash;&gt;-->
<!-- <dependency>-->
<!-- <groupId>com.oracle.database.jdbc</groupId>-->
<!-- <artifactId>ojdbc8</artifactId>-->
<!-- </dependency>-->
<!-- &lt;!&ndash; PostgreSql &ndash;&gt;-->
<!-- <dependency>-->
<!-- <groupId>org.postgresql</groupId>-->
<!-- <artifactId>postgresql</artifactId>-->
<!-- </dependency>-->
<!-- &lt;!&ndash; SqlServer &ndash;&gt;-->
<!-- <dependency>-->
<!-- <groupId>com.microsoft.sqlserver</groupId>-->
<!-- <artifactId>mssql-jdbc</artifactId>-->
<!-- </dependency>-->
<!-- 核心模块-->
<dependency>
<groupId>com.dsk</groupId>
<artifactId>dsk-framework</artifactId>
</dependency>
<dependency>
<groupId>com.dsk</groupId>
<artifactId>dsk-system</artifactId>
</dependency>
<dependency>
<groupId>com.dsk</groupId>
<artifactId>dsk-biz-api</artifactId>
</dependency>
<dependency>
<groupId>com.dsk</groupId>
<artifactId>dsk-biz-bi</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- skywalking 整合 logback -->
<!-- <dependency>-->
<!-- <groupId>org.apache.skywalking</groupId>-->
<!-- <artifactId>apm-toolkit-logback-1.x</artifactId>-->
<!-- <version>${与你的agent探针版本保持一致}</version>-->
<!-- </dependency>-->
<!-- <dependency>-->
<!-- <groupId>org.apache.skywalking</groupId>-->
<!-- <artifactId>apm-toolkit-trace</artifactId>-->
<!-- <version>${与你的agent探针版本保持一致}</version>-->
<!-- </dependency>-->
</dependencies>
<build>
<finalName>dsk-operate-sys</finalName>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>${spring-boot.version}</version>
<configuration>
<fork>true</fork> <!-- 如果没有该配置,devtools不会生效 -->
</configuration>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<version>3.2.2</version>
<configuration>
<failOnMissingWebXml>false</failOnMissingWebXml>
<warName>${project.artifactId}</warName>
</configuration>
</plugin>
</plugins>
</build>
</project>
package com.dsk;
import cn.dev33.satoken.secure.BCrypt;
import com.google.api.client.util.SecurityUtils;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.metrics.buffering.BufferingApplicationStartup;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.annotation.EnableScheduling;
/**
* 启动程序
*
* @author ruoyi
*/
@SpringBootApplication
@EnableScheduling
@EnableAsync(proxyTargetClass = true)
public class DskOperateSysApplication {
public static void main(String[] args) {
System.setProperty("spring.devtools.restart.enabled", "false");
SpringApplication application = new SpringApplication(DskOperateSysApplication.class);
application.setApplicationStartup(new BufferingApplicationStartup(2048));
application.run(args);
System.out.println("(♥◠‿◠)ノ゙ dsk-operate-sys启动成功 ლ(´ڡ`ლ)゙");
}
}
package com.dsk;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
/**
* web容器中进行部署
*
* @author ruoyi
*/
public class DskOperateSysServletInitializer extends SpringBootServletInitializer {
@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
return application.sources(DskOperateSysApplication.class);
}
}
package com.dsk.web.controller.common;
import cn.dev33.satoken.annotation.SaIgnore;
import cn.hutool.captcha.AbstractCaptcha;
import cn.hutool.captcha.generator.CodeGenerator;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.RandomUtil;
import com.dsk.common.constant.CacheConstants;
import com.dsk.common.constant.Constants;
import com.dsk.common.constant.GlobalConstants;
import com.dsk.common.core.domain.R;
import com.dsk.common.enums.CaptchaType;
import com.dsk.common.exception.user.CaptchaException;
import com.dsk.common.exception.user.CaptchaExpireException;
import com.dsk.common.helper.LoginHelper;
import com.dsk.common.utils.DingTalkUtil;
import com.dsk.common.utils.MessageUtils;
import com.dsk.common.utils.StringUtils;
import com.dsk.common.utils.email.MailUtils;
import com.dsk.common.utils.redis.RedisUtils;
import com.dsk.common.utils.reflect.ReflectUtils;
import com.dsk.common.utils.spring.SpringUtils;
import com.dsk.framework.config.properties.CaptchaProperties;
import com.dsk.framework.config.properties.MailProperties;
import com.dsk.system.domain.SysUser;
import com.dsk.system.mapper.SysUserMapper;
import com.dsk.system.service.ISysConfigService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.dromara.sms4j.api.SmsBlend;
import org.dromara.sms4j.api.entity.SmsResponse;
import org.dromara.sms4j.core.factory.SmsFactory;
import org.dromara.sms4j.provider.enumerate.SupplierType;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.validation.constraints.NotBlank;
import java.time.Duration;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* 验证码操作处理
*
* @author Lion Li
*/
@SaIgnore
@Slf4j
@Validated
@RequiredArgsConstructor
@RestController
public class CaptchaController {
private final CaptchaProperties captchaProperties;
private final ISysConfigService configService;
private final MailProperties mailProperties;
private final SysUserMapper userMapper;
@Value("${captcha.enable}")
private boolean captchaEnabled;
/**
* 发送短信验证码
*
* @param phonenumber 用户手机号
*/
@GetMapping("/captchaSms")
public R<Void> smsCaptcha(@NotBlank(message = "{user.phonenumber.not.blank}") String phonenumber,String uid,String captchaCode,String templateId) {
String key = GlobalConstants.CAPTCHA_CODE_KEY + phonenumber;
// 验证码开关
if (captchaEnabled) {
validateCaptcha(phonenumber, captchaCode, uid);
}
String code = RandomUtil.randomNumbers(6);
RedisUtils.setCacheObject(key, code, Duration.ofMinutes(Constants.CAPTCHA_EXPIRATION));
// 验证码模板id 自行处理 (查数据库或写死均可)
LinkedHashMap<String, String> map = new LinkedHashMap<>(1);
map.put("code", code);
SmsBlend smsBlend = SmsFactory.createSmsBlend(SupplierType.ALIBABA);
SmsResponse smsResponse = smsBlend.sendMessage(phonenumber, templateId, map);
if (!"OK".equals(smsResponse.getCode())) {
log.error("验证码短信发送异常 => {}", smsResponse);
return R.fail(smsResponse.getMessage());
}
//DingTalkUtil.sendDingTalkMsg("短信通知:"+phonenumber+"登录验证码:"+code+",请尽快输入验证码完成验证,勿将验证码透露给他人,如非本人操作,请忽略本条短信。");
return R.ok();
}
private void validateCaptcha(String phonenumber, String captchaCode, String uuid) {
String verifyKey = GlobalConstants.CAPTCHA_CODE_KEY + StringUtils.defaultString(uuid, "");
String captcha = RedisUtils.getCacheObject(verifyKey);
RedisUtils.deleteObject(verifyKey);
if (captcha == null) {
// recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.expire"));
throw new CaptchaExpireException();
}
if (!captchaCode.equalsIgnoreCase(captcha)) {
// recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.error"));
throw new CaptchaException();
}
}
/**
* 发送短信验证码
*/
@GetMapping("/getTempSmsCode")
public R<Void> getTempSmsCode() {
SysUser sysUser = userMapper.selectUserById(LoginHelper.getUserId());
String phonenumber = sysUser.getPhonenumber();
String key = GlobalConstants.CAPTCHA_CODE_KEY + phonenumber;
String code = RandomUtil.randomNumbers(6);
RedisUtils.setCacheObject(key, code, Duration.ofMinutes(10));
// 验证码模板id 自行处理 (查数据库或写死均可)
LinkedHashMap<String, String> map = new LinkedHashMap<>(1);
map.put("code", code);
SmsBlend smsBlend = SmsFactory.createSmsBlend(SupplierType.ALIBABA);
SmsResponse smsResponse = smsBlend.sendMessage(phonenumber, "SMS_181860796", map);
if (!"OK".equals(smsResponse.getCode())) {
log.error("验证码短信发送异常 => {}", smsResponse);
return R.fail(smsResponse.getMessage());
}
//DingTalkUtil.sendDingTalkMsg("短信通知:"+phonenumber+"登录验证码:"+code+",请尽快输入验证码完成验证,勿将验证码透露给他人,如非本人操作,请忽略本条短信。");
return R.ok();
}
/**
* 校验短信验证码
*
* @param smsCode 验证码
*/
@PostMapping("/checkSmsCode")
public R<Boolean> checkSmsCode(String smsCode) {
SysUser sysUser = userMapper.selectUserById(LoginHelper.getUserId());
String key = GlobalConstants.CAPTCHA_CODE_KEY + sysUser.getPhonenumber();
String code = RedisUtils.getCacheObject(key);
if (StringUtils.isBlank(code)) {
throw new CaptchaExpireException();
}
if (code.equals(smsCode)) {
RedisUtils.deleteObject(key);
return R.ok("校验成功");
}
return R.fail("验证码错误");
}
/**
* 发送邮箱验证码
*
* @param email 邮箱
*/
@GetMapping("/captchaEmail")
public R<Void> emailCode(@NotBlank(message = "{user.email.not.blank}") String email) {
if (!mailProperties.getEnabled()) {
return R.fail("当前系统没有开启邮箱功能!");
}
String key = GlobalConstants.CAPTCHA_CODE_KEY + email;
String code = RandomUtil.randomNumbers(4);
RedisUtils.setCacheObject(key, code, Duration.ofMinutes(Constants.CAPTCHA_EXPIRATION));
try {
MailUtils.sendText(email, "登录验证码", "您本次验证码为:" + code + ",有效性为" + Constants.CAPTCHA_EXPIRATION + "分钟,请尽快填写。");
} catch (Exception e) {
log.error("验证码短信发送异常 => {}", e.getMessage());
return R.fail(e.getMessage());
}
return R.ok();
}
/**
* 生成验证码
*/
@GetMapping("/captchaImage")
public R<Map<String, Object>> getCode() {
Map<String, Object> ajax = new HashMap<>();
boolean captchaEnabled = captchaProperties.getEnable();
ajax.put("captchaEnabled", captchaEnabled);
if (!captchaEnabled) {
return R.ok(ajax);
}
// 保存验证码信息
String uuid = IdUtil.simpleUUID();
String verifyKey = GlobalConstants.CAPTCHA_CODE_KEY + uuid;
// 生成验证码
CaptchaType captchaType = captchaProperties.getType();
boolean isMath = CaptchaType.MATH == captchaType;
Integer length = isMath ? captchaProperties.getNumberLength() : captchaProperties.getCharLength();
CodeGenerator codeGenerator = ReflectUtils.newInstance(captchaType.getClazz(), length);
AbstractCaptcha captcha = SpringUtils.getBean(captchaProperties.getCategory().getClazz());
captcha.setGenerator(codeGenerator);
captcha.createCode();
String code = captcha.getCode();
if (isMath) {
ExpressionParser parser = new SpelExpressionParser();
Expression exp = parser.parseExpression(StringUtils.remove(code, "="));
code = exp.getValue(String.class);
}
log.info("图形验证码:" + code);
RedisUtils.setCacheObject(verifyKey, code, Duration.ofMinutes(Constants.CAPTCHA_EXPIRATION));
ajax.put("uuid", uuid);
ajax.put("img", captcha.getImageBase64());
return R.ok(ajax);
}
}
package com.dsk.web.controller.monitor;
import cn.dev33.satoken.annotation.SaCheckPermission;
import cn.hutool.core.collection.CollUtil;
import com.dsk.common.constant.CacheConstants;
import com.dsk.common.constant.CacheNames;
import com.dsk.common.constant.GlobalConstants;
import com.dsk.common.core.domain.R;
import com.dsk.common.utils.JsonUtils;
import com.dsk.common.utils.StringUtils;
import com.dsk.common.utils.redis.CacheUtils;
import com.dsk.common.utils.redis.RedisUtils;
import com.dsk.system.domain.SysCache;
import lombok.RequiredArgsConstructor;
import org.redisson.spring.data.connection.RedissonConnectionFactory;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.web.bind.annotation.*;
import java.util.*;
import java.util.stream.Collectors;
/**
* 缓存监控
*
* @author Lion Li
*/
@RequiredArgsConstructor
@RestController
@RequestMapping("/monitor/cache")
public class CacheController {
private final RedissonConnectionFactory connectionFactory;
private final static List<SysCache> CACHES = new ArrayList<>();
static {
CACHES.add(new SysCache(CacheConstants.ONLINE_TOKEN_KEY, "在线用户"));
CACHES.add(new SysCache(CacheNames.SYS_CONFIG, "配置信息"));
CACHES.add(new SysCache(CacheNames.SYS_DICT, "数据字典"));
CACHES.add(new SysCache(GlobalConstants.CAPTCHA_CODE_KEY, "验证码"));
CACHES.add(new SysCache(GlobalConstants.REPEAT_SUBMIT_KEY, "防重提交"));
CACHES.add(new SysCache(GlobalConstants.RATE_LIMIT_KEY, "限流处理"));
CACHES.add(new SysCache(GlobalConstants.SYS_OSS_CONFIG, "OSS配置"));
CACHES.add(new SysCache(GlobalConstants.PWD_ERR_CNT_KEY, "密码错误次数"));
}
/**
* 获取缓存监控列表
*/
@SaCheckPermission("monitor:cache:list")
@GetMapping()
public R<Map<String, Object>> getInfo() throws Exception {
RedisConnection connection = connectionFactory.getConnection();
Properties info = connection.info();
Properties commandStats = connection.info("commandstats");
Long dbSize = connection.dbSize();
Map<String, Object> result = new HashMap<>(3);
result.put("info", info);
result.put("dbSize", dbSize);
List<Map<String, String>> pieList = new ArrayList<>();
if (commandStats != null) {
commandStats.stringPropertyNames().forEach(key -> {
Map<String, String> data = new HashMap<>(2);
String property = commandStats.getProperty(key);
data.put("name", StringUtils.removeStart(key, "cmdstat_"));
data.put("value", StringUtils.substringBetween(property, "calls=", ",usec"));
pieList.add(data);
});
}
result.put("commandStats", pieList);
return R.ok(result);
}
/**
* 获取缓存监控缓存名列表
*/
@SaCheckPermission("monitor:cache:list")
@GetMapping("/getNames")
public R<List<SysCache>> cache() {
return R.ok(CACHES);
}
/**
* 获取缓存监控Key列表
*
* @param cacheName 缓存名
*/
@SaCheckPermission("monitor:cache:list")
@GetMapping("/getKeys/{cacheName}")
public R<Collection<String>> getCacheKeys(@PathVariable String cacheName) {
Collection<String> cacheKeys = new HashSet<>(0);
if (isCacheNames(cacheName)) {
Set<Object> keys = CacheUtils.keys(cacheName);
if (CollUtil.isNotEmpty(keys)) {
cacheKeys = keys.stream().map(Object::toString).collect(Collectors.toList());
}
} else {
cacheKeys = RedisUtils.keys(cacheName + "*");
}
return R.ok(cacheKeys);
}
/**
* 获取缓存监控缓存值详情
*
* @param cacheName 缓存名
* @param cacheKey 缓存key
*/
@SaCheckPermission("monitor:cache:list")
@GetMapping("/getValue/{cacheName}/{cacheKey}")
public R<SysCache> getCacheValue(@PathVariable String cacheName, @PathVariable String cacheKey) {
Object cacheValue;
if (isCacheNames(cacheName)) {
cacheValue = CacheUtils.get(cacheName, cacheKey);
} else {
cacheValue = RedisUtils.getCacheObject(cacheKey);
}
SysCache sysCache = new SysCache(cacheName, cacheKey, JsonUtils.toJsonString(cacheValue));
return R.ok(sysCache);
}
/**
* 清理缓存监控缓存名
*
* @param cacheName 缓存名
*/
@SaCheckPermission("monitor:cache:list")
@DeleteMapping("/clearCacheName/{cacheName}")
public R<Void> clearCacheName(@PathVariable String cacheName) {
if (isCacheNames(cacheName)) {
CacheUtils.clear(cacheName);
} else {
RedisUtils.deleteKeys(cacheName + "*");
}
return R.ok();
}
/**
* 清理缓存监控Key
*
* @param cacheKey key名
*/
@SaCheckPermission("monitor:cache:list")
@DeleteMapping("/clearCacheKey/{cacheName}/{cacheKey}")
public R<Void> clearCacheKey(@PathVariable String cacheName, @PathVariable String cacheKey) {
if (isCacheNames(cacheName)) {
CacheUtils.evict(cacheName, cacheKey);
} else {
RedisUtils.deleteObject(cacheKey);
}
return R.ok();
}
/**
* 清理全部缓存监控
*/
@SaCheckPermission("monitor:cache:list")
@DeleteMapping("/clearCacheAll")
public R<Void> clearCacheAll() {
RedisUtils.deleteKeys("*");
return R.ok();
}
private boolean isCacheNames(String cacheName) {
return !StringUtils.contains(cacheName, ":");
}
}
package com.dsk.web.controller.monitor;
import cn.dev33.satoken.annotation.SaCheckPermission;
import com.dsk.common.annotation.Log;
import com.dsk.common.constant.CacheConstants;
import com.dsk.common.constant.GlobalConstants;
import com.dsk.common.core.controller.BaseController;
import com.dsk.common.core.domain.PageQuery;
import com.dsk.common.core.domain.R;
import com.dsk.common.core.page.TableDataInfo;
import com.dsk.common.enums.BusinessType;
import com.dsk.common.utils.poi.ExcelUtil;
import com.dsk.common.utils.redis.RedisUtils;
import com.dsk.system.domain.SysLogininfor;
import com.dsk.system.service.ISysLogininforService;
import lombok.RequiredArgsConstructor;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletResponse;
import java.util.List;
/**
* 系统访问记录
*
* @author Lion Li
*/
@Validated
@RequiredArgsConstructor
@RestController
@RequestMapping("/monitor/logininfor")
public class SysLogininforController extends BaseController {
private final ISysLogininforService logininforService;
/**
* 获取系统访问记录列表
*/
@SaCheckPermission("monitor:logininfor:list")
@GetMapping("/list")
public TableDataInfo<SysLogininfor> list(SysLogininfor logininfor, PageQuery pageQuery) {
return logininforService.selectPageLogininforList(logininfor, pageQuery);
}
/**
* 导出系统访问记录列表
*/
@Log(title = "登录日志", businessType = BusinessType.EXPORT)
@SaCheckPermission("monitor:logininfor:export")
@PostMapping("/export")
public void export(SysLogininfor logininfor, HttpServletResponse response) {
List<SysLogininfor> list = logininforService.selectLogininforList(logininfor);
ExcelUtil.exportExcel(list, "登录日志", SysLogininfor.class, response);
}
/**
* 批量删除登录日志
* @param infoIds 日志ids
*/
@SaCheckPermission("monitor:logininfor:remove")
@Log(title = "登录日志", businessType = BusinessType.DELETE)
@DeleteMapping("/{infoIds}")
public R<Void> remove(@PathVariable Long[] infoIds) {
return toAjax(logininforService.deleteLogininforByIds(infoIds));
}
/**
* 清理系统访问记录
*/
@SaCheckPermission("monitor:logininfor:remove")
@Log(title = "登录日志", businessType = BusinessType.CLEAN)
@DeleteMapping("/clean")
public R<Void> clean() {
logininforService.cleanLogininfor();
return R.ok();
}
@SaCheckPermission("monitor:logininfor:unlock")
@Log(title = "账户解锁", businessType = BusinessType.OTHER)
@GetMapping("/unlock/{userName}")
public R<Void> unlock(@PathVariable("userName") String userName) {
String loginName = GlobalConstants.PWD_ERR_CNT_KEY + userName;
if (RedisUtils.hasKey(loginName)) {
RedisUtils.deleteObject(loginName);
}
return R.ok();
}
}
package com.dsk.web.controller.monitor;
import cn.dev33.satoken.annotation.SaCheckPermission;
import com.dsk.common.annotation.Log;
import com.dsk.common.core.controller.BaseController;
import com.dsk.common.core.domain.PageQuery;
import com.dsk.common.core.domain.R;
import com.dsk.common.core.page.TableDataInfo;
import com.dsk.common.enums.BusinessType;
import com.dsk.common.utils.poi.ExcelUtil;
import com.dsk.system.domain.SysOperLog;
import com.dsk.system.service.ISysOperLogService;
import lombok.RequiredArgsConstructor;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletResponse;
import java.util.List;
/**
* 操作日志记录
*
* @author Lion Li
*/
@Validated
@RequiredArgsConstructor
@RestController
@RequestMapping("/monitor/operlog")
public class SysOperlogController extends BaseController {
private final ISysOperLogService operLogService;
/**
* 获取操作日志记录列表
*/
@SaCheckPermission("monitor:operlog:list")
@GetMapping("/list")
public TableDataInfo<SysOperLog> list(SysOperLog operLog, PageQuery pageQuery) {
return operLogService.selectPageOperLogList(operLog, pageQuery);
}
/**
* 导出操作日志记录列表
*/
@Log(title = "操作日志", businessType = BusinessType.EXPORT)
@SaCheckPermission("monitor:operlog:export")
@PostMapping("/export")
public void export(SysOperLog operLog, HttpServletResponse response) {
List<SysOperLog> list = operLogService.selectOperLogList(operLog);
ExcelUtil.exportExcel(list, "操作日志", SysOperLog.class, response);
}
/**
* 批量删除操作日志记录
* @param operIds 日志ids
*/
@Log(title = "操作日志", businessType = BusinessType.DELETE)
@SaCheckPermission("monitor:operlog:remove")
@DeleteMapping("/{operIds}")
public R<Void> remove(@PathVariable Long[] operIds) {
return toAjax(operLogService.deleteOperLogByIds(operIds));
}
/**
* 清理操作日志记录
*/
@Log(title = "操作日志", businessType = BusinessType.CLEAN)
@SaCheckPermission("monitor:operlog:remove")
@DeleteMapping("/clean")
public R<Void> clean() {
operLogService.cleanOperLog();
return R.ok();
}
}
package com.dsk.web.controller.monitor;
import cn.dev33.satoken.annotation.SaCheckPermission;
import cn.dev33.satoken.exception.NotLoginException;
import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.core.bean.BeanUtil;
import com.dsk.common.annotation.Log;
import com.dsk.common.constant.CacheConstants;
import com.dsk.common.core.controller.BaseController;
import com.dsk.common.core.domain.R;
import com.dsk.common.core.domain.dto.UserOnlineDTO;
import com.dsk.common.core.page.TableDataInfo;
import com.dsk.common.enums.BusinessType;
import com.dsk.common.utils.StreamUtils;
import com.dsk.common.utils.StringUtils;
import com.dsk.common.utils.redis.RedisUtils;
import com.dsk.system.domain.SysUserOnline;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* 在线用户监控
*
* @author Lion Li
*/
@RequiredArgsConstructor
@RestController
@RequestMapping("/monitor/online")
public class SysUserOnlineController extends BaseController {
/**
* 获取在线用户监控列表
*
* @param ipaddr IP地址
* @param userName 用户名
*/
@SaCheckPermission("monitor:online:list")
@GetMapping("/list")
public TableDataInfo<SysUserOnline> list(String ipaddr, String userName) {
// 获取所有未过期的 token
List<String> keys = StpUtil.searchTokenValue("", 0, -1, false);
List<UserOnlineDTO> userOnlineDTOList = new ArrayList<>();
for (String key : keys) {
String token = StringUtils.substringAfterLast(key, ":");
// 如果已经过期则跳过
if (StpUtil.stpLogic.getTokenActiveTimeoutByToken(token) < -1) {
continue;
}
userOnlineDTOList.add(RedisUtils.getCacheObject(CacheConstants.ONLINE_TOKEN_KEY + token));
}
if (StringUtils.isNotEmpty(ipaddr) && StringUtils.isNotEmpty(userName)) {
userOnlineDTOList = StreamUtils.filter(userOnlineDTOList, userOnline ->
StringUtils.equals(ipaddr, userOnline.getIpaddr()) &&
StringUtils.equals(userName, userOnline.getUserName())
);
} else if (StringUtils.isNotEmpty(ipaddr)) {
userOnlineDTOList = StreamUtils.filter(userOnlineDTOList, userOnline ->
StringUtils.equals(ipaddr, userOnline.getIpaddr())
);
} else if (StringUtils.isNotEmpty(userName)) {
userOnlineDTOList = StreamUtils.filter(userOnlineDTOList, userOnline ->
StringUtils.equals(userName, userOnline.getUserName())
);
}
Collections.reverse(userOnlineDTOList);
userOnlineDTOList.removeAll(Collections.singleton(null));
List<SysUserOnline> userOnlineList = BeanUtil.copyToList(userOnlineDTOList, SysUserOnline.class);
return TableDataInfo.build(userOnlineList);
}
/**
* 强退用户
*
* @param tokenId token值
*/
@SaCheckPermission("monitor:online:forceLogout")
@Log(title = "在线用户", businessType = BusinessType.FORCE)
@DeleteMapping("/{tokenId}")
public R<Void> forceLogout(@PathVariable String tokenId) {
try {
StpUtil.kickoutByTokenValue(tokenId);
} catch (NotLoginException ignored) {
}
return R.ok();
}
}
package com.dsk.web.controller.system;
import cn.dev33.satoken.annotation.SaCheckLogin;
import cn.dev33.satoken.annotation.SaCheckPermission;
import com.dsk.common.annotation.Log;
import com.dsk.common.core.controller.BaseController;
import com.dsk.common.core.domain.PageQuery;
import com.dsk.common.core.domain.R;
import com.dsk.common.core.domain.model.DskAccessTokenVO;
import com.dsk.common.core.page.TableDataInfo;
import com.dsk.common.enums.BusinessType;
import com.dsk.common.tenant.helper.TenantHelper;
import com.dsk.common.utils.poi.ExcelUtil;
import com.dsk.system.domain.SysConfig;
import com.dsk.system.service.ISysConfigService;
import lombok.RequiredArgsConstructor;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletResponse;
import java.util.List;
/**
* 参数配置 信息操作处理
*
* @author Lion Li
*/
@Validated
@RequiredArgsConstructor
@RestController
@RequestMapping("/system/config")
public class SysConfigController extends BaseController {
private final ISysConfigService configService;
/**
* 获取参数配置列表
*/
@SaCheckPermission("system:config:list")
@GetMapping("/list")
public TableDataInfo<SysConfig> list(SysConfig config, PageQuery pageQuery) {
return configService.selectPageConfigList(config, pageQuery);
}
/**
* 导出参数配置列表
*/
@Log(title = "参数管理", businessType = BusinessType.EXPORT)
@SaCheckPermission("system:config:export")
@PostMapping("/export")
public void export(SysConfig config, HttpServletResponse response) {
List<SysConfig> list = configService.selectConfigList(config);
ExcelUtil.exportExcel(list, "参数数据", SysConfig.class, response);
}
/**
* 根据参数编号获取详细信息
*
* @param configId 参数ID
*/
@SaCheckPermission("system:config:query")
@GetMapping(value = "/{configId}")
public R<SysConfig> getInfo(@PathVariable Long configId) {
return R.ok(configService.selectConfigById(configId));
}
/**
* 根据参数键名查询参数值
*
* @param configKey 参数Key
*/
@GetMapping(value = "/configKey/{configKey}")
public R<Void> getConfigKey(@PathVariable String configKey) {
return R.ok(configService.selectConfigByKey(configKey));
}
/**
* 新增参数配置
*/
@SaCheckPermission("system:config:add")
@Log(title = "参数管理", businessType = BusinessType.INSERT)
@PostMapping
public R<Void> add(@Validated @RequestBody SysConfig config) {
if (!configService.checkConfigKeyUnique(config)) {
return R.fail("新增参数'" + config.getConfigName() + "'失败,参数键名已存在");
}
configService.insertConfig(config);
return R.ok();
}
/**
* 新增租户参数配置
*/
@SaCheckPermission("system:config:tenant:add")
@Log(title = "参数管理", businessType = BusinessType.INSERT)
@PostMapping("/tenant/add")
public R<Void> tenantAdd(@Validated @RequestBody SysConfig config) {
TenantHelper.ignore(() -> {
configService.insertTenantConfig(config);
});
return R.ok();
}
/**
* 修改参数配置
*/
@SaCheckPermission("system:config:edit")
@Log(title = "参数管理", businessType = BusinessType.UPDATE)
@PutMapping
public R<Void> edit(@Validated @RequestBody SysConfig config) {
if (!configService.checkConfigKeyUnique(config)) {
return R.fail("修改参数'" + config.getConfigName() + "'失败,参数键名已存在");
}
configService.updateConfig(config);
return R.ok();
}
/**
* 根据参数键名修改参数配置
*/
@SaCheckPermission("system:config:edit")
@Log(title = "参数管理", businessType = BusinessType.UPDATE)
@PutMapping("/updateByKey")
public R<Void> updateByKey(@RequestBody SysConfig config) {
configService.updateConfig(config);
return R.ok();
}
/**
* 删除参数配置
*
* @param configIds 参数ID串
*/
@SaCheckPermission("system:config:remove")
@Log(title = "参数管理", businessType = BusinessType.DELETE)
@DeleteMapping("/{configIds}")
public R<Void> remove(@PathVariable Long[] configIds) {
configService.deleteConfigByIds(configIds);
return R.ok();
}
/**
* 刷新参数缓存
*/
@SaCheckPermission("system:config:remove")
@Log(title = "参数管理", businessType = BusinessType.CLEAN)
@DeleteMapping("/refreshCache")
public R<Void> refreshCache() {
configService.resetConfigCache();
return R.ok();
}
/**
* 获取大司空open 插件访问token
*/
@SaCheckLogin
@Log(title = "获取dsk访问token", businessType = BusinessType.OTHER)
@GetMapping("/dsk/accessToken")
public R<DskAccessTokenVO> dskAccessToken() {
DskAccessTokenVO dskAccessTokenVO = configService.getDskAccessToken();
return R.ok(dskAccessTokenVO);
}
}
package com.dsk.web.controller.system;
import cn.dev33.satoken.annotation.SaCheckPermission;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.convert.Convert;
import com.dsk.common.annotation.Log;
import com.dsk.common.constant.UserConstants;
import com.dsk.common.core.controller.BaseController;
import com.dsk.common.core.domain.R;
import com.dsk.system.domain.SysDept;
import com.dsk.common.enums.BusinessType;
import com.dsk.common.utils.StringUtils;
import com.dsk.system.domain.vo.SysDeptVo;
import com.dsk.system.service.ISysDeptService;
import lombok.RequiredArgsConstructor;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.util.List;
/**
* 部门信息
*
* @author Lion Li
*/
@Validated
@RequiredArgsConstructor
@RestController
@RequestMapping("/system/dept")
public class SysDeptController extends BaseController {
private final ISysDeptService deptService;
/**
* 获取部门列表
*/
@SaCheckPermission("system:dept:list")
@GetMapping("/list")
public R<List<SysDeptVo>> list(SysDept dept) {
List<SysDept> depts = deptService.selectDeptList(dept);
List<SysDeptVo> deptVos = BeanUtil.copyToList(depts, SysDeptVo.class);
deptVos.forEach(sysDeptVo -> {
//查询该部门及其子部门是否存在用户
sysDeptVo.setExistUsers(deptService.checkAllDeptsAndUsers(sysDeptVo.getDeptId()));
});
return R.ok(deptVos);
}
/**
* 查询部门列表(排除节点)
*
* @param deptId 部门ID
*/
@SaCheckPermission("system:dept:list")
@GetMapping("/list/exclude/{deptId}")
public R<List<SysDept>> excludeChild(@PathVariable(value = "deptId", required = false) Long deptId) {
List<SysDept> depts = deptService.selectDeptList(new SysDept());
depts.removeIf(d -> d.getDeptId().equals(deptId)
|| StringUtils.splitList(d.getAncestors()).contains(Convert.toStr(deptId)));
return R.ok(depts);
}
/**
* 根据部门编号获取详细信息
*
* @param deptId 部门ID
*/
@SaCheckPermission("system:dept:query")
@GetMapping(value = "/{deptId}")
public R<SysDept> getInfo(@PathVariable Long deptId) {
deptService.checkDeptDataScope(deptId);
return R.ok(deptService.selectDeptById(deptId));
}
/**
* 新增部门
*/
@SaCheckPermission("system:dept:add")
@Log(title = "部门管理", businessType = BusinessType.INSERT)
@PostMapping
public R<Void> add(@Validated @RequestBody SysDept dept) {
if (!deptService.checkDeptNameUnique(dept)) {
return R.fail("新增部门'" + dept.getDeptName() + "'失败,部门名称已存在");
}
return toAjax(deptService.insertDept(dept));
}
/**
* 修改部门
*/
@SaCheckPermission("system:dept:edit")
@Log(title = "部门管理", businessType = BusinessType.UPDATE)
@PutMapping
public R<Void> edit(@Validated @RequestBody SysDept dept) {
Long deptId = dept.getDeptId();
deptService.checkDeptDataScope(deptId);
if (!deptService.checkDeptNameUnique(dept)) {
return R.fail("修改部门'" + dept.getDeptName() + "'失败,部门名称已存在");
} else if (dept.getParentId().equals(deptId)) {
return R.fail("修改部门'" + dept.getDeptName() + "'失败,上级部门不能是自己");
} else if (StringUtils.equals(UserConstants.DEPT_DISABLE, dept.getStatus())
&& deptService.selectNormalChildrenDeptById(deptId) > 0) {
return R.fail("该部门包含未停用的子部门!");
}
return toAjax(deptService.updateDept(dept));
}
/**
* 删除部门
*
* @param deptId 部门ID
*/
@SaCheckPermission("system:dept:remove")
@Log(title = "部门管理", businessType = BusinessType.DELETE)
@DeleteMapping("/{deptId}")
@Transactional
public R<Void> remove(@PathVariable Long deptId) {
deptService.checkDeptDataScope(deptId);
return toAjax(deptService.deleteDeptById(deptId));
}
}
package com.dsk.web.controller.system;
import cn.dev33.satoken.annotation.SaCheckPermission;
import cn.hutool.core.util.ObjectUtil;
import com.dsk.common.annotation.Log;
import com.dsk.common.core.controller.BaseController;
import com.dsk.common.core.domain.PageQuery;
import com.dsk.common.core.domain.R;
import com.dsk.common.core.domain.entity.SysDictData;
import com.dsk.common.core.page.TableDataInfo;
import com.dsk.common.enums.BusinessType;
import com.dsk.common.utils.poi.ExcelUtil;
import com.dsk.system.service.ISysDictDataService;
import com.dsk.system.service.ISysDictTypeService;
import lombok.RequiredArgsConstructor;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletResponse;
import java.util.ArrayList;
import java.util.List;
/**
* 数据字典信息
*
* @author Lion Li
*/
@Validated
@RequiredArgsConstructor
@RestController
@RequestMapping("/system/dict/data")
public class SysDictDataController extends BaseController {
private final ISysDictDataService dictDataService;
private final ISysDictTypeService dictTypeService;
/**
* 查询字典数据列表
*/
@SaCheckPermission("system:dict:list")
@GetMapping("/list")
public TableDataInfo<SysDictData> list(SysDictData dictData, PageQuery pageQuery) {
return dictDataService.selectPageDictDataList(dictData, pageQuery);
}
/**
* 导出字典数据列表
*/
@Log(title = "字典数据", businessType = BusinessType.EXPORT)
@SaCheckPermission("system:dict:export")
@PostMapping("/export")
public void export(SysDictData dictData, HttpServletResponse response) {
List<SysDictData> list = dictDataService.selectDictDataList(dictData);
ExcelUtil.exportExcel(list, "字典数据", SysDictData.class, response);
}
/**
* 查询字典数据详细
*
* @param dictCode 字典code
*/
@SaCheckPermission("system:dict:query")
@GetMapping(value = "/{dictCode}")
public R<SysDictData> getInfo(@PathVariable Long dictCode) {
return R.ok(dictDataService.selectDictDataById(dictCode));
}
/**
* 根据字典类型查询字典数据信息
*
* @param dictType 字典类型
*/
@GetMapping(value = "/type/{dictType}")
public R<List<SysDictData>> dictType(@PathVariable String dictType) {
List<SysDictData> data = dictTypeService.selectDictDataByType(dictType);
if (ObjectUtil.isNull(data)) {
data = new ArrayList<>();
}
return R.ok(data);
}
/**
* 新增字典类型
*/
@SaCheckPermission("system:dict:add")
@Log(title = "字典数据", businessType = BusinessType.INSERT)
@PostMapping
public R<Void> add(@Validated @RequestBody SysDictData dict) {
dictDataService.insertDictData(dict);
return R.ok();
}
/**
* 修改保存字典类型
*/
@SaCheckPermission("system:dict:edit")
@Log(title = "字典数据", businessType = BusinessType.UPDATE)
@PutMapping
public R<Void> edit(@Validated @RequestBody SysDictData dict) {
dictDataService.updateDictData(dict);
return R.ok();
}
/**
* 删除字典类型
*
* @param dictCodes 字典code串
*/
@SaCheckPermission("system:dict:remove")
@Log(title = "字典类型", businessType = BusinessType.DELETE)
@DeleteMapping("/{dictCodes}")
public R<Void> remove(@PathVariable Long[] dictCodes) {
dictDataService.deleteDictDataByIds(dictCodes);
return R.ok();
}
}
package com.dsk.web.controller.system;
import cn.dev33.satoken.annotation.SaCheckPermission;
import com.dsk.common.annotation.Log;
import com.dsk.common.core.controller.BaseController;
import com.dsk.common.core.domain.PageQuery;
import com.dsk.common.core.domain.R;
import com.dsk.common.core.domain.entity.SysDictType;
import com.dsk.common.core.page.TableDataInfo;
import com.dsk.common.enums.BusinessType;
import com.dsk.common.utils.poi.ExcelUtil;
import com.dsk.system.service.ISysDictTypeService;
import lombok.RequiredArgsConstructor;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletResponse;
import java.util.List;
/**
* 数据字典信息
*
* @author Lion Li
*/
@Validated
@RequiredArgsConstructor
@RestController
@RequestMapping("/system/dict/type")
public class SysDictTypeController extends BaseController {
private final ISysDictTypeService dictTypeService;
/**
* 查询字典类型列表
*/
@SaCheckPermission("system:dict:list")
@GetMapping("/list")
public TableDataInfo<SysDictType> list(SysDictType dictType, PageQuery pageQuery) {
return dictTypeService.selectPageDictTypeList(dictType, pageQuery);
}
/**
* 导出字典类型列表
*/
@Log(title = "字典类型", businessType = BusinessType.EXPORT)
@SaCheckPermission("system:dict:export")
@PostMapping("/export")
public void export(SysDictType dictType, HttpServletResponse response) {
List<SysDictType> list = dictTypeService.selectDictTypeList(dictType);
ExcelUtil.exportExcel(list, "字典类型", SysDictType.class, response);
}
/**
* 查询字典类型详细
*
* @param dictId 字典ID
*/
@SaCheckPermission("system:dict:query")
@GetMapping(value = "/{dictId}")
public R<SysDictType> getInfo(@PathVariable Long dictId) {
return R.ok(dictTypeService.selectDictTypeById(dictId));
}
/**
* 新增字典类型
*/
@SaCheckPermission("system:dict:add")
@Log(title = "字典类型", businessType = BusinessType.INSERT)
@PostMapping
public R<Void> add(@Validated @RequestBody SysDictType dict) {
if (!dictTypeService.checkDictTypeUnique(dict)) {
return R.fail("新增字典'" + dict.getDictName() + "'失败,字典类型已存在");
}
dictTypeService.insertDictType(dict);
return R.ok();
}
/**
* 修改字典类型
*/
@SaCheckPermission("system:dict:edit")
@Log(title = "字典类型", businessType = BusinessType.UPDATE)
@PutMapping
public R<Void> edit(@Validated @RequestBody SysDictType dict) {
if (!dictTypeService.checkDictTypeUnique(dict)) {
return R.fail("修改字典'" + dict.getDictName() + "'失败,字典类型已存在");
}
dictTypeService.updateDictType(dict);
return R.ok();
}
/**
* 删除字典类型
*
* @param dictIds 字典ID串
*/
@SaCheckPermission("system:dict:remove")
@Log(title = "字典类型", businessType = BusinessType.DELETE)
@DeleteMapping("/{dictIds}")
public R<Void> remove(@PathVariable Long[] dictIds) {
dictTypeService.deleteDictTypeByIds(dictIds);
return R.ok();
}
/**
* 刷新字典缓存
*/
@SaCheckPermission("system:dict:remove")
@Log(title = "字典类型", businessType = BusinessType.CLEAN)
@DeleteMapping("/refreshCache")
public R<Void> refreshCache() {
dictTypeService.resetDictCache();
return R.ok();
}
/**
* 获取字典选择框列表
*/
@GetMapping("/optionselect")
public R<List<SysDictType>> optionselect() {
List<SysDictType> dictTypes = dictTypeService.selectDictTypeAll();
return R.ok(dictTypes);
}
}
package com.dsk.web.controller.system;
import cn.dev33.satoken.annotation.SaIgnore;
import com.dsk.common.config.RuoYiConfig;
import com.dsk.common.utils.StringUtils;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* 首页
*
* @author Lion Li
*/
@RequiredArgsConstructor
@RestController
public class SysIndexController {
/**
* 系统基础配置
*/
private final RuoYiConfig ruoyiConfig;
/**
* 访问首页,提示语
*/
@SaIgnore
@GetMapping("/")
public String index() {
return StringUtils.format("欢迎使用{}后台管理框架,当前版本:v{},请通过前端地址访问。", ruoyiConfig.getName(), ruoyiConfig.getVersion());
}
}
package com.dsk.web.controller.system;
import cn.dev33.satoken.annotation.SaCheckLogin;
import cn.dev33.satoken.annotation.SaIgnore;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.lang.RegexPool;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.ReUtil;
import com.dsk.common.constant.Constants;
import com.dsk.common.constant.GlobalConstants;
import com.dsk.common.core.domain.R;
import com.dsk.common.core.domain.model.*;
import com.dsk.common.helper.LoginHelper;
import com.dsk.common.utils.redis.RedisUtils;
import com.dsk.jsk.util.IpUtil;
import com.dsk.system.domain.SysMenu;
import com.dsk.system.domain.SysUser;
import com.dsk.system.domain.vo.LoginVo;
import com.dsk.system.domain.vo.RouterVo;
import com.dsk.system.domain.vo.SysTenantVo;
import com.dsk.system.service.ISysMenuService;
import com.dsk.system.service.ISysUserService;
import com.dsk.system.service.SysLoginService;
import lombok.RequiredArgsConstructor;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletRequest;
import javax.validation.constraints.NotBlank;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* 登录验证
*
* @author Lion Li
*/
@Validated
@RequiredArgsConstructor
@RestController
public class SysLoginController {
private final SysLoginService loginService;
private final ISysMenuService menuService;
private final ISysUserService userService;
/**
* 登录方法
*
* @param loginBody 登录信息
* @return 结果
*/
@SaIgnore
@PostMapping("/login")
public R<LoginVo> login(@Validated @RequestBody LoginBody loginBody, HttpServletRequest request) {
// Map<String, Object> ajax = new HashMap<>();
// 生成令牌
// LoginVo loginVo = loginService.login(loginBody.getUsername(), loginBody.getPassword(), loginBody.getCode(),
// loginBody.getUuid());
// ajax.put(Constants.TOKEN, token);
// return R.ok(ajax);
String ipAddr = IpUtil.getIpAddr(request);
LoginVo loginVo = loginService.passwordLogin(loginBody, ipAddr);
return R.ok(loginVo);
}
/**
* 三方平台跳转登录方法
*
* @param tpLoginBody 登录信息
* @return 结果
*/
@SaIgnore
@PostMapping("/thirdPlatformLogin")
public R<LoginVo> thirdPlatformLogin(@Validated @RequestBody ThirdPlatformLoginBody tpLoginBody, HttpServletRequest request) {
String ipAddr = IpUtil.getIpAddr(request);
return R.ok(loginService.thirdPlatformLogin(tpLoginBody, ipAddr));
}
// /**
// * 发送短信验证码
// *
// * @param phones 电话号
// * @param templateId 模板ID
// */
// @SaIgnore
// @GetMapping("/send/sms/code")
// public R<Object> sendAliyun(String phones, String templateId) {
// LinkedHashMap<String, String> map = new LinkedHashMap<>(1);
// map.put("code", RandomUtil.randomNumbers(4));
// SmsBlend smsBlend = SmsFactory.createSmsBlend(SupplierType.ALIBABA);
// SmsResponse smsResponse = smsBlend.sendMessage(phones, templateId, map);
// return R.ok(smsResponse);
// }
/**
* 短信登录
*
* @param smsLoginBody 登录信息
* @return 结果
*/
@SaIgnore
@PostMapping("/smsLogin")
public R<LoginVo> smsLogin(@Validated @RequestBody SmsLoginBody smsLoginBody) {
// 生成令牌
LoginVo loginVo = loginService.smsLogin(smsLoginBody.getPhonenumber(), smsLoginBody.getSmsCode());
return R.ok(loginVo);
}
/**
* 动态切换租户
*/
@SaCheckLogin
@PostMapping("/switch/tenant")
public R<LoginVo> switchTenant(String tenantId) {
LoginVo loginVo = loginService.switchTenant(tenantId);
return R.ok(loginVo);
}
/**
* 用户租户列表:根据用户账号获取用户绑定的租户列表
*
* @return 结果
*/
@SaCheckLogin
@PostMapping("/user/tenants")
public R<List<SysTenantVo>> userTenants() {
LoginUser loginUser = LoginHelper.getLoginUser();
List<SysTenantVo> tenantList = loginService.tenantListByAccount(loginUser.getUserPhone());
return R.ok(tenantList);
}
/**
* 用户租户列表:根据用户账号获取用户绑定的租户列表
*
* @return 结果
*/
@SaIgnore
@PostMapping("/user/tenant/list")
public R<List<SysTenantVo>> userTenantList(String phone) {
if (!ReUtil.isMatch(RegexPool.MOBILE, phone)) {
return R.ok(new ArrayList<>());
}
List<SysTenantVo> tenantList = loginService.tenantListByAccount(phone);
if (CollectionUtil.isNotEmpty(tenantList)) {
for (SysTenantVo sysTenantVo : tenantList) {
sysTenantVo.setCompanyName("");
}
}
return R.ok(tenantList);
}
/**
* 获取当前用户默认租户
*
* @return
*/
@SaCheckLogin
@PostMapping("/user/default/tenant")
public R<Map> userDefaultTenant() {
LoginUser loginUser = LoginHelper.getLoginUser();
Assert.notNull(loginUser, "用户信息不存在!");
String defaultTenantKey = GlobalConstants.PHONE_DEFAULT_TENANT + loginUser.getUserPhone();
String defaultTenant = RedisUtils.getCacheObject(defaultTenantKey);
return R.ok(MapUtil.of("tenantId", defaultTenant));
}
/**
* 获取当前用户默认租户
*
* @return
*/
@SaIgnore
@PostMapping("/user/default/tenant/phone")
public R<Map> userDefaultTenant(String phone) {
// LoginUser loginUser = LoginHelper.getLoginUser();
// Assert.notNull(loginUser,"用户信息不存在!");
String defaultTenantKey = GlobalConstants.PHONE_DEFAULT_TENANT + phone;
String defaultTenant = RedisUtils.getCacheObject(defaultTenantKey);
return R.ok(MapUtil.of("tenantId", defaultTenant));
}
/**
* 邮件登录
*
* @param body 登录信息
* @return 结果
*/
@PostMapping("/emailLogin")
public R<Map<String, Object>> emailLogin(@Validated @RequestBody EmailLoginBody body) {
Map<String, Object> ajax = new HashMap<>();
// 生成令牌
String token = loginService.emailLogin(body.getEmail(), body.getEmailCode());
ajax.put(Constants.TOKEN, token);
return R.ok(ajax);
}
/**
* 小程序登录(示例)
*
* @param xcxCode 小程序code
* @return 结果
*/
@SaIgnore
@PostMapping("/xcxLogin")
public R<Map<String, Object>> xcxLogin(@NotBlank(message = "{xcx.code.not.blank}") String xcxCode) {
Map<String, Object> ajax = new HashMap<>();
// 生成令牌
String token = loginService.xcxLogin(xcxCode);
ajax.put(Constants.TOKEN, token);
return R.ok(ajax);
}
/**
* 退出登录
*/
@SaIgnore
@PostMapping("/logout")
public R<Void> logout() {
loginService.logout();
return R.ok("退出成功");
}
/**
* 获取用户信息
*
* @return 用户信息
*/
@GetMapping("getInfo")
public R<Map<String, Object>> getInfo(HttpServletRequest request) {
LoginUser loginUser = LoginHelper.getLoginUser();
//校验当前发请求的IP与登录账号的IP是否相同
// String loginUserIpaddr = loginUser.getIpaddr();
// if (StringUtils.isNotBlank(loginUserIpaddr) && !Objects.equals(IpUtil.getIpAddr(request), loginUserIpaddr)) {
// throw new ServiceException("该账号已在其他地方登录,请求失败!", 401);
// }
SysUser user = userService.selectUserById(loginUser.getUserId());
Map<String, Object> ajax = new HashMap<>();
ajax.put("user", user);
ajax.put("roles", loginUser.getRolePermission());
ajax.put("permissions", loginUser.getMenuPermission());
ajax.put("isThirdPlatformLogin", LoginHelper.getLoginUser().getIsThirdPlatformLogin());
return R.ok(ajax);
}
/**
* 获取路由信息
*
* @return 路由信息
*/
@GetMapping("getRouters")
public R<List<RouterVo>> getRouters() {
Long userId = LoginHelper.getUserId();
List<SysMenu> menus = menuService.selectMenuTreeByUserId(userId);
return R.ok(menuService.buildMenus(menus));
}
}
package com.dsk.web.controller.system;
import cn.dev33.satoken.annotation.SaCheckPermission;
import cn.dev33.satoken.annotation.SaCheckRole;
import cn.dev33.satoken.annotation.SaMode;
import cn.hutool.core.lang.tree.Tree;
import com.dsk.common.annotation.Log;
import com.dsk.common.constant.TenantConstants;
import com.dsk.common.constant.UserConstants;
import com.dsk.common.core.controller.BaseController;
import com.dsk.common.core.domain.R;
import com.dsk.system.domain.SysMenu;
import com.dsk.common.enums.BusinessType;
import com.dsk.common.utils.StringUtils;
import com.dsk.system.service.ISysMenuService;
import lombok.RequiredArgsConstructor;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* 菜单信息
*
* @author Lion Li
*/
@Validated
@RequiredArgsConstructor
@RestController
@RequestMapping("/system/menu")
public class SysMenuController extends BaseController {
private final ISysMenuService menuService;
/**
* 获取菜单列表
*/
@SaCheckPermission("system:menu:list")
@GetMapping("/list")
public R<List<SysMenu>> list(SysMenu menu) {
List<SysMenu> menus = menuService.selectMenuList(menu, getUserId());
return R.ok(menus);
}
/**
* 根据菜单编号获取详细信息
*
* @param menuId 菜单ID
*/
@SaCheckPermission("system:menu:query")
@GetMapping(value = "/{menuId}")
public R<SysMenu> getInfo(@PathVariable Long menuId) {
return R.ok(menuService.selectMenuById(menuId));
}
/**
* 获取菜单下拉树列表
*/
@GetMapping("/treeselect")
public R<List<Tree<Long>>> treeselect(SysMenu menu) {
List<SysMenu> menus = menuService.selectMenuList(menu, getUserId());
return R.ok(menuService.buildMenuTreeSelect(menus));
}
/**
* 获取企业方案菜单下拉树列表
* 剔除"企业管理"相关菜单
*/
@SaCheckRole(value = {TenantConstants.SUPER_ADMIN_ROLE_KEY, TenantConstants.DSK_TENANT_ADMIN_ROLE_KEY}, mode = SaMode.OR)
@GetMapping("/tenantPackageTreeSelect")
public R<List<Tree<Long>>> tenantPackageTreeSelect(SysMenu menu) {
List<SysMenu> menus = menuService.selectTenantPackageMenuList(menu, getUserId());
return R.ok(menuService.buildMenuTreeSelect(menus));
}
/**
* 加载对应角色菜单列表树
*
* @param roleId 角色ID
*/
@GetMapping(value = "/roleMenuTreeselect/{roleId}")
public R<Map<String, Object>> roleMenuTreeselect(@PathVariable("roleId") Long roleId) {
List<SysMenu> menus = menuService.selectMenuList(getUserId());
Map<String, Object> ajax = new HashMap<>();
ajax.put("checkedKeys", menuService.selectMenuListByRoleId(roleId));
ajax.put("menus", menuService.buildMenuTreeSelect(menus));
return R.ok(ajax);
}
/**
* 新增菜单
*/
@SaCheckPermission("system:menu:add")
@Log(title = "菜单管理", businessType = BusinessType.INSERT)
@PostMapping
public R<Void> add(@Validated @RequestBody SysMenu menu) {
if (!menuService.checkMenuNameUnique(menu)) {
return R.fail("新增菜单'" + menu.getMenuName() + "'失败,菜单名称已存在");
} else if (UserConstants.YES_FRAME.equals(menu.getIsFrame()) && !StringUtils.ishttp(menu.getPath())) {
return R.fail("新增菜单'" + menu.getMenuName() + "'失败,地址必须以http(s)://开头");
}
return toAjax(menuService.insertMenu(menu));
}
/**
* 修改菜单
*/
@SaCheckPermission("system:menu:edit")
@Log(title = "菜单管理", businessType = BusinessType.UPDATE)
@PutMapping
public R<Void> edit(@Validated @RequestBody SysMenu menu) {
if (!menuService.checkMenuNameUnique(menu)) {
return R.fail("修改菜单'" + menu.getMenuName() + "'失败,菜单名称已存在");
} else if (UserConstants.YES_FRAME.equals(menu.getIsFrame()) && !StringUtils.ishttp(menu.getPath())) {
return R.fail("修改菜单'" + menu.getMenuName() + "'失败,地址必须以http(s)://开头");
} else if (menu.getMenuId().equals(menu.getParentId())) {
return R.fail("修改菜单'" + menu.getMenuName() + "'失败,上级菜单不能选择自己");
}
return toAjax(menuService.updateMenu(menu));
}
/**
* 删除菜单
*
* @param menuId 菜单ID
*/
@SaCheckPermission("system:menu:remove")
@Log(title = "菜单管理", businessType = BusinessType.DELETE)
@DeleteMapping("/{menuId}")
public R<Void> remove(@PathVariable("menuId") Long menuId) {
if (menuService.hasChildByMenuId(menuId)) {
return R.warn("存在子菜单,不允许删除");
}
if (menuService.checkMenuExistRole(menuId)) {
return R.warn("菜单已分配,不允许删除");
}
return toAjax(menuService.deleteMenuById(menuId));
}
}
package com.dsk.web.controller.system;
import cn.dev33.satoken.annotation.SaCheckPermission;
import com.dsk.common.annotation.Log;
import com.dsk.common.core.controller.BaseController;
import com.dsk.common.core.domain.PageQuery;
import com.dsk.common.core.domain.R;
import com.dsk.common.core.page.TableDataInfo;
import com.dsk.common.enums.BusinessType;
import com.dsk.system.domain.SysNotice;
import com.dsk.system.service.ISysNoticeService;
import lombok.RequiredArgsConstructor;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
/**
* 公告 信息操作处理
*
* @author Lion Li
*/
@Validated
@RequiredArgsConstructor
@RestController
@RequestMapping("/system/notice")
public class SysNoticeController extends BaseController {
private final ISysNoticeService noticeService;
/**
* 获取通知公告列表
*/
@SaCheckPermission("system:notice:list")
@GetMapping("/list")
public TableDataInfo<SysNotice> list(SysNotice notice, PageQuery pageQuery) {
return noticeService.selectPageNoticeList(notice, pageQuery);
}
/**
* 根据通知公告编号获取详细信息
*
* @param noticeId 公告ID
*/
@SaCheckPermission("system:notice:query")
@GetMapping(value = "/{noticeId}")
public R<SysNotice> getInfo(@PathVariable Long noticeId) {
return R.ok(noticeService.selectNoticeById(noticeId));
}
/**
* 新增通知公告
*/
@SaCheckPermission("system:notice:add")
@Log(title = "通知公告", businessType = BusinessType.INSERT)
@PostMapping
public R<Void> add(@Validated @RequestBody SysNotice notice) {
return toAjax(noticeService.insertNotice(notice));
}
/**
* 修改通知公告
*/
@SaCheckPermission("system:notice:edit")
@Log(title = "通知公告", businessType = BusinessType.UPDATE)
@PutMapping
public R<Void> edit(@Validated @RequestBody SysNotice notice) {
return toAjax(noticeService.updateNotice(notice));
}
/**
* 删除通知公告
*
* @param noticeIds 公告ID串
*/
@SaCheckPermission("system:notice:remove")
@Log(title = "通知公告", businessType = BusinessType.DELETE)
@DeleteMapping("/{noticeIds}")
public R<Void> remove(@PathVariable Long[] noticeIds) {
return toAjax(noticeService.deleteNoticeByIds(noticeIds));
}
}
package com.dsk.web.controller.system;
import cn.dev33.satoken.annotation.SaCheckPermission;
import com.dsk.common.annotation.Log;
import com.dsk.common.annotation.RepeatSubmit;
import com.dsk.common.core.controller.BaseController;
import com.dsk.common.core.domain.PageQuery;
import com.dsk.common.core.domain.R;
import com.dsk.common.core.page.TableDataInfo;
import com.dsk.common.core.validate.AddGroup;
import com.dsk.common.core.validate.EditGroup;
import com.dsk.common.core.validate.QueryGroup;
import com.dsk.common.enums.BusinessType;
import com.dsk.system.domain.bo.SysOssConfigBo;
import com.dsk.system.domain.vo.SysOssConfigVo;
import com.dsk.system.service.ISysOssConfigService;
import lombok.RequiredArgsConstructor;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
import java.util.Arrays;
/**
* 对象存储配置
*
* @author Lion Li
* @author 孤舟烟雨
* @date 2021-08-13
*/
@Validated
@RequiredArgsConstructor
@RestController
@RequestMapping("/system/oss/config")
public class SysOssConfigController extends BaseController {
private final ISysOssConfigService iSysOssConfigService;
/**
* 查询对象存储配置列表
*/
@SaCheckPermission("system:oss:list")
@GetMapping("/list")
public TableDataInfo<SysOssConfigVo> list(@Validated(QueryGroup.class) SysOssConfigBo bo, PageQuery pageQuery) {
return iSysOssConfigService.queryPageList(bo, pageQuery);
}
/**
* 获取对象存储配置详细信息
*
* @param ossConfigId OSS配置ID
*/
@SaCheckPermission("system:oss:query")
@GetMapping("/{ossConfigId}")
public R<SysOssConfigVo> getInfo(@NotNull(message = "主键不能为空")
@PathVariable Long ossConfigId) {
return R.ok(iSysOssConfigService.queryById(ossConfigId));
}
/**
* 新增对象存储配置
*/
@SaCheckPermission("system:oss:add")
@Log(title = "对象存储配置", businessType = BusinessType.INSERT)
@RepeatSubmit()
@PostMapping()
public R<Void> add(@Validated(AddGroup.class) @RequestBody SysOssConfigBo bo) {
return toAjax(iSysOssConfigService.insertByBo(bo));
}
/**
* 修改对象存储配置
*/
@SaCheckPermission("system:oss:edit")
@Log(title = "对象存储配置", businessType = BusinessType.UPDATE)
@RepeatSubmit()
@PutMapping()
public R<Void> edit(@Validated(EditGroup.class) @RequestBody SysOssConfigBo bo) {
return toAjax(iSysOssConfigService.updateByBo(bo));
}
/**
* 删除对象存储配置
*
* @param ossConfigIds OSS配置ID串
*/
@SaCheckPermission("system:oss:remove")
@Log(title = "对象存储配置", businessType = BusinessType.DELETE)
@DeleteMapping("/{ossConfigIds}")
public R<Void> remove(@NotEmpty(message = "主键不能为空")
@PathVariable Long[] ossConfigIds) {
return toAjax(iSysOssConfigService.deleteWithValidByIds(Arrays.asList(ossConfigIds), true));
}
/**
* 状态修改
*/
@SaCheckPermission("system:oss:edit")
@Log(title = "对象存储状态修改", businessType = BusinessType.UPDATE)
@PutMapping("/changeStatus")
public R<Void> changeStatus(@RequestBody SysOssConfigBo bo) {
return toAjax(iSysOssConfigService.updateOssConfigStatus(bo));
}
}
package com.dsk.web.controller.system;
import cn.dev33.satoken.annotation.SaCheckPermission;
import cn.hutool.core.util.ObjectUtil;
import com.dsk.common.annotation.Log;
import com.dsk.common.core.controller.BaseController;
import com.dsk.common.core.domain.PageQuery;
import com.dsk.common.core.domain.R;
import com.dsk.common.core.page.TableDataInfo;
import com.dsk.common.core.validate.QueryGroup;
import com.dsk.common.enums.BusinessType;
import com.dsk.system.domain.bo.SysOssBo;
import com.dsk.system.domain.vo.SysOssVo;
import com.dsk.system.service.ISysOssService;
import lombok.RequiredArgsConstructor;
import org.springframework.http.MediaType;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletResponse;
import javax.validation.constraints.NotEmpty;
import java.io.IOException;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* 文件上传 控制层
*
* @author Lion Li
*/
@Validated
@RequiredArgsConstructor
@RestController
@RequestMapping("/system/oss")
public class SysOssController extends BaseController {
private final ISysOssService iSysOssService;
/**
* 查询OSS对象存储列表
*/
@SaCheckPermission("system:oss:list")
@GetMapping("/list")
public TableDataInfo<SysOssVo> list(@Validated(QueryGroup.class) SysOssBo bo, PageQuery pageQuery) {
return iSysOssService.queryPageList(bo, pageQuery);
}
/**
* 查询OSS对象基于id串
*
* @param ossIds OSS对象ID串
*/
@SaCheckPermission("system:oss:list")
@GetMapping("/listByIds/{ossIds}")
public R<List<SysOssVo>> listByIds(@NotEmpty(message = "主键不能为空")
@PathVariable Long[] ossIds) {
List<SysOssVo> list = iSysOssService.listByIds(Arrays.asList(ossIds));
return R.ok(list);
}
/**
* 上传OSS对象存储
*
* @param file 文件
*/
@SaCheckPermission("system:oss:upload")
@Log(title = "OSS对象存储", businessType = BusinessType.INSERT)
@PostMapping(value = "/upload", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
public R<Map<String, String>> upload(@RequestPart("file") MultipartFile file) {
if (ObjectUtil.isNull(file)) {
return R.fail("上传文件不能为空");
}
SysOssVo oss = iSysOssService.upload(file);
Map<String, String> map = new HashMap<>(2);
map.put("url", oss.getUrl());
map.put("fileName", oss.getOriginalName());
map.put("ossId", oss.getOssId().toString());
return R.ok(map);
}
/**
* 下载OSS对象
*
* @param ossId OSS对象ID
*/
@SaCheckPermission("system:oss:download")
@GetMapping("/download/{ossId}")
public void download(@PathVariable Long ossId, HttpServletResponse response) throws IOException {
iSysOssService.download(ossId,response);
}
/**
* 删除OSS对象存储
*
* @param ossIds OSS对象ID串
*/
@SaCheckPermission("system:oss:remove")
@Log(title = "OSS对象存储", businessType = BusinessType.DELETE)
@DeleteMapping("/{ossIds}")
public R<Void> remove(@NotEmpty(message = "主键不能为空")
@PathVariable Long[] ossIds) {
return toAjax(iSysOssService.deleteWithValidByIds(Arrays.asList(ossIds), true));
}
}
package com.dsk.web.controller.system;
import cn.dev33.satoken.annotation.SaCheckPermission;
import com.dsk.common.annotation.Log;
import com.dsk.common.core.controller.BaseController;
import com.dsk.common.core.domain.PageQuery;
import com.dsk.common.core.domain.R;
import com.dsk.common.core.page.TableDataInfo;
import com.dsk.common.enums.BusinessType;
import com.dsk.common.utils.poi.ExcelUtil;
import com.dsk.system.domain.SysPost;
import com.dsk.system.service.ISysPostService;
import lombok.RequiredArgsConstructor;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletResponse;
import java.util.List;
/**
* 岗位信息操作处理
*
* @author Lion Li
*/
@Validated
@RequiredArgsConstructor
@RestController
@RequestMapping("/system/post")
public class SysPostController extends BaseController {
private final ISysPostService postService;
/**
* 获取岗位列表
*/
@SaCheckPermission("system:post:list")
@GetMapping("/list")
public TableDataInfo<SysPost> list(SysPost post, PageQuery pageQuery) {
return postService.selectPagePostList(post, pageQuery);
}
/**
* 导出岗位列表
*/
@Log(title = "岗位管理", businessType = BusinessType.EXPORT)
@SaCheckPermission("system:post:export")
@PostMapping("/export")
public void export(SysPost post, HttpServletResponse response) {
List<SysPost> list = postService.selectPostList(post);
ExcelUtil.exportExcel(list, "岗位数据", SysPost.class, response);
}
/**
* 根据岗位编号获取详细信息
*
* @param postId 岗位ID
*/
@SaCheckPermission("system:post:query")
@GetMapping(value = "/{postId}")
public R<SysPost> getInfo(@PathVariable Long postId) {
return R.ok(postService.selectPostById(postId));
}
/**
* 新增岗位
*/
@SaCheckPermission("system:post:add")
@Log(title = "岗位管理", businessType = BusinessType.INSERT)
@PostMapping
public R<Void> add(@Validated @RequestBody SysPost post) {
if (!postService.checkPostNameUnique(post)) {
return R.fail("新增岗位'" + post.getPostName() + "'失败,岗位名称已存在");
} else if (!postService.checkPostCodeUnique(post)) {
return R.fail("新增岗位'" + post.getPostName() + "'失败,岗位编码已存在");
}
return toAjax(postService.insertPost(post));
}
/**
* 修改岗位
*/
@SaCheckPermission("system:post:edit")
@Log(title = "岗位管理", businessType = BusinessType.UPDATE)
@PutMapping
public R<Void> edit(@Validated @RequestBody SysPost post) {
if (!postService.checkPostNameUnique(post)) {
return R.fail("修改岗位'" + post.getPostName() + "'失败,岗位名称已存在");
} else if (!postService.checkPostCodeUnique(post)) {
return R.fail("修改岗位'" + post.getPostName() + "'失败,岗位编码已存在");
}
return toAjax(postService.updatePost(post));
}
/**
* 删除岗位
*
* @param postIds 岗位ID串
*/
@SaCheckPermission("system:post:remove")
@Log(title = "岗位管理", businessType = BusinessType.DELETE)
@DeleteMapping("/{postIds}")
public R<Void> remove(@PathVariable Long[] postIds) {
return toAjax(postService.deletePostByIds(postIds));
}
/**
* 获取岗位选择框列表
*/
@GetMapping("/optionselect")
public R<List<SysPost>> optionselect() {
List<SysPost> posts = postService.selectPostAll();
return R.ok(posts);
}
}
package com.dsk.web.controller.system;
import cn.dev33.satoken.secure.BCrypt;
import cn.hutool.core.io.FileUtil;
import com.dsk.common.annotation.Log;
import com.dsk.common.core.controller.BaseController;
import com.dsk.common.core.domain.R;
import com.dsk.common.enums.BusinessType;
import com.dsk.common.helper.LoginHelper;
import com.dsk.common.utils.StringUtils;
import com.dsk.common.utils.file.MimeTypeUtils;
import com.dsk.system.domain.SysUser;
import com.dsk.system.domain.vo.SysOssVo;
import com.dsk.system.service.ISysOssService;
import com.dsk.system.service.ISysUserService;
import lombok.RequiredArgsConstructor;
import org.springframework.http.MediaType;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import java.util.Arrays;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;
/**
* 个人信息 业务处理
*
* @author Lion Li
*/
@Validated
@RequiredArgsConstructor
@RestController
@RequestMapping("/system/user/profile")
public class SysProfileController extends BaseController {
private final ISysUserService userService;
private final ISysOssService iSysOssService;
/**
* 个人信息
*/
@GetMapping
public R<Map<String, Object>> profile() {
SysUser user = userService.selectUserById(getUserId());
Map<String, Object> ajax = new HashMap<>();
ajax.put("user", user);
ajax.put("roleGroup", userService.selectUserRoleGroup(user.getUserName()));
ajax.put("postGroup", userService.selectUserPostGroup(user.getUserName()));
return R.ok(ajax);
}
/**
* 修改用户
*/
@Log(title = "个人信息", businessType = BusinessType.UPDATE)
@PutMapping
public R<Void> updateProfile(@RequestBody SysUser user) {
if (StringUtils.isNotEmpty(user.getPhonenumber()) && !userService.checkPhoneUnique(user)) {
return R.fail("修改用户'" + user.getUserName() + "'失败,手机号码已存在");
}
if (StringUtils.isNotEmpty(user.getEmail()) && !userService.checkEmailUnique(user)) {
return R.fail("修改用户'" + user.getUserName() + "'失败,邮箱账号已存在");
}
user.setUserId(getUserId());
user.setUserName(null);
user.setPassword(null);
user.setAvatar(null);
user.setDeptId(null);
if (userService.updateUserProfile(user) > 0) {
return R.ok();
}
return R.fail("修改个人信息异常,请联系管理员");
}
/**
* 重置密码
*
* @param newPassword 旧密码
* @param oldPassword 新密码
*/
@Log(title = "个人信息", businessType = BusinessType.UPDATE)
@PutMapping("/updatePwd")
public R<Void> updatePwd(String oldPassword, String newPassword) {
oldPassword = new String(Base64.getDecoder().decode(oldPassword));
newPassword = new String(Base64.getDecoder().decode(newPassword));
SysUser user = userService.selectUserById(LoginHelper.getUserId());
String userName = user.getUserName();
String password = user.getPassword();
if (!BCrypt.checkpw(oldPassword, password)) {
return R.fail("修改密码失败,旧密码错误");
}
if (BCrypt.checkpw(newPassword, password)) {
return R.fail("新密码不能与旧密码相同");
}
if (userService.resetUserPwd(userName, BCrypt.hashpw(newPassword)) > 0) {
return R.ok();
}
return R.fail("修改密码异常,请联系管理员");
}
/**
* 头像上传
*
* @param avatarfile 用户头像
*/
@Log(title = "用户头像", businessType = BusinessType.UPDATE)
@PostMapping(value = "/avatar", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
public R<Map<String, Object>> avatar(@RequestPart("avatarfile") MultipartFile avatarfile) {
Map<String, Object> ajax = new HashMap<>();
if (!avatarfile.isEmpty()) {
String extension = FileUtil.extName(avatarfile.getOriginalFilename());
if (!StringUtils.equalsAnyIgnoreCase(extension, MimeTypeUtils.IMAGE_EXTENSION)) {
return R.fail("文件格式不正确,请上传" + Arrays.toString(MimeTypeUtils.IMAGE_EXTENSION) + "格式");
}
SysOssVo oss = iSysOssService.upload(avatarfile);
String avatar = oss.getUrl();
if (userService.updateUserAvatar(getUsername(), avatar)) {
ajax.put("imgUrl", avatar);
return R.ok(ajax);
}
}
return R.fail("上传图片异常,请联系管理员");
}
}
package com.dsk.web.controller.system;
import com.dsk.common.core.controller.BaseController;
import com.dsk.common.core.domain.AjaxResult;
import com.dsk.common.core.domain.R;
import com.dsk.system.domain.SysRegion;
import com.dsk.system.service.SysRegionService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
/**
* 地区信息
*
* @author lcl
* @create 2023/6/28
*/
@RestController
@RequestMapping("/system/region")
public class SysRegionController extends BaseController {
@Autowired
private SysRegionService baseService;
/**
* 通过父id获取下级地区信息
*/
@GetMapping("/list/{parentId}")
public R<List<SysRegion>> listByParentId(@PathVariable Integer parentId) {
return R.ok(baseService.selectByParentId(parentId));
}
}
package com.dsk.web.controller.system;
import cn.dev33.satoken.annotation.SaIgnore;
import com.dsk.common.core.controller.BaseController;
import com.dsk.common.core.domain.R;
import com.dsk.common.core.domain.model.RegisterBody;
import com.dsk.system.service.ISysConfigService;
import com.dsk.system.service.SysRegisterService;
import lombok.RequiredArgsConstructor;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
/**
* 注册验证
*
* @author Lion Li
*/
@Validated
@RequiredArgsConstructor
@RestController
public class SysRegisterController extends BaseController {
private final SysRegisterService registerService;
private final ISysConfigService configService;
/**
* 用户注册
*/
@SaIgnore
@PostMapping("/register")
public R<Void> register(@Validated @RequestBody RegisterBody user) {
if (!("true".equals(configService.selectConfigByKey("sys.account.registerUser")))) {
return R.fail("当前系统没有开启注册功能!");
}
registerService.register(user);
return R.ok();
}
}
package com.dsk.web.controller.system;
import cn.dev33.satoken.annotation.SaCheckPermission;
import com.dsk.common.annotation.Log;
import com.dsk.common.core.controller.BaseController;
import com.dsk.common.core.domain.PageQuery;
import com.dsk.common.core.domain.R;
import com.dsk.common.utils.Chinese2PinyinUtils;
import com.dsk.system.domain.SysDept;
import com.dsk.system.domain.SysRole;
import com.dsk.system.domain.SysUser;
import com.dsk.common.core.page.TableDataInfo;
import com.dsk.common.enums.BusinessType;
import com.dsk.common.utils.poi.ExcelUtil;
import com.dsk.system.domain.SysUserRole;
import com.dsk.system.service.ISysDeptService;
import com.dsk.system.service.ISysRoleService;
import com.dsk.system.service.ISysUserService;
import com.dsk.system.service.SysPermissionService;
import lombok.RequiredArgsConstructor;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletResponse;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* 角色信息
*
* @author Lion Li
*/
@Validated
@RequiredArgsConstructor
@RestController
@RequestMapping("/system/role")
public class SysRoleController extends BaseController {
private final ISysRoleService roleService;
private final ISysUserService userService;
private final ISysDeptService deptService;
private final SysPermissionService permissionService;
/**
* 获取角色信息列表
*/
@SaCheckPermission("system:role:list")
@GetMapping("/list")
public TableDataInfo<SysRole> list(SysRole role, PageQuery pageQuery) {
return roleService.selectPageRoleList(role, pageQuery);
}
/**
* 导出角色信息列表
*/
@Log(title = "角色管理", businessType = BusinessType.EXPORT)
@SaCheckPermission("system:role:export")
@PostMapping("/export")
public void export(SysRole role, HttpServletResponse response) {
List<SysRole> list = roleService.selectRoleList(role);
ExcelUtil.exportExcel(list, "角色数据", SysRole.class, response);
}
/**
* 根据角色编号获取详细信息
*
* @param roleId 角色ID
*/
@SaCheckPermission("system:role:query")
@GetMapping(value = "/{roleId}")
public R<SysRole> getInfo(@PathVariable Long roleId) {
roleService.checkRoleDataScope(roleId);
return R.ok(roleService.selectRoleById(roleId));
}
/**
* 新增角色
*/
@SaCheckPermission("system:role:add")
@Log(title = "角色管理", businessType = BusinessType.INSERT)
@PostMapping
public R<Void> add(@Validated @RequestBody SysRole role) {
roleService.checkRoleAllowed(role);
//应产品经理强!烈!要求,让后台自动生成权限字符,为方便后期排查bug,经考虑后将角色名的拼音设置为权限字符
role.setRoleKey(Chinese2PinyinUtils.toLowerPinyin(role.getRoleName()));
if (!roleService.checkRoleNameUnique(role)) {
return R.fail("新增角色'" + role.getRoleName() + "'失败,角色名称已存在");
} else if (!roleService.checkRoleKeyUnique(role)) {
return R.fail("新增角色'" + role.getRoleName() + "'失败,角色权限已存在");
}
return toAjax(roleService.insertRole(role));
}
/**
* 修改保存角色
*/
@SaCheckPermission("system:role:edit")
@Log(title = "角色管理", businessType = BusinessType.UPDATE)
@PutMapping
public R<Void> edit(@Validated @RequestBody SysRole role) {
roleService.checkRoleAllowed(role);
roleService.checkRoleDataScope(role.getRoleId());
if (!roleService.checkRoleNameUnique(role)) {
return R.fail("修改角色'" + role.getRoleName() + "'失败,角色名称已存在");
} else if (!roleService.checkRoleKeyUnique(role)) {
return R.fail("修改角色'" + role.getRoleName() + "'失败,角色权限已存在");
}
if (roleService.updateRole(role) > 0) {
roleService.cleanOnlineUserByRole(role.getRoleId());
return R.ok();
}
return R.fail("修改角色'" + role.getRoleName() + "'失败,请联系管理员");
}
/**
* 修改保存数据权限
*/
@SaCheckPermission("system:role:edit")
@Log(title = "角色管理", businessType = BusinessType.UPDATE)
@PutMapping("/dataScope")
public R<Void> dataScope(@RequestBody SysRole role) {
roleService.checkRoleAllowed(role);
roleService.checkRoleDataScope(role.getRoleId());
return toAjax(roleService.authDataScope(role));
}
/**
* 状态修改
*/
@SaCheckPermission("system:role:edit")
@Log(title = "角色管理", businessType = BusinessType.UPDATE)
@PutMapping("/changeStatus")
public R<Void> changeStatus(@RequestBody SysRole role) {
roleService.checkRoleAllowed(role);
roleService.checkRoleDataScope(role.getRoleId());
return toAjax(roleService.updateRoleStatus(role));
}
/**
* 删除角色
*
* @param roleIds 角色ID串
*/
@SaCheckPermission("system:role:remove")
@Log(title = "角色管理", businessType = BusinessType.DELETE)
@DeleteMapping("/{roleIds}")
public R<Void> remove(@PathVariable Long[] roleIds) {
return toAjax(roleService.deleteRoleByIds(roleIds));
}
/**
* 获取角色选择框列表
*/
@SaCheckPermission("system:role:query")
@GetMapping("/optionselect")
public R<List<SysRole>> optionselect() {
return R.ok(roleService.selectRoleAll());
}
/**
* 查询已分配用户角色列表
*/
@SaCheckPermission("system:role:list")
@GetMapping("/authUser/allocatedList")
public TableDataInfo<SysUser> allocatedList(SysUser user, PageQuery pageQuery) {
return userService.selectAllocatedList(user, pageQuery);
}
/**
* 查询未分配用户角色列表
*/
@SaCheckPermission("system:role:list")
@GetMapping("/authUser/unallocatedList")
public TableDataInfo<SysUser> unallocatedList(SysUser user, PageQuery pageQuery) {
return userService.selectUnallocatedList(user, pageQuery);
}
/**
* 取消授权用户
*/
@SaCheckPermission("system:role:edit")
@Log(title = "角色管理", businessType = BusinessType.GRANT)
@PutMapping("/authUser/cancel")
public R<Void> cancelAuthUser(@RequestBody SysUserRole userRole) {
return toAjax(roleService.deleteAuthUser(userRole));
}
/**
* 批量取消授权用户
*
* @param roleId 角色ID
* @param userIds 用户ID串
*/
@SaCheckPermission("system:role:edit")
@Log(title = "角色管理", businessType = BusinessType.GRANT)
@PutMapping("/authUser/cancelAll")
public R<Void> cancelAuthUserAll(Long roleId, Long[] userIds) {
return toAjax(roleService.deleteAuthUsers(roleId, userIds));
}
/**
* 批量选择用户授权
*
* @param roleId 角色ID
* @param userIds 用户ID串
*/
@SaCheckPermission("system:role:edit")
@Log(title = "角色管理", businessType = BusinessType.GRANT)
@PutMapping("/authUser/selectAll")
public R<Void> selectAuthUserAll(Long roleId, Long[] userIds) {
roleService.checkRoleDataScope(roleId);
return toAjax(roleService.insertAuthUsers(roleId, userIds));
}
/**
* 获取对应角色部门树列表
*
* @param roleId 角色ID
*/
@SaCheckPermission("system:role:list")
@GetMapping(value = "/deptTree/{roleId}")
public R<Map<String, Object>> roleDeptTreeselect(@PathVariable("roleId") Long roleId) {
Map<String, Object> ajax = new HashMap<>();
ajax.put("checkedKeys", deptService.selectDeptListByRoleId(roleId));
ajax.put("depts", deptService.selectDeptTreeList(new SysDept()));
return R.ok(ajax);
}
}
package com.dsk.web.controller.system;
import cn.dev33.satoken.annotation.SaCheckPermission;
import cn.dev33.satoken.annotation.SaCheckRole;
import cn.dev33.satoken.annotation.SaMode;
import cn.hutool.core.bean.BeanUtil;
import com.baomidou.lock.annotation.Lock4j;
import com.dsk.common.annotation.Log;
import com.dsk.common.annotation.RepeatSubmit;
import com.dsk.common.constant.TenantConstants;
import com.dsk.common.core.controller.BaseController;
import com.dsk.common.core.domain.PageQuery;
import com.dsk.common.core.domain.R;
import com.dsk.common.core.page.TableDataInfo;
import com.dsk.common.core.validate.AddGroup;
import com.dsk.common.core.validate.EditGroup;
import com.dsk.common.enums.BusinessType;
import com.dsk.common.tenant.helper.TenantHelper;
import com.dsk.common.utils.StringUtils;
import com.dsk.system.domain.SysUser;
import com.dsk.system.domain.bo.SysTenantAdminBo;
import com.dsk.system.domain.bo.SysTenantBo;
import com.dsk.system.domain.vo.SysTenantVo;
import com.dsk.system.service.ISysTenantService;
import com.dsk.system.service.ISysUserService;
import lombok.RequiredArgsConstructor;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
import java.util.Arrays;
/**
* 租户管理
*
* @author sxk
* @date 2023.08.22
*/
@Validated
@RequiredArgsConstructor
@RestController
@RequestMapping("/system/tenant")
public class SysTenantController extends BaseController {
private final ISysTenantService tenantService;
private final ISysUserService userService;
/**
* 查询租户列表
*/
@SaCheckRole(value = {TenantConstants.SUPER_ADMIN_ROLE_KEY, TenantConstants.DSK_TENANT_ADMIN_ROLE_KEY}, mode = SaMode.OR)
@SaCheckPermission(value = "system:tenant:list", orRole = "accountAdmin")
@GetMapping("/list")
public TableDataInfo<SysTenantVo> list(SysTenantBo bo, PageQuery pageQuery) {
return tenantService.queryPageList(bo, pageQuery);
}
// /**
// * 导出租户列表
// */
// @SaCheckRole(TenantConstants.SUPER_ADMIN_ROLE_KEY)
// @SaCheckPermission("system:tenant:export")
// @Log(title = "租户", businessType = BusinessType.EXPORT)
// @PostMapping("/export")
// public void export(SysTenantBo bo, HttpServletResponse response) {
// List<SysTenantVo> list = tenantService.queryList(bo);
// ExcelUtil.exportExcel(list, "租户", SysTenantVo.class, response);
// }
/**
* 获取租户详细信息
*
* @param id 主键
*/
@SaCheckRole(value = {TenantConstants.SUPER_ADMIN_ROLE_KEY, TenantConstants.DSK_TENANT_ADMIN_ROLE_KEY}, mode = SaMode.OR)
@SaCheckPermission(value = "system:tenant:query", orRole = "accountAdmin")
@GetMapping("/{id}")
public R<SysTenantVo> getInfo(@NotNull(message = "主键不能为空") @PathVariable Long id) {
return R.ok(tenantService.queryById(id));
}
/**
* 新增租户
*/
@SaCheckRole(value = {TenantConstants.SUPER_ADMIN_ROLE_KEY, TenantConstants.DSK_TENANT_ADMIN_ROLE_KEY}, mode = SaMode.OR)
@SaCheckPermission(value = "system:tenant:add", orRole = "accountAdmin")
@Log(title = "租户", businessType = BusinessType.INSERT)
@Lock4j
@RepeatSubmit()
@PostMapping()
public R<Void> add(@Validated(AddGroup.class) @RequestBody SysTenantBo bo) {
if (!tenantService.checkCompanyNameUnique(bo)) {
return R.fail("新增企业'" + bo.getCompanyName() + "'失败,企业名称已存在");
}
return toAjax(TenantHelper.ignore(() -> tenantService.insertByBo(bo)));
}
/**
* 新增企业普通管理员账号
*/
@SaCheckRole(value = {TenantConstants.SUPER_ADMIN_ROLE_KEY, TenantConstants.DSK_TENANT_ADMIN_ROLE_KEY}, mode = SaMode.OR)
@SaCheckPermission(value = "system:user:add")
@Log(title = "用户管理", businessType = BusinessType.INSERT)
@PostMapping("/addTenantAdmin")
public R<Void> addTenantAdmin(@Validated @RequestBody SysTenantAdminBo tenantAdminBo) {
//校验手机号是否已存在
if (TenantHelper.ignore(()->userService.checkTenantAdminPhoneUnique(tenantAdminBo))) {
return R.fail("新增用户'" + tenantAdminBo.getPhonenumber() + "'失败,手机号码已存在");
}
return toAjax(TenantHelper.ignore(() -> tenantService.addTenantAdmin(tenantAdminBo)));
//return R.ok("手机号验证成功");
}
/**
* 修改租户
*/
@SaCheckRole(value = {TenantConstants.SUPER_ADMIN_ROLE_KEY, TenantConstants.DSK_TENANT_ADMIN_ROLE_KEY}, mode = SaMode.OR)
@SaCheckPermission(value = "system:tenant:edit", orRole = "accountAdmin")
@Log(title = "租户", businessType = BusinessType.UPDATE)
@RepeatSubmit()
@PutMapping()
public R<Void> edit(@Validated(EditGroup.class) @RequestBody SysTenantBo bo) {
tenantService.checkTenantAllowed(bo.getTenantId());
if (!tenantService.checkCompanyNameUnique(bo)) {
return R.fail("修改企业'" + bo.getCompanyName() + "'失败,公司名称已存在");
}
return toAjax(tenantService.updateByBo(bo));
}
// /**
// * 状态修改
// */
// @SaCheckRole(TenantConstants.SUPER_ADMIN_ROLE_KEY)
// @SaCheckPermission("system:tenant:edit")
// @Log(title = "租户", businessType = BusinessType.UPDATE)
// @PutMapping("/changeStatus")
// public R<Void> changeStatus(@RequestBody SysTenantBo bo) {
// tenantService.checkTenantAllowed(bo.getTenantId());
// return toAjax(tenantService.updateTenantStatus(bo));
// }
/**
* 删除租户
*
* @param ids 主键串
*/
@SaCheckRole(value = {TenantConstants.SUPER_ADMIN_ROLE_KEY, TenantConstants.DSK_TENANT_ADMIN_ROLE_KEY}, mode = SaMode.OR)
@SaCheckPermission(value = "system:tenant:remove", orRole = "accountAdmin")
@Log(title = "租户", businessType = BusinessType.DELETE)
@DeleteMapping("/{ids}")
public R<Void> remove(@NotEmpty(message = "主键不能为空") @PathVariable Long[] ids) {
return toAjax(tenantService.deleteWithValidByIds(Arrays.asList(ids), Boolean.TRUE));
}
// /**
// * 动态切换租户
// *
// * @param tenantId 租户ID
// */
// @SaCheckRole(TenantConstants.SUPER_ADMIN_ROLE_KEY)
// @GetMapping("/dynamic/{tenantId}")
// public R<Void> dynamicTenant(@NotBlank(message = "租户ID不能为空") @PathVariable String tenantId) {
// TenantHelper.setDynamic(tenantId);
// return R.ok();
// }
/**
* 清除动态租户
*/
@SaCheckRole(value = {TenantConstants.SUPER_ADMIN_ROLE_KEY, TenantConstants.DSK_TENANT_ADMIN_ROLE_KEY}, mode = SaMode.OR)
@GetMapping("/dynamic/clear")
public R<Void> dynamicClear() {
TenantHelper.clearDynamic();
return R.ok();
}
/**
* 同步租户套餐
*
* @param tenantId 租户id
* @param packageId 套餐id
*/
@SaCheckRole(value = {TenantConstants.SUPER_ADMIN_ROLE_KEY, TenantConstants.DSK_TENANT_ADMIN_ROLE_KEY}, mode = SaMode.OR)
@SaCheckPermission(value = "system:tenant:edit", orRole = "accountAdmin")
@Log(title = "租户", businessType = BusinessType.UPDATE)
@GetMapping("/syncTenantPackage")
public R<Void> syncTenantPackage(@NotBlank(message = "企业ID不能为空") String tenantId,
@NotNull(message = "企业方案ID不能为空") Long packageId) {
return toAjax(TenantHelper.ignore(() -> tenantService.syncTenantPackage(tenantId, packageId)));
}
}
package com.dsk.web.controller.system;
import cn.dev33.satoken.annotation.SaCheckPermission;
import cn.dev33.satoken.annotation.SaCheckRole;
import cn.dev33.satoken.annotation.SaMode;
import cn.hutool.core.util.ObjectUtil;
import com.dsk.common.annotation.Log;
import com.dsk.common.annotation.RepeatSubmit;
import com.dsk.common.constant.TenantConstants;
import com.dsk.common.core.controller.BaseController;
import com.dsk.common.core.domain.PageQuery;
import com.dsk.common.core.domain.R;
import com.dsk.common.core.page.TableDataInfo;
import com.dsk.common.core.validate.AddGroup;
import com.dsk.common.core.validate.EditGroup;
import com.dsk.common.enums.BusinessType;
import com.dsk.system.domain.SysTenantPackage;
import com.dsk.system.domain.bo.SysTenantPackageBo;
import com.dsk.system.domain.vo.SysTenantPackageSelectVo;
import com.dsk.system.domain.vo.SysTenantPackageVo;
import com.dsk.system.service.ISysTenantPackageService;
import lombok.RequiredArgsConstructor;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
import java.util.Arrays;
import java.util.List;
/**
* 租户套餐管理
*
* @author sxk
* @date 2023.08.23
*/
@Validated
@RequiredArgsConstructor
@RestController
@RequestMapping("/system/tenant/package")
public class SysTenantPackageController extends BaseController {
@Resource
private final ISysTenantPackageService tenantPackageService;
/**
* 查询租户套餐列表
*/
@SaCheckRole(value = {TenantConstants.SUPER_ADMIN_ROLE_KEY, TenantConstants.DSK_TENANT_ADMIN_ROLE_KEY}, mode = SaMode.OR)
@SaCheckPermission(value = "system:tenantPackage:list", orRole = "accountAdmin")
@GetMapping("/list")
public TableDataInfo<SysTenantPackageVo> list(SysTenantPackageBo bo, PageQuery pageQuery) {
return tenantPackageService.queryPageList(bo, pageQuery);
}
/**
* 查询租户套餐下拉选列表
*/
@SaCheckRole(value = {TenantConstants.SUPER_ADMIN_ROLE_KEY, TenantConstants.DSK_TENANT_ADMIN_ROLE_KEY}, mode = SaMode.OR)
@SaCheckPermission(value = "system:tenantPackage:list", orRole = "accountAdmin")
@GetMapping("/selectList")
public R<List<SysTenantPackageSelectVo>> selectList() {
return R.ok(tenantPackageService.selectList());
}
// /**
// * 导出租户套餐列表
// */
// @SaCheckRole(TenantConstants.SUPER_ADMIN_ROLE_KEY)
// @SaCheckPermission("system:tenantPackage:export")
// @Log(title = "租户套餐", businessType = BusinessType.EXPORT)
// @PostMapping("/export")
// public void export(SysTenantPackageBo bo, HttpServletResponse response) {
// List<SysTenantPackageVo> list = tenantPackageService.queryList(bo);
// ExcelUtil.exportExcel(list, "租户套餐", SysTenantPackageVo.class, response);
// }
/**
* 获取租户套餐详细信息
*
* @param packageId 主键
*/
@SaCheckRole(value = {TenantConstants.SUPER_ADMIN_ROLE_KEY, TenantConstants.DSK_TENANT_ADMIN_ROLE_KEY}, mode = SaMode.OR)
@SaCheckPermission(value = "system:tenantPackage:query", orRole = "accountAdmin")
@GetMapping("/{packageId}")
public R<SysTenantPackageVo> getInfo(@NotNull(message = "主键不能为空") @PathVariable Long packageId) {
return R.ok(tenantPackageService.queryById(packageId));
}
/**
* 新增租户套餐
*/
@SaCheckRole(value = {TenantConstants.SUPER_ADMIN_ROLE_KEY, TenantConstants.DSK_TENANT_ADMIN_ROLE_KEY}, mode = SaMode.OR)
@SaCheckPermission(value = "system:tenantPackage:add", orRole = "accountAdmin")
@Log(title = "租户套餐", businessType = BusinessType.INSERT)
@RepeatSubmit()
@PostMapping()
public R<Void> add(@Validated(AddGroup.class) @RequestBody SysTenantPackageBo bo) {
if (tenantPackageService.checkDeptNameUnique(bo)) {
return R.fail("新增方案'" + bo.getPackageName() + "'失败,方案名称已存在");
}
return toAjax(tenantPackageService.insertByBo(bo));
}
/**
* 修改租户套餐
*/
@SaCheckRole(value = {TenantConstants.SUPER_ADMIN_ROLE_KEY, TenantConstants.DSK_TENANT_ADMIN_ROLE_KEY}, mode = SaMode.OR)
@SaCheckPermission(value = "system:tenantPackage:edit", orRole = "accountAdmin")
@Log(title = "租户套餐", businessType = BusinessType.UPDATE)
@RepeatSubmit()
@PutMapping()
public R<Void> edit(@Validated(EditGroup.class) @RequestBody SysTenantPackageBo bo) {
SysTenantPackage tenantPackage = tenantPackageService.selectTenantPackageByName(bo.getPackageName());
if (ObjectUtil.isNotNull(tenantPackage) && !tenantPackage.getPackageId().equals(bo.getPackageId())) {
return R.fail("修改方案'" + bo.getPackageName() + "'失败,方案名称已存在");
}
return toAjax(tenantPackageService.updateByBo(bo));
}
// /**
// * 状态修改
// */
// @SaCheckRole(TenantConstants.SUPER_ADMIN_ROLE_KEY)
// @SaCheckPermission("system:tenantPackage:edit")
// @Log(title = "租户套餐", businessType = BusinessType.UPDATE)
// @PutMapping("/changeStatus")
// public R<Void> changeStatus(@RequestBody SysTenantPackageBo bo) {
// return toAjax(tenantPackageService.updatePackageStatus(bo));
// }
/**
* 删除租户套餐
*
* @param packageIds 主键串
*/
@SaCheckRole(value = {TenantConstants.SUPER_ADMIN_ROLE_KEY, TenantConstants.DSK_TENANT_ADMIN_ROLE_KEY}, mode = SaMode.OR)
@SaCheckPermission(value = "system:tenantPackage:remove", orRole = "accountAdmin")
@Log(title = "租户套餐", businessType = BusinessType.DELETE)
@DeleteMapping("/{packageIds}")
public R<Void> remove(@NotEmpty(message = "主键不能为空") @PathVariable Long[] packageIds) {
return toAjax(tenantPackageService.deleteWithValidByIds(Arrays.asList(packageIds), true));
}
}
package com.dsk.web.controller.system;
import com.dsk.common.core.controller.BaseController;
import com.dsk.common.core.domain.PageQuery;
import com.dsk.common.core.domain.R;
import com.dsk.common.core.page.TableDataInfo;
import com.dsk.system.domain.SysUserFileRecord;
import com.dsk.system.service.ISysUserFileRecordService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
/**
* 系统用户文件记录
*
* @author lcl
* @create 2023/9/15
*/
@RestController
@RequestMapping("/user/file/record")
public class SysUserFileRecordController extends BaseController {
@Autowired
private ISysUserFileRecordService baseService;
/**
* 获取用户文件记录列表
*/
@GetMapping(value = "/list")
public TableDataInfo<SysUserFileRecord> userList(PageQuery page){
return baseService.userList(page);
}
/**
* 添加用户文件记录列表
*/
@PostMapping()
public R<Void> add(@RequestBody SysUserFileRecord bean){
return toAjax(baseService.add(bean));
}
}
package com.dsk.web.schedule;
import com.dsk.common.tenant.helper.TenantHelper;
import com.dsk.system.service.ISysTenantService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
/**
* 租户相关定时任务类
*
* @author sxk
* @date 2023.09.06
*/
@Component
@RequiredArgsConstructor
@Slf4j
public class TenantTimerTask {
private final ISysTenantService tenantService;
/**
* 每6小时扫描并禁用已过期租户账号
*/
@Scheduled(cron = "0 0/10 * * * ? ")
public void updateExpiredTenantStatus() {
log.info("执行定时禁用已过期租户账号(10min/次)");
TenantHelper.ignore(tenantService::handleExpiredTenant);
}
}
\ No newline at end of file
--- # 监控中心配置
spring.boot.admin.client:
# 增加客户端开关
enabled: true
url: http://localhost:9090/admin
instance:
service-host-type: IP
username: dsk-operate
password: dsk123123
--- # xxl-job 配置
xxl.job:
# 执行器开关
enabled: false
# 调度中心地址:如调度中心集群部署存在多个地址则用逗号分隔。
admin-addresses: http://localhost:9100/xxl-job-admin
# 执行器通讯TOKEN:非空时启用
access-token: xxl-job
executor:
# 执行器AppName:执行器心跳注册分组依据;为空则关闭自动注册
appname: xxl-job-executor
# 执行器端口号 执行器从9101开始往后写
port: 9101
# 执行器注册:默认IP:PORT
address:
# 执行器IP:默认自动获取IP
ip:
# 执行器运行日志文件存储磁盘路径
logpath: ./logs/xxl-job
# 执行器日志文件保存天数:大于3生效
logretentiondays: 30
--- # 数据源配置
spring:
datasource:
type: com.zaxxer.hikari.HikariDataSource
# 动态数据源文档 https://www.kancloud.cn/tracy5546/dynamic-datasource/content
dynamic:
# 性能分析插件(有性能损耗 不建议生产环境使用)
p6spy: false
# 设置默认的数据源或者数据源组,默认值即为 master
primary: master
# 严格模式 匹配不到数据源则报错
strict: true
datasource:
# 主库数据源
master:
type: ${spring.datasource.type}
driverClassName: com.mysql.cj.jdbc.Driver
# jdbc 所有参数配置参考 https://lionli.blog.csdn.net/article/details/122018562
# rewriteBatchedStatements=true 批处理优化 大幅提升批量插入更新删除性能(对数据库有性能损耗 使用批量操作应考虑性能问题)
# url: jdbc:mysql://192.168.0.79:3306/dsk_operate_sys?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
# username: dev
# password: 8AXjjCHZ666!
url: jdbc:mysql://rds-szh.dsk.com/dos?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
username: dsk_api_szh
password: j1nxbVPF0QS*B57C
# 从库数据源
# slave:
# lazy: true
# type: ${spring.datasource.type}
# driverClassName: com.mysql.cj.jdbc.Driver
# url: jdbc:mysql://localhost:3306/ry-vue?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true&rewriteBatchedStatements=true
# username:
# password:
# oracle:
# type: ${spring.datasource.type}
# driverClassName: oracle.jdbc.OracleDriver
# url: jdbc:oracle:thin:@//localhost:1521/XE
# username: ROOT
# password: root
# hikari:
# connectionTestQuery: SELECT 1 FROM DUAL
# postgres:
# type: ${spring.datasource.type}
# driverClassName: org.postgresql.Driver
# url: jdbc:postgresql://localhost:5432/postgres?useUnicode=true&characterEncoding=utf8&useSSL=true&autoReconnect=true&reWriteBatchedInserts=true
# username: root
# password: root
# sqlserver:
# type: ${spring.datasource.type}
# driverClassName: com.microsoft.sqlserver.jdbc.SQLServerDriver
# url: jdbc:sqlserver://localhost:1433;DatabaseName=tempdb;SelectMethod=cursor;encrypt=false;rewriteBatchedStatements=true
# username: SA
# password: root
hikari:
# 最大连接池数量
maxPoolSize: 20
# 最小空闲线程数量
minIdle: 10
# 配置获取连接等待超时的时间
connectionTimeout: 30000
# 校验超时时间
validationTimeout: 5000
# 空闲连接存活最大时间,默认10分钟
idleTimeout: 600000
# 此属性控制池中连接的最长生命周期,值0表示无限生命周期,默认30分钟
maxLifetime: 1800000
# 连接测试query(配置检测连接是否有效)
connectionTestQuery: SELECT 1
# 多久检查一次连接的活性
keepaliveTime: 30000
--- # redis 单机配置(单机与集群只能开启一个另一个需要注释掉)
spring:
redis:
host: redis-szh.dsk.com
# 端口,默认为6379
port: 6379
# 数据库索引
database: 13
# 密码
password: Xi8$r*@d7zn5FaP2
# 密码(如没有密码请注释掉)
# password:
# 连接超时时间
timeout: 10s
# 是否开启ssl
ssl: false
redisson:
# redis key前缀
keyPrefix:
# 线程池数量
threads: 4
# Netty线程池数量
nettyThreads: 8
# 单节点配置
singleServerConfig:
# 客户端名称
clientName: ${ruoyi.name}
# 最小空闲连接数
connectionMinimumIdleSize: 8
# 连接池大小
connectionPoolSize: 32
# 连接空闲超时,单位:毫秒
idleConnectionTimeout: 10000
# 命令等待超时,单位:毫秒
timeout: 3000
# 发布和订阅连接池大小
subscriptionConnectionPoolSize: 50
--- # mail 邮件发送
mail:
enabled: false
host: smtp.163.com
port: 465
# 是否需要用户名密码验证
auth: true
# 发送方,遵循RFC-822标准
from: xxx@163.com
# 用户名(注意:如果使用foxmail邮箱,此处user为qq号)
user: xxx@163.com
# 密码(注意,某些邮箱需要为SMTP服务单独设置密码,详情查看相关帮助)
pass: xxxxxxxxxx
# 使用 STARTTLS安全连接,STARTTLS是对纯文本通信协议的扩展。
starttlsEnable: true
# 使用SSL安全连接
sslEnable: true
# SMTP超时时长,单位毫秒,缺省值不超时
timeout: 0
# Socket连接超时值,单位毫秒,缺省值不超时
connectionTimeout: 0
--- # sms 短信 支持 阿里云 腾讯云 云片 等等各式各样的短信服务商
# https://wind.kim/doc/start 文档地址 各个厂商可同时使用
sms:
# 阿里云 dysmsapi.aliyuncs.com
alibaba:
#请求地址 默认为 dysmsapi.aliyuncs.com 如无特殊改变可以不用设置
requestUrl: dysmsapi.aliyuncs.com
#阿里云的accessKey
accessKeyId: LTAIC4Pb2jJZbUmD
#阿里云的accessKeySecret
accessKeySecret: L7VANFwqaTludkczLsg9jhvidk0e28
#短信签名
signature: 大司空信息科技
tencent:
#请求地址默认为 sms.tencentcloudapi.com 如无特殊改变可不用设置
requestUrl: sms.tencentcloudapi.com
#腾讯云的accessKey
accessKeyId: xxxxxxx
#腾讯云的accessKeySecret
accessKeySecret: xxxxxxx
#短信签名
signature: 测试
#短信sdkAppId
sdkAppId: appid
#地域信息默认为 ap-guangzhou 如无特殊改变可不用设置
territory: ap-guangzhou
dsk:
exportBackUrl: https://szhapi.jiansheku.com/export/backUrl
\ No newline at end of file
--- # 监控中心配置
spring.boot.admin.client:
# 增加客户端开关
enabled: false
url: http://localhost:9090/admin
instance:
service-host-type: IP
username: ruoyi
password: 123456
--- # xxl-job 配置
xxl.job:
# 执行器开关
enabled: false
# 调度中心地址:如调度中心集群部署存在多个地址则用逗号分隔。
admin-addresses: http://localhost:9100/xxl-job-admin
# 执行器通讯TOKEN:非空时启用
access-token: xxl-job
executor:
# 执行器AppName:执行器心跳注册分组依据;为空则关闭自动注册
appname: xxl-job-executor
# 执行器端口号 执行器从9101开始往后写
port: 9101
# 执行器注册:默认IP:PORT
address:
# 执行器IP:默认自动获取IP
ip:
# 执行器运行日志文件存储磁盘路径
logpath: ./logs/xxl-job
# 执行器日志文件保存天数:大于3生效
logretentiondays: 30
--- # 数据源配置
spring:
datasource:
type: com.zaxxer.hikari.HikariDataSource
# 动态数据源文档 https://www.kancloud.cn/tracy5546/dynamic-datasource/content
dynamic:
# 性能分析插件(有性能损耗 不建议生产环境使用)
p6spy: true
# 设置默认的数据源或者数据源组,默认值即为 master
primary: master
# 严格模式 匹配不到数据源则报错
strict: true
datasource:
# 主库数据源
master:
type: ${spring.datasource.type}
driverClassName: com.mysql.cj.jdbc.Driver
# jdbc 所有参数配置参考 https://lionli.blog.csdn.net/article/details/122018562
# rewriteBatchedStatements=true 批处理优化 大幅提升批量插入更新删除性能(对数据库有性能损耗 使用批量操作应考虑性能问题)
url: jdbc:mysql://120.46.64.239:3306/dsk_operate?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
username: dskdev
password: encPHwi8dbKeL8ZX
# 从库数据源
# slave:
# lazy: true
# type: ${spring.datasource.type}
# driverClassName: com.mysql.cj.jdbc.Driver
# url: jdbc:mysql://localhost:3306/ry-vue?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true&rewriteBatchedStatements=true
# username:
# password:
# oracle:
# type: ${spring.datasource.type}
# driverClassName: oracle.jdbc.OracleDriver
# url: jdbc:oracle:thin:@//localhost:1521/XE
# username: ROOT
# password: root
# hikari:
# connectionTestQuery: SELECT 1 FROM DUAL
# postgres:
# type: ${spring.datasource.type}
# driverClassName: org.postgresql.Driver
# url: jdbc:postgresql://localhost:5432/postgres?useUnicode=true&characterEncoding=utf8&useSSL=true&autoReconnect=true&reWriteBatchedInserts=true
# username: root
# password: root
# sqlserver:
# type: ${spring.datasource.type}
# driverClassName: com.microsoft.sqlserver.jdbc.SQLServerDriver
# url: jdbc:sqlserver://localhost:1433;DatabaseName=tempdb;SelectMethod=cursor;encrypt=false;rewriteBatchedStatements=true
# username: SA
# password: root
hikari:
# 最大连接池数量
maxPoolSize: 20
# 最小空闲线程数量
minIdle: 10
# 配置获取连接等待超时的时间
connectionTimeout: 30000
# 校验超时时间
validationTimeout: 5000
# 空闲连接存活最大时间,默认10分钟
idleTimeout: 600000
# 此属性控制池中连接的最长生命周期,值0表示无限生命周期,默认30分钟
maxLifetime: 1800000
# 连接测试query(配置检测连接是否有效)
connectionTestQuery: SELECT 1
# 多久检查一次连接的活性
keepaliveTime: 30000
--- # redis 单机配置(单机与集群只能开启一个另一个需要注释掉)
spring:
redis:
host: 120.46.64.239
# 端口,默认为6379
port: 6379
# 数据库索引
database: 13
# 密码
password: zfTFIJjaN#6xB83r
# 密码(如没有密码请注释掉)
# password:
# 连接超时时间
timeout: 10s
# 是否开启ssl
ssl: false
redisson:
# redis key前缀
keyPrefix:
# 线程池数量
threads: 4
# Netty线程池数量
nettyThreads: 8
# 单节点配置
singleServerConfig:
# 客户端名称
clientName: ${ruoyi.name}
# 最小空闲连接数
connectionMinimumIdleSize: 8
# 连接池大小
connectionPoolSize: 32
# 连接空闲超时,单位:毫秒
idleConnectionTimeout: 10000
# 命令等待超时,单位:毫秒
timeout: 3000
# 发布和订阅连接池大小
subscriptionConnectionPoolSize: 50
--- # mail 邮件发送
mail:
enabled: false
host: smtp.163.com
port: 465
# 是否需要用户名密码验证
auth: true
# 发送方,遵循RFC-822标准
from: xxx@163.com
# 用户名(注意:如果使用foxmail邮箱,此处user为qq号)
user: xxx@163.com
# 密码(注意,某些邮箱需要为SMTP服务单独设置密码,详情查看相关帮助)
pass: xxxxxxxxxx
# 使用 STARTTLS安全连接,STARTTLS是对纯文本通信协议的扩展。
starttlsEnable: true
# 使用SSL安全连接
sslEnable: true
# SMTP超时时长,单位毫秒,缺省值不超时
timeout: 0
# Socket连接超时值,单位毫秒,缺省值不超时
connectionTimeout: 0
--- # sms 短信 支持 阿里云 腾讯云 云片 等等各式各样的短信服务商
# https://wind.kim/doc/start 文档地址 各个厂商可同时使用
sms:
# 阿里云 dysmsapi.aliyuncs.com
alibaba:
#请求地址 默认为 dysmsapi.aliyuncs.com 如无特殊改变可以不用设置
requestUrl: dysmsapi.aliyuncs.com
#阿里云的accessKey
accessKeyId: LTAIC4Pb2jJZbUmD
#阿里云的accessKeySecret
accessKeySecret: L7VANFwqaTludkczLsg9jhvidk0e28
#短信签名
signature: 大司空信息科技
tencent:
#请求地址默认为 sms.tencentcloudapi.com 如无特殊改变可不用设置
requestUrl: sms.tencentcloudapi.com
#腾讯云的accessKey
accessKeyId: xxxxxxx
#腾讯云的accessKeySecret
accessKeySecret: xxxxxxx
#短信签名
signature: 测试
#短信sdkAppId
sdkAppId: appid
#地域信息默认为 ap-guangzhou 如无特殊改变可不用设置
territory: ap-guangzhou
dsk:
exportBackUrl: http://120.46.64.239:9099/prod-api/export/backUrl
sa-token:
# 是否允许同一账号并发登录 (为true时允许一起登录, 为false时新登录挤掉旧登录)
is-concurrent: true
# 在多人登录同一账号时,是否共用一个token (为true时所有登录共用一个token, 为false时每次登录新建一个token)
is-share: true
\ No newline at end of file
# 项目相关配置
ruoyi:
# 名称
name: dsk-operate-sys
# 版本
version: ${dsk-operate-sys.version}
# 版权年份
copyrightYear: 2022
# 实例演示开关
demoEnabled: false
# 获取ip地址开关
addressEnabled: true
# 缓存懒加载
cacheLazy: false
captcha:
enable: true
# 页面 <参数设置> 可开启关闭 验证码校验
# 验证码类型 math 数组计算 char 字符验证
type: CHAR
# line 线段干扰 circle 圆圈干扰 shear 扭曲干扰
category: CIRCLE
# 数字验证码位数
numberLength: 1
# 字符验证码长度
charLength: 4
# 开发环境配置
server:
# 服务器的HTTP端口,默认为8080
port: 9098
servlet:
# 应用的访问路径
context-path: /
# undertow 配置
undertow:
# HTTP post内容的最大大小。当值为-1时,默认值为大小是无限的
max-http-post-size: -1
# 以下的配置会影响buffer,这些buffer会用于服务器连接的IO操作,有点类似netty的池化内存管理
# 每块buffer的空间大小,越小的空间被利用越充分
buffer-size: 512
# 是否分配的直接内存
direct-buffers: true
threads:
# 设置IO线程数, 它主要执行非阻塞的任务,它们会负责多个连接, 默认设置每个CPU核心一个线程
io: 8
# 阻塞任务线程池, 当执行类似servlet请求阻塞操作, undertow会从这个线程池中取得线程,它的值设置取决于系统的负载
worker: 256
# 日志配置
logging:
level:
com.dsk: @logging.level@
org.springframework: warn
config: classpath:logback-plus.xml
# 用户配置
user:
password:
# 密码最大错误次数
maxRetryCount: 5
# 密码锁定时间(默认10分钟)
lockTime: 10
# Spring配置
spring:
application:
name: ${ruoyi.name}
# 资源信息
messages:
# 国际化资源文件路径
basename: i18n/messages_zh_CN
profiles:
active: @profiles.active@
# 文件上传
servlet:
multipart:
# 单个文件大小
max-file-size: 20MB
# 设置总上传的文件大小
max-request-size: 50MB
# 服务模块
devtools:
restart:
# 热部署开关
enabled: true
mvc:
format:
date-time: yyyy-MM-dd HH:mm:ss
jackson:
# 日期格式化
date-format: yyyy-MM-dd HH:mm:ss
serialization:
# 格式化输出
indent_output: false
# 忽略无法转换的对象
fail_on_empty_beans: false
deserialization:
# 允许对象忽略json中不存在的属性
fail_on_unknown_properties: false
main:
allow-circular-references: true
# Sa-Token配置
sa-token:
# token名称 (同时也是cookie名称)
token-name: Authorization
# token有效期 设为一天 (必定过期) 单位: 秒
timeout: 28800
# 多端不同 token 有效期 可查看 LoginHelper.loginByDevice 方法自定义
# token最低活跃时间 (指定时间无操作就过期) 单位: 秒
active-timeout: 18000
# 是否允许同一账号并发登录 (为true时允许一起登录, 为false时新登录挤掉旧登录)
is-concurrent: false
# 在多人登录同一账号时,是否共用一个token (为true时所有登录共用一个token, 为false时每次登录新建一个token)
is-share: false
# 是否尝试从header里读取token
is-read-header: true
# 是否尝试从cookie里读取token
is-read-cookie: false
# token前缀
token-prefix: "Bearer"
# jwt秘钥
jwt-secret-key: abcdefghijklmnopqrstuvwxyz
# security配置
security:
# 排除路径
excludes:
# 静态资源
- /*.html
- /**/*.html
- /**/*.css
- /**/*.js
# 公共路径
- /favicon.ico
- /error
# swagger 文档配置
- /*/api-docs
- /*/api-docs/**
# actuator 监控配置
- /api/**
- /actuator/**
# 多租户配置
tenant:
# 是否开启
enable: true
# 排除表
excludes:
- sys_menu
- sys_tenant
- sys_tenant_package
- sys_role_dept
- sys_role_menu
- sys_user_post
- sys_user_role
- sys_client
- sys_region
- sys_user_file_record
- sys_oss_config
- sys_region
- contact_info
- customer_user
- customer_follow_record
- business_user
- business_backlog
- business_follow_record
- business_label
- business_relate_company
- business_open_tender
# MyBatisPlus配置
# https://baomidou.com/config/
mybatis-plus:
# 不支持多包, 如有需要可在注解配置 或 提升扫包等级
# 例如 com.**.**.mapper
mapperPackage: com.dsk.**.mapper
# 对应的 XML 文件位置
mapperLocations: classpath*:mapper/**/*Mapper.xml
# 实体扫描,多个package用逗号或者分号分隔
typeAliasesPackage: com.dsk.**.domain
# 启动时是否检查 MyBatis XML 文件的存在,默认不检查
checkConfigLocation: false
configuration:
# 自动驼峰命名规则(camel case)映射
mapUnderscoreToCamelCase: true
# MyBatis 自动映射策略
# NONE:不启用 PARTIAL:只对非嵌套 resultMap 自动映射 FULL:对所有 resultMap 自动映射
autoMappingBehavior: PARTIAL
# MyBatis 自动映射时未知列或未知属性处理策
# NONE:不做处理 WARNING:打印相关警告 FAILING:抛出异常和详细信息
autoMappingUnknownColumnBehavior: NONE
# 更详细的日志输出 会有性能损耗 org.apache.ibatis.logging.stdout.StdOutImpl
# 关闭日志记录 (可单纯使用 p6spy 分析) org.apache.ibatis.logging.nologging.NoLoggingImpl
# 默认日志输出 org.apache.ibatis.logging.slf4j.Slf4jImpl
logImpl: org.apache.ibatis.logging.stdout.StdOutImpl
global-config:
# 是否打印 Logo banner
banner: true
dbConfig:
# 主键类型
# AUTO 自增 NONE 空 INPUT 用户输入 ASSIGN_ID 雪花 ASSIGN_UUID 唯一 UUID
idType: ASSIGN_ID
# 逻辑已删除值
logicDeleteValue: 2
# 逻辑未删除值
logicNotDeleteValue: 0
# 字段验证策略之 insert,在 insert 的时候的字段验证策略
# IGNORED 忽略 NOT_NULL 非NULL NOT_EMPTY 非空 DEFAULT 默认 NEVER 不加入 SQL
insertStrategy: NOT_NULL
# 字段验证策略之 update,在 update 的时候的字段验证策略
updateStrategy: NOT_NULL
# 字段验证策略之 select,在 select 的时候的字段验证策略既 wrapper 根据内部 entity 生成的 where 条件
where-strategy: NOT_NULL
# 数据加密
mybatis-encryptor:
# 是否开启加密
enable: false
# 默认加密算法
algorithm: BASE64
# 编码方式 BASE64/HEX。默认BASE64
encode: BASE64
# 安全秘钥 对称算法的秘钥 如:AES,SM4
password:
# 公私钥 非对称算法的公私钥 如:SM2,RSA
publicKey:
privateKey:
springdoc:
api-docs:
# 是否开启接口文档
enabled: false
# swagger-ui:
# # 持久化认证数据
# persistAuthorization: true
info:
# 标题
title: '标题:${ruoyi.name}后台管理系统_接口文档'
# 描述
description: '描述:用于管理集团旗下公司的人员信息,具体包括XXX,XXX模块...'
# 版本
version: '版本号: ${dsk-operate-sys.version}'
# 作者信息
contact:
name: Lion Li
email: crazylionli@163.com
url: https://gitee.com/dromara/dsk-operate-sys
components:
# 鉴权方式配置
security-schemes:
apiKey:
type: APIKEY
in: HEADER
name: ${sa-token.token-name}
#这里定义了两个分组,可定义多个,也可以不定义
group-configs:
- group: 1.演示模块
packages-to-scan: com.dsk.demo
- group: 2.系统模块
packages-to-scan: com.dsk.web
- group: 3.代码生成模块
packages-to-scan: com.dsk.generator
# 防止XSS攻击
xss:
# 过滤开关
enabled: true
# 排除链接(多个用逗号分隔)
excludes: /system/notice
# 匹配链接
urlPatterns: /system/*,/monitor/*,/tool/*
# 全局线程池相关配置
thread-pool:
# 是否开启线程池
enabled: false
# 队列最大长度
queueCapacity: 128
# 线程池维护线程所允许的空闲时间
keepAliveSeconds: 300
--- # 分布式锁 lock4j 全局配置
lock4j:
# 获取分布式锁超时时间,默认为 3000 毫秒
acquire-timeout: 3000
# 分布式锁的超时时间,默认为 30 秒
expire: 30000
--- # Actuator 监控端点的配置项
management:
endpoints:
enabled-by-default: false
web:
exposure:
include: '*'
endpoint:
health:
show-details: ALWAYS
logfile:
external-file: ./logs/sys-console.log
gv:
img-path: D:\dsk\dsk-operate-sys\
#spring:
# datasource:
# type: com.alibaba.druid.pool.DruidDataSource
# druid:
# driver-class-name: com.mysql.cj.jdbc.Driver
# username: ${MYSQL_USER:root}
# password: ${MYSQL_PWD:root}
# url: jdbc:mysql://${MYSQL_HOST:pigx-mysql}:${MYSQL_PORT:3306}/${MYSQL_DB:pigxx_report}?characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=GMT%2B8&allowMultiQueries=true&allowPublicKeyRetrieval=true
# jpa:
# database-platform:
# org.hibernate.dialect.MySQL8Dialectsecurity:
# oauth2:
# client:
# ignore-urls:
# - /
# - /api/project/getData
# - /static/**
# - /api/project/get-file/*
# # 文件上传路径
Application Version: ${dsk-operate-sys.version}
Spring Boot Version: ${spring-boot.version}
#错误消息
not.null=* 必须填写
user.jcaptcha.error=验证码错误
user.jcaptcha.expire=验证码已失效
user.not.exists=对不起, 您的账号:{0} 不存在.
user.password.not.match=用户不存在/密码错误
user.password.retry.limit.count=密码输入错误{0}次
user.password.retry.limit.exceed=密码输入错误{0}次,帐户锁定{1}分钟
user.password.delete=对不起,您的账号:{0} 已被删除
user.blocked=对不起,您的账号:{0} 已禁用,请联系管理员
role.blocked=角色已封禁,请联系管理员
user.logout.success=退出成功
length.not.valid=长度必须在{min}到{max}个字符之间
user.username.not.blank=用户名不能为空
user.username.not.valid=* 2到20个汉字、字母、数字或下划线组成,且必须以非数字开头
user.username.length.valid=账户长度必须在{min}到{max}个字符之间
user.password.not.blank=用户密码不能为空
user.password.length.valid=用户密码长度必须在{min}到{max}个字符之间
user.password.not.valid=* 5-50个字符
user.email.not.valid=邮箱格式错误
user.email.not.blank=邮箱不能为空
user.phonenumber.not.blank=用户手机号不能为空
user.mobile.phone.number.not.valid=手机号格式错误
user.login.success=登录成功
user.register.success=注册成功
user.register.save.error=保存用户 {0} 失败,注册账号已存在
user.register.error=注册失败,请联系系统管理人员
user.notfound=请重新登录
user.forcelogout=管理员强制退出,请重新登录
user.unknown.error=未知错误,请重新登录
##文件上传消息
upload.exceed.maxSize=上传的文件大小超出限制的文件大小!<br/>允许的文件最大大小是:{0}MB!
upload.filename.exceed.length=上传的文件名最长{0}个字符
##权限
no.permission=您没有数据的权限,请联系管理员添加权限 [{0}]
no.create.permission=您没有创建数据的权限,请联系管理员添加权限 [{0}]
no.update.permission=您没有修改数据的权限,请联系管理员添加权限 [{0}]
no.delete.permission=您没有删除数据的权限,请联系管理员添加权限 [{0}]
no.export.permission=您没有导出数据的权限,请联系管理员添加权限 [{0}]
no.view.permission=您没有查看数据的权限,请联系管理员添加权限 [{0}]
repeat.submit.message=不允许重复提交,请稍候再试
rate.limiter.message=访问过于频繁,请稍候再试
sms.code.not.blank=短信验证码不能为空
sms.code.retry.limit.count=短信验证码输入错误{0}次
sms.code.retry.limit.exceed=短信验证码输入错误{0}次,帐户锁定{1}分钟
email.code.not.blank=邮箱验证码不能为空
email.code.retry.limit.count=邮箱验证码输入错误{0}次
email.code.retry.limit.exceed=邮箱验证码输入错误{0}次,帐户锁定{1}分钟
xcx.code.not.blank=小程序code不能为空
#\u9519\u8BEF\u6D88\u606F
not.null=* Required fill in
user.jcaptcha.error=Captcha error
user.jcaptcha.expire=Captcha invalid
user.not.exists=Sorry, your account: {0} does not exist
user.password.not.match=User does not exist/Password error
user.password.retry.limit.count=Password input error {0} times
user.password.retry.limit.exceed=Password input error {0} times, account locked for {1} minutes
user.password.delete=Sorry, your account\uFF1A{0} has been deleted
user.blocked=Sorry, your account: {0} has been disabled. Please contact the administrator
role.blocked=Role disabled\uFF0Cplease contact administrators
user.logout.success=Exit successful
length.not.valid=The length must be between {min} and {max} characters
user.username.not.blank=Username cannot be blank
user.username.not.valid=* 2 to 20 chinese characters, letters, numbers or underscores, and must start with a non number
user.username.length.valid=Account length must be between {min} and {max} characters
user.password.not.blank=Password cannot be empty
user.password.length.valid=Password length must be between {min} and {max} characters
user.password.not.valid=* 5-50 characters
user.email.not.valid=Mailbox format error
user.email.not.blank=Mailbox cannot be blank
user.phonenumber.not.blank=Phone number cannot be blank
user.mobile.phone.number.not.valid=Phone number format error
user.login.success=Login successful
user.register.success=Register successful
user.register.save.error=Failed to save user {0}, The registered account already exists
user.register.error=Register failed, please contact system administrator
user.notfound=Please login again
user.forcelogout=The administrator is forced to exit\uFF0Cplease login again
user.unknown.error=Unknown error, please login again
##\u6587\u4EF6\u4E0A\u4F20\u6D88\u606F
upload.exceed.maxSize=The uploaded file size exceeds the limit file size\uFF01<br/>the maximum allowed file size is\uFF1A{0}MB\uFF01
upload.filename.exceed.length=The maximum length of uploaded file name is {0} characters
##\u6743\u9650
no.permission=You do not have permission to the data\uFF0Cplease contact your administrator to add permissions [{0}]
no.create.permission=You do not have permission to create data\uFF0Cplease contact your administrator to add permissions [{0}]
no.update.permission=You do not have permission to modify data\uFF0Cplease contact your administrator to add permissions [{0}]
no.delete.permission=You do not have permission to delete data\uFF0Cplease contact your administrator to add permissions [{0}]
no.export.permission=You do not have permission to export data\uFF0Cplease contact your administrator to add permissions [{0}]
no.view.permission=You do not have permission to view data\uFF0Cplease contact your administrator to add permissions [{0}]
repeat.submit.message=Repeat submit is not allowed, please try again later
rate.limiter.message=Visit too frequently, please try again later
sms.code.not.blank=Sms code cannot be blank
sms.code.retry.limit.count=Sms code input error {0} times
sms.code.retry.limit.exceed=Sms code input error {0} times, account locked for {1} minutes
email.code.not.blank=Email code cannot be blank
email.code.retry.limit.count=Email code input error {0} times
email.code.retry.limit.exceed=Email code input error {0} times, account locked for {1} minutes
xcx.code.not.blank=Mini program code cannot be blank
##\u79DF\u6237
company.number.not.blank=company number cannot be blank
company.not.exists=Sorry, your company does not exist. Please contact the administrator
company.blocked=Sorry, your company is disabled. Please contact the administrator
company.expired=Sorry, your company has expired. Please contact the administrator.
\ No newline at end of file
#\u9519\u8BEF\u6D88\u606F
not.null=* \u5FC5\u987B\u586B\u5199
user.jcaptcha.error=\u9A8C\u8BC1\u7801\u9519\u8BEF
user.jcaptcha.expire=\u9A8C\u8BC1\u7801\u5DF2\u5931\u6548
user.not.exists=\u5BF9\u4E0D\u8D77, \u60A8\u7684\u8D26\u53F7\uFF1A{0} \u4E0D\u5B58\u5728.
user.password.not.match=\u7528\u6237\u4E0D\u5B58\u5728/\u5BC6\u7801\u9519\u8BEF
user.password.retry.limit.count=\u5BC6\u7801\u8F93\u5165\u9519\u8BEF{0}\u6B21
user.password.retry.limit.exceed=\u5BC6\u7801\u8F93\u5165\u9519\u8BEF{0}\u6B21\uFF0C\u5E10\u6237\u9501\u5B9A{1}\u5206\u949F
user.password.delete=\u5BF9\u4E0D\u8D77\uFF0C\u60A8\u7684\u8D26\u53F7\uFF1A{0} \u5DF2\u88AB\u5220\u9664
user.blocked=\u5BF9\u4E0D\u8D77\uFF0C\u60A8\u7684\u8D26\u53F7\uFF1A{0} \u5DF2\u7981\u7528\uFF0C\u8BF7\u8054\u7CFB\u7BA1\u7406\u5458
role.blocked=\u89D2\u8272\u5DF2\u5C01\u7981\uFF0C\u8BF7\u8054\u7CFB\u7BA1\u7406\u5458
user.logout.success=\u9000\u51FA\u6210\u529F
length.not.valid=\u957F\u5EA6\u5FC5\u987B\u5728{min}\u5230{max}\u4E2A\u5B57\u7B26\u4E4B\u95F4
user.username.not.blank=\u7528\u6237\u540D\u4E0D\u80FD\u4E3A\u7A7A
user.username.not.valid=* 2\u523020\u4E2A\u6C49\u5B57\u3001\u5B57\u6BCD\u3001\u6570\u5B57\u6216\u4E0B\u5212\u7EBF\u7EC4\u6210\uFF0C\u4E14\u5FC5\u987B\u4EE5\u975E\u6570\u5B57\u5F00\u5934
user.username.length.valid=\u8D26\u6237\u957F\u5EA6\u5FC5\u987B\u5728{min}\u5230{max}\u4E2A\u5B57\u7B26\u4E4B\u95F4
user.password.not.blank=\u7528\u6237\u5BC6\u7801\u4E0D\u80FD\u4E3A\u7A7A
user.password.length.valid=\u7528\u6237\u5BC6\u7801\u957F\u5EA6\u5FC5\u987B\u5728{min}\u5230{max}\u4E2A\u5B57\u7B26\u4E4B\u95F4
user.password.not.valid=* 5-50\u4E2A\u5B57\u7B26
user.email.not.valid=\u90AE\u7BB1\u683C\u5F0F\u9519\u8BEF
user.email.not.blank=\u90AE\u7BB1\u4E0D\u80FD\u4E3A\u7A7A
user.phonenumber.not.blank=\u7528\u6237\u624B\u673A\u53F7\u4E0D\u80FD\u4E3A\u7A7A
user.mobile.phone.number.not.valid=\u624B\u673A\u53F7\u683C\u5F0F\u9519\u8BEF
user.login.success=\u767B\u5F55\u6210\u529F
user.register.success=\u6CE8\u518C\u6210\u529F
user.register.save.error=\u4FDD\u5B58\u7528\u6237 {0} \u5931\u8D25\uFF0C\u6CE8\u518C\u8D26\u53F7\u5DF2\u5B58\u5728
user.register.error=\u6CE8\u518C\u5931\u8D25\uFF0C\u8BF7\u8054\u7CFB\u7CFB\u7EDF\u7BA1\u7406\u4EBA\u5458
user.notfound=\u8BF7\u91CD\u65B0\u767B\u5F55
user.forcelogout=\u7BA1\u7406\u5458\u5F3A\u5236\u9000\u51FA\uFF0C\u8BF7\u91CD\u65B0\u767B\u5F55
user.unknown.error=\u672A\u77E5\u9519\u8BEF\uFF0C\u8BF7\u91CD\u65B0\u767B\u5F55
##\u6587\u4EF6\u4E0A\u4F20\u6D88\u606F
upload.exceed.maxSize=\u4E0A\u4F20\u7684\u6587\u4EF6\u5927\u5C0F\u8D85\u51FA\u9650\u5236\u7684\u6587\u4EF6\u5927\u5C0F\uFF01<br/>\u5141\u8BB8\u7684\u6587\u4EF6\u6700\u5927\u5927\u5C0F\u662F\uFF1A{0}MB\uFF01
upload.filename.exceed.length=\u4E0A\u4F20\u7684\u6587\u4EF6\u540D\u6700\u957F{0}\u4E2A\u5B57\u7B26
##\u6743\u9650
no.permission=\u60A8\u6CA1\u6709\u6570\u636E\u7684\u6743\u9650\uFF0C\u8BF7\u8054\u7CFB\u7BA1\u7406\u5458\u6DFB\u52A0\u6743\u9650 [{0}]
no.create.permission=\u60A8\u6CA1\u6709\u521B\u5EFA\u6570\u636E\u7684\u6743\u9650\uFF0C\u8BF7\u8054\u7CFB\u7BA1\u7406\u5458\u6DFB\u52A0\u6743\u9650 [{0}]
no.update.permission=\u60A8\u6CA1\u6709\u4FEE\u6539\u6570\u636E\u7684\u6743\u9650\uFF0C\u8BF7\u8054\u7CFB\u7BA1\u7406\u5458\u6DFB\u52A0\u6743\u9650 [{0}]
no.delete.permission=\u60A8\u6CA1\u6709\u5220\u9664\u6570\u636E\u7684\u6743\u9650\uFF0C\u8BF7\u8054\u7CFB\u7BA1\u7406\u5458\u6DFB\u52A0\u6743\u9650 [{0}]
no.export.permission=\u60A8\u6CA1\u6709\u5BFC\u51FA\u6570\u636E\u7684\u6743\u9650\uFF0C\u8BF7\u8054\u7CFB\u7BA1\u7406\u5458\u6DFB\u52A0\u6743\u9650 [{0}]
no.view.permission=\u60A8\u6CA1\u6709\u67E5\u770B\u6570\u636E\u7684\u6743\u9650\uFF0C\u8BF7\u8054\u7CFB\u7BA1\u7406\u5458\u6DFB\u52A0\u6743\u9650 [{0}]
repeat.submit.message=\u4E0D\u5141\u8BB8\u91CD\u590D\u63D0\u4EA4\uFF0C\u8BF7\u7A0D\u5019\u518D\u8BD5
rate.limiter.message=\u8BBF\u95EE\u8FC7\u4E8E\u9891\u7E41\uFF0C\u8BF7\u7A0D\u5019\u518D\u8BD5
sms.code.not.blank=\u77ED\u4FE1\u9A8C\u8BC1\u7801\u4E0D\u80FD\u4E3A\u7A7A
sms.code.retry.limit.count=\u77ED\u4FE1\u9A8C\u8BC1\u7801\u8F93\u5165\u9519\u8BEF{0}\u6B21
sms.code.retry.limit.exceed=\u77ED\u4FE1\u9A8C\u8BC1\u7801\u8F93\u5165\u9519\u8BEF{0}\u6B21\uFF0C\u5E10\u6237\u9501\u5B9A{1}\u5206\u949F
email.code.not.blank=\u90AE\u7BB1\u9A8C\u8BC1\u7801\u4E0D\u80FD\u4E3A\u7A7A
email.code.retry.limit.count=\u90AE\u7BB1\u9A8C\u8BC1\u7801\u8F93\u5165\u9519\u8BEF{0}\u6B21
email.code.retry.limit.exceed=\u90AE\u7BB1\u9A8C\u8BC1\u7801\u8F93\u5165\u9519\u8BEF{0}\u6B21\uFF0C\u5E10\u6237\u9501\u5B9A{1}\u5206\u949F
xcx.code.not.blank=\u5C0F\u7A0B\u5E8Fcode\u4E0D\u80FD\u4E3A\u7A7A
##\u79DF\u6237
company.number.not.blank=\u4F01\u4E1A\u7F16\u53F7\u4E0D\u80FD\u4E3A\u7A7A
company.not.exists=\u5BF9\u4E0D\u8D77, \u60A8\u7684\u4F01\u4E1A\u4E0D\u5B58\u5728\uFF0C\u8BF7\u8054\u7CFB\u7BA1\u7406\u5458
company.blocked=\u5BF9\u4E0D\u8D77\uFF0C\u60A8\u7684\u4F01\u4E1A\u5DF2\u505C\u7528\uFF0C\u8BF7\u8054\u7CFB\u7BA1\u7406\u5458
company.expired=\u5BF9\u4E0D\u8D77\uFF0C\u60A8\u7684\u4F01\u4E1A\u5DF2\u8FC7\u671F\uFF0C\u8BF7\u8054\u7CFB\u7BA1\u7406\u5458
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<property name="log.path" value="./logs"/>
<property name="console.log.pattern"
value="%red(%d{yyyy-MM-dd HH:mm:ss}) %green([%thread]) %highlight(%-5level) %boldMagenta(%logger{36}%n) - %msg%n"/>
<property name="log.pattern" value="%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n"/>
<!-- 控制台输出 -->
<appender name="console" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>${console.log.pattern}</pattern>
<charset>utf-8</charset>
</encoder>
</appender>
<!-- 控制台输出 -->
<appender name="file_console" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${log.path}/sys-console.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!-- 日志文件名格式 -->
<fileNamePattern>${log.path}/sys-console.%d{yyyy-MM-dd}.log</fileNamePattern>
<!-- 日志最大 1天 -->
<maxHistory>1</maxHistory>
</rollingPolicy>
<encoder>
<pattern>${log.pattern}</pattern>
<charset>utf-8</charset>
</encoder>
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<!-- 过滤的级别 -->
<level>INFO</level>
</filter>
</appender>
<!-- 系统日志输出 -->
<appender name="file_info" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${log.path}/sys-info.log</file>
<!-- 循环政策:基于时间创建日志文件 -->
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!-- 日志文件名格式 -->
<fileNamePattern>${log.path}/sys-info.%d{yyyy-MM-dd}.log</fileNamePattern>
<!-- 日志最大的历史 60天 -->
<maxHistory>60</maxHistory>
</rollingPolicy>
<encoder>
<pattern>${log.pattern}</pattern>
</encoder>
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<!-- 过滤的级别 -->
<level>INFO</level>
<!-- 匹配时的操作:接收(记录) -->
<onMatch>ACCEPT</onMatch>
<!-- 不匹配时的操作:拒绝(不记录) -->
<onMismatch>DENY</onMismatch>
</filter>
</appender>
<appender name="file_error" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${log.path}/sys-error.log</file>
<!-- 循环政策:基于时间创建日志文件 -->
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!-- 日志文件名格式 -->
<fileNamePattern>${log.path}/sys-error.%d{yyyy-MM-dd}.log</fileNamePattern>
<!-- 日志最大的历史 60天 -->
<maxHistory>60</maxHistory>
</rollingPolicy>
<encoder>
<pattern>${log.pattern}</pattern>
</encoder>
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<!-- 过滤的级别 -->
<level>ERROR</level>
<!-- 匹配时的操作:接收(记录) -->
<onMatch>ACCEPT</onMatch>
<!-- 不匹配时的操作:拒绝(不记录) -->
<onMismatch>DENY</onMismatch>
</filter>
</appender>
<!-- info异步输出 -->
<appender name="async_info" class="ch.qos.logback.classic.AsyncAppender">
<!-- 不丢失日志.默认的,如果队列的80%已满,则会丢弃TRACT、DEBUG、INFO级别的日志 -->
<discardingThreshold>0</discardingThreshold>
<!-- 更改默认的队列的深度,该值会影响性能.默认值为256 -->
<queueSize>512</queueSize>
<!-- 添加附加的appender,最多只能添加一个 -->
<appender-ref ref="file_info"/>
</appender>
<!-- error异步输出 -->
<appender name="async_error" class="ch.qos.logback.classic.AsyncAppender">
<!-- 不丢失日志.默认的,如果队列的80%已满,则会丢弃TRACT、DEBUG、INFO级别的日志 -->
<discardingThreshold>0</discardingThreshold>
<!-- 更改默认的队列的深度,该值会影响性能.默认值为256 -->
<queueSize>512</queueSize>
<!-- 添加附加的appender,最多只能添加一个 -->
<appender-ref ref="file_error"/>
</appender>
<!-- 整合 skywalking 控制台输出 tid -->
<!-- <appender name="console" class="ch.qos.logback.core.ConsoleAppender">-->
<!-- <encoder class="ch.qos.logback.core.encoder.LayoutWrappingEncoder">-->
<!-- <layout class="org.apache.skywalking.apm.toolkit.log.logback.v1.x.TraceIdPatternLogbackLayout">-->
<!-- <pattern>[%tid] ${console.log.pattern}</pattern>-->
<!-- </layout>-->
<!-- <charset>utf-8</charset>-->
<!-- </encoder>-->
<!-- </appender>-->
<!-- 整合 skywalking 推送采集日志 -->
<!-- <appender name="sky_log" class="org.apache.skywalking.apm.toolkit.log.logback.v1.x.log.GRPCLogClientAppender">-->
<!-- <encoder class="ch.qos.logback.core.encoder.LayoutWrappingEncoder">-->
<!-- <layout class="org.apache.skywalking.apm.toolkit.log.logback.v1.x.TraceIdPatternLogbackLayout">-->
<!-- <pattern>[%tid] ${console.log.pattern}</pattern>-->
<!-- </layout>-->
<!-- <charset>utf-8</charset>-->
<!-- </encoder>-->
<!-- </appender>-->
<!--系统操作日志-->
<root level="info">
<appender-ref ref="console" />
<appender-ref ref="async_info" />
<appender-ref ref="async_error" />
<appender-ref ref="file_console" />
<!-- <appender-ref ref="sky_log"/>-->
</root>
</configuration>
# p6spy 性能分析插件配置文件
modulelist=com.baomidou.mybatisplus.extension.p6spy.MybatisPlusLogFactory,com.p6spy.engine.outage.P6OutageFactory
# 自定义日志打印
logMessageFormat=com.baomidou.mybatisplus.extension.p6spy.P6SpyLogger
#日志输出到控制台
appender=com.baomidou.mybatisplus.extension.p6spy.StdoutLogger
# 使用日志系统记录 sql
#appender=com.p6spy.engine.spy.appender.Slf4JLogger
# 设置 p6spy driver 代理
#deregisterdrivers=true
# 取消JDBC URL前缀
useprefix=true
# 配置记录 Log 例外,可去掉的结果集有error,info,batch,debug,statement,commit,rollback,result,resultset.
excludecategories=info,debug,result,commit,resultset
# 日期格式
dateformat=yyyy-MM-dd HH:mm:ss
# SQL语句打印时间格式
databaseDialectTimestampFormat=yyyy-MM-dd HH:mm:ss
# 实际驱动可多个
#driverlist=org.h2.Driver
# 是否开启慢SQL记录
outagedetection=true
# 慢SQL记录标准 2 秒
outagedetectioninterval=2
# 是否过滤 Log
filter=true
# 过滤 Log 时所排除的 sql 关键字,以逗号分隔
exclude=SELECT 1
package com.dsk.test;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
/**
* 断言单元测试案例
*
* @author Lion Li
*/
@DisplayName("断言单元测试案例")
public class AssertUnitTest {
@DisplayName("测试 assertEquals 方法")
@Test
public void testAssertEquals() {
Assertions.assertEquals("666", new String("666"));
Assertions.assertNotEquals("666", new String("666"));
}
@DisplayName("测试 assertSame 方法")
@Test
public void testAssertSame() {
Object obj = new Object();
Object obj1 = obj;
Assertions.assertSame(obj, obj1);
Assertions.assertNotSame(obj, obj1);
}
@DisplayName("测试 assertTrue 方法")
@Test
public void testAssertTrue() {
Assertions.assertTrue(true);
Assertions.assertFalse(true);
}
@DisplayName("测试 assertNull 方法")
@Test
public void testAssertNull() {
Assertions.assertNull(null);
Assertions.assertNotNull(null);
}
}
package com.dsk.test;
import com.dsk.common.config.RuoYiConfig;
import org.junit.jupiter.api.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.concurrent.TimeUnit;
/**
* 单元测试案例
*
* @author Lion Li
*/
@SpringBootTest // 此注解只能在 springboot 主包下使用 需包含 main 方法与 yml 配置文件
@DisplayName("单元测试案例")
public class DemoUnitTest {
@Autowired
private RuoYiConfig ruoYiConfig;
@DisplayName("测试 @SpringBootTest @Test @DisplayName 注解")
@Test
public void testTest() {
System.out.println(ruoYiConfig);
}
@Disabled
@DisplayName("测试 @Disabled 注解")
@Test
public void testDisabled() {
System.out.println(ruoYiConfig);
}
@Timeout(value = 2L, unit = TimeUnit.SECONDS)
@DisplayName("测试 @Timeout 注解")
@Test
public void testTimeout() throws InterruptedException {
Thread.sleep(3000);
System.out.println(ruoYiConfig);
}
@DisplayName("测试 @RepeatedTest 注解")
@RepeatedTest(3)
public void testRepeatedTest() {
System.out.println(666);
}
@BeforeAll
public static void testBeforeAll() {
System.out.println("@BeforeAll ==================");
}
@BeforeEach
public void testBeforeEach() {
System.out.println("@BeforeEach ==================");
}
@AfterEach
public void testAfterEach() {
System.out.println("@AfterEach ==================");
}
@AfterAll
public static void testAfterAll() {
System.out.println("@AfterAll ==================");
}
}
package com.dsk.test;
import com.dsk.common.enums.UserType;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.EnumSource;
import org.junit.jupiter.params.provider.MethodSource;
import org.junit.jupiter.params.provider.NullSource;
import org.junit.jupiter.params.provider.ValueSource;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Stream;
/**
* 带参数单元测试案例
*
* @author Lion Li
*/
@DisplayName("带参数单元测试案例")
public class ParamUnitTest {
@DisplayName("测试 @ValueSource 注解")
@ParameterizedTest
@ValueSource(strings = {"t1", "t2", "t3"})
public void testValueSource(String str) {
System.out.println(str);
}
@DisplayName("测试 @NullSource 注解")
@ParameterizedTest
@NullSource
public void testNullSource(String str) {
System.out.println(str);
}
@DisplayName("测试 @EnumSource 注解")
@ParameterizedTest
@EnumSource(UserType.class)
public void testEnumSource(UserType type) {
System.out.println(type.getUserType());
}
@DisplayName("测试 @MethodSource 注解")
@ParameterizedTest
@MethodSource("getParam")
public void testMethodSource(String str) {
System.out.println(str);
}
public static Stream<String> getParam() {
List<String> list = new ArrayList<>();
list.add("t1");
list.add("t2");
list.add("t3");
return list.stream();
}
@BeforeEach
public void testBeforeEach() {
System.out.println("@BeforeEach ==================");
}
@AfterEach
public void testAfterEach() {
System.out.println("@AfterEach ==================");
}
}
package com.dsk.test;
import org.junit.jupiter.api.*;
import org.springframework.boot.test.context.SpringBootTest;
/**
* 标签单元测试案例
*
* @author Lion Li
*/
@SpringBootTest
@DisplayName("标签单元测试案例")
public class TagUnitTest {
@Tag("dev")
@DisplayName("测试 @Tag dev")
@Test
public void testTagDev() {
System.out.println("dev");
}
@Tag("prod")
@DisplayName("测试 @Tag prod")
@Test
public void testTagProd() {
System.out.println("prod");
}
@Tag("local")
@DisplayName("测试 @Tag local")
@Test
public void testTagLocal() {
System.out.println("local");
}
@Tag("exclude")
@DisplayName("测试 @Tag exclude")
@Test
public void testTagExclude() {
System.out.println("exclude");
}
@BeforeEach
public void testBeforeEach() {
System.out.println("@BeforeEach ==================");
}
@AfterEach
public void testAfterEach() {
System.out.println("@AfterEach ==================");
}
}
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>dsk-operate-sys</artifactId>
<groupId>com.dsk</groupId>
<version>4.8.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>dsk-common</artifactId>
<description>
common通用工具
</description>
<dependencies>
<!-- Spring框架基本的核心工具 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
</dependency>
<!-- SpringWeb模块 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
</dependency>
<!-- Sa-Token 权限认证, 在线文档:http://sa-token.dev33.cn/ -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-spring-boot-starter</artifactId>
</dependency>
<!-- Sa-Token 整合 jwt -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-jwt</artifactId>
</dependency>
<!-- 自定义验证注解 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<!--常用工具类 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
<!-- JSON工具类 -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>easyexcel</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>transmittable-thread-local</artifactId>
</dependency>
<!-- yml解析器 -->
<dependency>
<groupId>org.yaml</groupId>
<artifactId>snakeyaml</artifactId>
</dependency>
<!-- servlet包 -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
</dependency>
<dependency>
<groupId>jakarta.servlet</groupId>
<artifactId>jakarta.servlet-api</artifactId>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
</dependency>
<!-- dynamic-datasource 多数据源-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>dynamic-datasource-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-core</artifactId>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-http</artifactId>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-captcha</artifactId>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-jwt</artifactId>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-extra</artifactId>
</dependency>
<dependency>
<groupId>com.sun.mail</groupId>
<artifactId>jakarta.mail</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-webmvc-core</artifactId>
</dependency>
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-javadoc</artifactId>
</dependency>
<!-- 自动生成YML配置关联JSON文件 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
</dependency>
<!--redisson-->
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson-spring-data-27</artifactId>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>lock4j-redisson-spring-boot-starter</artifactId>
</dependency>
<!-- 加密包引入 -->
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk15to18</artifactId>
</dependency>
<!-- 离线IP地址定位库 -->
<dependency>
<groupId>org.lionsoul</groupId>
<artifactId>ip2region</artifactId>
</dependency>
<dependency>
<groupId>io.github.linpeilie</groupId>
<artifactId>mapstruct-plus</artifactId>
</dependency>
<dependency>
<groupId>com.gitee.dasikong</groupId>
<artifactId>dsk-acc-open-sdk-java</artifactId>
<version>2.1.0</version>
<scope>compile</scope>
</dependency>
<!--中文转拼音-->
<dependency>
<groupId>com.belerweb</groupId>
<artifactId>pinyin4j</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.fastjson2</groupId>
<artifactId>fastjson2</artifactId>
<version>2.0.25</version>
</dependency>
</dependencies>
</project>
package com.dsk.common.annotation;
import com.dsk.common.excel.CellMergeStrategy;
import java.lang.annotation.*;
/**
* excel 列单元格合并(合并列相同项)
*
* 需搭配 {@link CellMergeStrategy} 策略使用
*
* @author Lion Li
*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface CellMerge {
/**
* col index
*/
int index() default -1;
}
package com.dsk.common.annotation;
import java.lang.annotation.*;
/**
* 数据权限
*
* 一个注解只能对应一个模板
*
* @author Lion Li
* @version 3.5.0
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DataColumn {
/**
* 占位符关键字
*/
String[] key() default "deptName";
/**
* 占位符替换值
*/
String[] value() default "dept_id";
}
package com.dsk.common.annotation;
import java.lang.annotation.*;
/**
* 数据权限组
*
* @author Lion Li
* @version 3.5.0
*/
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DataPermission {
DataColumn[] value();
}
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment