init: Initialize the basic project.

This commit is contained in:
2026-01-06 09:58:29 +08:00
commit 1ab79d6f8f
1441 changed files with 129326 additions and 0 deletions

1
nl-web-app/README.md Normal file
View File

@@ -0,0 +1 @@
# 主启动模块

141
nl-web-app/pom.xml Normal file
View File

@@ -0,0 +1,141 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.nl</groupId>
<artifactId>nl-tool-platform</artifactId>
<version>3.0.0</version>
</parent>
<artifactId>nl-web-app</artifactId>
<packaging>jar</packaging>
<description>主启动模块</description>
<dependencies>
<!-- test -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
<!-- test -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- dynamic-datasource -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>dynamic-datasource-spring-boot3-starter</artifactId>
</dependency>
<!-- mysql -->
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
</dependency>
<!-- postgresql -->
<!--<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
</dependency>-->
<!-- oracle -->
<!--<dependency>
<groupId>com.oracle.database.jdbc</groupId>
<artifactId>ojdbc10</artifactId>
</dependency>-->
<!--<dependency>
<groupId>com.oracle.database.nls</groupId>
<artifactId>orai18n</artifactId>
</dependency>-->
<!-- mssql -->
<!--<dependency>
<groupId>com.microsoft.sqlserver</groupId>
<artifactId>mssql-jdbc</artifactId>
</dependency>-->
<!-- 达梦数据库 -->
<!--<dependency>
<groupId>com.dameng</groupId>
<artifactId>DmJdbcDriver18</artifactId>
</dependency>-->
<!-- 人大金仓数据库 -->
<!--<dependency>
<groupId>cn.com.kingbase</groupId>
<artifactId>kingbase8</artifactId>
</dependency>-->
<!-- 登录鉴权插件 -->
<dependency>
<groupId>org.nl</groupId>
<artifactId>nl-plugin-auth</artifactId>
</dependency>
<!-- 业务功能插件 -->
<dependency>
<groupId>org.nl</groupId>
<artifactId>nl-plugin-biz</artifactId>
</dependency>
<!-- C端功能插件 -->
<dependency>
<groupId>org.nl</groupId>
<artifactId>nl-plugin-client</artifactId>
</dependency>
<!-- 开发工具插件 -->
<dependency>
<groupId>org.nl</groupId>
<artifactId>nl-plugin-dev</artifactId>
</dependency>
<!-- 代码生成插件 -->
<dependency>
<groupId>org.nl</groupId>
<artifactId>nl-plugin-gen</artifactId>
</dependency>
<!-- 移动端管理插件 -->
<dependency>
<groupId>org.nl</groupId>
<artifactId>nl-plugin-mobile</artifactId>
</dependency>
<!-- 系统功能插件 -->
<dependency>
<groupId>org.nl</groupId>
<artifactId>nl-plugin-sys</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>${spring-boot.version}</version>
<configuration>
<includeSystemScope>true</includeSystemScope>
</configuration>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

View File

@@ -0,0 +1,74 @@
/*
* Copyright [2022] [https://www.xiaonuo.vip]
*
* Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点:
*
* 1.请不要删除和修改根目录下的LICENSE文件。
* 2.请不要删除和修改Snowy源码头部的版权声明。
* 3.本项目代码可免费商业使用,商业使用请保留源码和相关描述文件的项目出处,作者声明等。
* 4.分发源码时候,请注明软件出处 https://www.xiaonuo.vip
* 5.不可二次分发开源参与同类竞品如有想法可联系团队xiaonuobase@qq.com商议合作。
* 6.若您的项目无法满足以上几点需要更多功能代码获取Snowy商业授权许可请在官网购买授权地址为 https://www.xiaonuo.vip
*/
package org.nl;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.Banner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.env.Environment;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* SpringBoot方式启动类
*
* @author xuyuxiang
* @date 2021/12/18 16:57
*/
@Slf4j
@RestController
@SpringBootApplication
public class Application {
/* 解决druid 日志报错discard long time none received connection:xxx */
static {
System.setProperty("druid.mysql.usePingMethod","false");
}
/**
* 主启动函数
*
* @author xuyuxiang
* @date 2022/7/30 21:42
*/
@SneakyThrows
public static void main(String[] args) {
SpringApplication springApplication = new SpringApplication(Application.class);
springApplication.setBannerMode(Banner.Mode.OFF);
ConfigurableApplicationContext configurableApplicationContext = springApplication.run(args);
Environment env = configurableApplicationContext.getEnvironment();
log.info("""
----------------------------------------------------------
Application is running! Access URLs:
Local: http://localhost:{}
Doc: http://localhost:{}/doc.html
----------------------------------------------------------""",
env.getProperty("server.port"),
env.getProperty("server.port"));
}
/**
* 首页
*
* @author xuyuxiang
* @date 2022/7/8 14:22
**/
@GetMapping("/")
public String index() {
return "WELCOME";
}
}

View File

@@ -0,0 +1,62 @@
/*
* Copyright [2022] [https://www.xiaonuo.vip]
*
* Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点:
*
* 1.请不要删除和修改根目录下的LICENSE文件。
* 2.请不要删除和修改Snowy源码头部的版权声明。
* 3.本项目代码可免费商业使用,商业使用请保留源码和相关描述文件的项目出处,作者声明等。
* 4.分发源码时候,请注明软件出处 https://www.xiaonuo.vip
* 5.不可二次分发开源参与同类竞品如有想法可联系团队xiaonuobase@qq.com商议合作。
* 6.若您的项目无法满足以上几点需要更多功能代码获取Snowy商业授权许可请在官网购买授权地址为 https://www.xiaonuo.vip
*/
package org.nl.core.config;
import com.alibaba.druid.spring.boot3.autoconfigure.properties.DruidStatProperties;
import com.alibaba.druid.util.Utils;
import jakarta.servlet.Filter;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* druid配置
*
* @author diantu
* @date 2023/06/30
**/
@Configuration
@ConditionalOnProperty(name = "spring.datasource.druid.stat-view-servlet.enabled", havingValue = "true")
public class DruidConfigure {
/**
* 去除druid监控页面广告
*/
@SuppressWarnings({ "rawtypes", "unchecked" })
@Bean
public FilterRegistrationBean removeDruidAdFilterRegistrationBean(DruidStatProperties properties) {
// 获取web监控页面的参数
DruidStatProperties.StatViewServlet config = properties.getStatViewServlet();
// 提取common.js的配置路径
String pattern = config.getUrlPattern() != null ? config.getUrlPattern() : "/druid/*";
String commonJsPattern = pattern.replaceAll("\\*", "js/common.js");
final String filePath = "support/http/resources/js/common.js";
// 创建filter进行过滤
Filter filter = (request, response, chain) -> {
chain.doFilter(request, response);
// 重置缓冲区,响应头不会被重置
response.resetBuffer();
// 获取common.js
String text = Utils.readFromResource(filePath);
// 正则替换banner, 除去底部的广告信息
text = text.replaceAll("<a.*?banner\"></a><br/>", "");
text = text.replaceAll("powered.*?shrek.wang</a>", "");
response.getWriter().write(text);
};
FilterRegistrationBean registrationBean = new FilterRegistrationBean();
registrationBean.setFilter(filter);
registrationBean.addUrlPatterns(commonJsPattern);
return registrationBean;
}
}

View File

@@ -0,0 +1,670 @@
/*
* Copyright [2022] [https://www.xiaonuo.vip]
*
* Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点:
*
* 1.请不要删除和修改根目录下的LICENSE文件。
* 2.请不要删除和修改Snowy源码头部的版权声明。
* 3.本项目代码可免费商业使用,商业使用请保留源码和相关描述文件的项目出处,作者声明等。
* 4.分发源码时候,请注明软件出处 https://www.xiaonuo.vip
* 5.不可二次分发开源参与同类竞品如有想法可联系团队xiaonuobase@qq.com商议合作。
* 6.若您的项目无法满足以上几点需要更多功能代码获取Snowy商业授权许可请在官网购买授权地址为 https://www.xiaonuo.vip
*/
package org.nl.core.config;
import cn.dev33.satoken.config.SaTokenConfig;
import cn.dev33.satoken.context.SaHolder;
import cn.dev33.satoken.context.model.SaResponse;
import cn.dev33.satoken.filter.SaServletFilter;
import cn.dev33.satoken.router.SaHttpMethod;
import cn.dev33.satoken.router.SaRouter;
import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.date.DateTime;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.*;
import cn.hutool.extra.spring.SpringUtil;
import cn.hutool.http.ContentType;
import cn.hutool.http.Header;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletRequest;
import org.apache.ibatis.mapping.DatabaseIdProvider;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.ReflectionException;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.mybatis.spring.annotation.MapperScan;
import org.nl.core.handler.GlobalExceptionUtil;
import org.nl.sys.core.enums.SysBuildInEnum;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import org.springframework.jdbc.support.JdbcUtils;
import org.springframework.stereotype.Component;
import org.springframework.util.ResourceUtils;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.nl.auth.core.util.StpClientUtil;
import org.nl.common.annotation.CommonNoRepeat;
import org.nl.common.annotation.CommonWrapper;
import org.nl.common.cache.CommonCacheOperator;
import org.nl.common.enums.CommonDeleteFlagEnum;
import org.nl.common.exception.CommonException;
import org.nl.common.listener.CommonDataChangeEventCenter;
import org.nl.common.listener.CommonDataChangeListener;
import org.nl.common.pojo.CommonResult;
import org.nl.common.pojo.CommonWrapperInterface;
import org.nl.common.util.CommonIpAddressUtil;
import org.nl.common.util.CommonJoinPointUtil;
import org.nl.common.util.CommonServletUtil;
import org.nl.common.util.CommonTimeFormatUtil;
import javax.sql.DataSource;
import java.lang.reflect.Array;
import java.lang.reflect.Method;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
/**
* Snowy配置
*
* @author xuyuxiang
* @date 2021/10/9 14:24
**/
@Configuration
@MapperScan(basePackages = {"org.nl.**.mapper"})
public class GlobalConfigure implements WebMvcConfigurer {
@Autowired
private SaTokenConfig saTokenConfig;
private static final String COMMON_REPEAT_SUBMIT_CACHE_KEY = "common-repeatSubmit:";
/**
* 无需登录的接口地址集合
*/
public static final String[] NO_LOGIN_PATH_ARR = {
/* 主入口 */
"/",
/* 静态资源 */
"/favicon.ico",
"/doc.html",
"/webjars/**",
"/v3/api-docs/**",
"/druid/**",
/* 移动端静态资源 */
"/mobile/**",
/* 认证相关 */
"/auth/c/getPicCaptcha",
"/auth/c/getPhoneValidCode",
"/auth/c/doLogin",
"/auth/c/doLoginByPhone",
"/auth/b/getPicCaptcha",
"/auth/b/getPhoneValidCode",
"/auth/b/doLogin",
"/auth/b/doLoginByPhone",
/* 三方登录相关 */
"/auth/third/render",
"/auth/third/callback",
/* 系统基础配置 */
"/dev/config/sysBaseList",
/* 系统字典树 */
"/dev/dict/tree",
/* 文件下载 */
"/dev/file/download",
/* 用户个人中心相关 */
"/sys/userCenter/getPicCaptcha",
"/sys/userCenter/findPasswordGetPhoneValidCode",
"/sys/userCenter/findPasswordGetEmailValidCode",
"/sys/userCenter/findPasswordByPhone",
"/sys/userCenter/findPasswordByEmail"
};
/**
* 仅超管使用的接口地址集合
*/
private static final String[] SUPER_PERMISSION_PATH_ARR = {
"/auth/session/**",
"/auth/third/page",
"/client/user/**",
"/sys/org/**",
"/sys/group/**",
"/sys/position/**",
"/sys/button/**",
"/sys/menu/**",
"/sys/module/**",
"/sys/role/**",
"/sys/user/**",
"/sys/index/bizDataCount",
"/sys/index/opDataCount",
"/sys/index/toolDataCount",
"/dev/config/**",
"/dev/dict/**",
"/dev/email/page",
"/dev/email/delete",
"/dev/email/detail",
"/dev/file/page",
"/dev/file/list",
"/dev/file/delete",
"/dev/file/detail",
"/dev/job/**",
"/dev/log/**",
"/dev/message/page",
"/dev/message/delete",
"/dev/message/detail",
"/dev/monitor/**",
"/dev/sms/page",
"/dev/sms/delete",
"/dev/sms/detail",
"/dev/slideshow/**",
"/gen/basic/**",
"/gen/config/**",
"/mobile/menu/**",
"/mobile/module/**",
};
/**
* B端要排除的相当于C端要认证的
*/
private static final String[] CLIENT_USER_PERMISSION_PATH_ARR = {
"/auth/c/**",
"/client/c/**"
};
/**
* 注册跨域过滤器
*/
@Bean
public SaServletFilter getSaServletFilter() {
return new SaServletFilter()
// 指定拦截路由
.addInclude("/**")
// 设置鉴权的接口
.setAuth(r -> {
// B端的接口校验B端登录
SaRouter.match("/**")
// 排除无需登录接口
.notMatch(CollectionUtil.newArrayList(NO_LOGIN_PATH_ARR))
// 排除C端认证接口
.notMatch(CollectionUtil.newArrayList(CLIENT_USER_PERMISSION_PATH_ARR))
// 校验B端登录
.check(r1 -> {
StpUtil.checkLogin();
// 更新过期时间
StpUtil.renewTimeout(saTokenConfig.getTimeout());
});
// C端的接口校验C端登录
SaRouter.match("/**")
// 排除无需登录接口
.notMatch(CollectionUtil.newArrayList(NO_LOGIN_PATH_ARR))
// 匹配C端认证接口
.match(CollectionUtil.newArrayList(CLIENT_USER_PERMISSION_PATH_ARR))
// 校验C端登录
.check(r1 -> StpClientUtil.checkLogin());
// B端的超管接口校验B端超管角色
SaRouter.match("/**")
// 排除无需登录接口
.notMatch(CollectionUtil.newArrayList(NO_LOGIN_PATH_ARR))
// 匹配超管接口
.match(CollectionUtil.newArrayList(SUPER_PERMISSION_PATH_ARR))
// 校验B端超管角色
.check(r1 -> StpUtil.checkRole(SysBuildInEnum.BUILD_IN_ROLE_CODE.getValue()));
})
// 前置函数:在每次认证函数之前执行
.setBeforeAuth(obj -> {
// ---------- 设置跨域响应头 ----------
SaHolder.getResponse()
// 是否可以在iframe显示视图 DENY=不可以 | SAMEORIGIN=同域下可以 | ALLOW-FROM uri=指定域名下可以
// .setHeader("X-Frame-Options", "SAMEORIGIN")
// 是否启用浏览器默认XSS防护 0=禁用 | 1=启用 | 1; mode=block 启用, 并在检查到XSS攻击时停止渲染页面
.setHeader("X-XSS-Protection", "1; mode=block")
// 禁用浏览器内容嗅探
.setHeader("X-Content-Type-Options", "nosniff")
// 允许指定域访问跨域资源
.setHeader("Access-Control-Allow-Origin", "*")
// 允许所有请求方式
.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE")
// 有效时间
.setHeader("Access-Control-Max-Age", "3600")
// 允许的header参数
.setHeader("Access-Control-Allow-Headers", "*");
// 如果是预检请求,则立即返回到前端
SaRouter.match(SaHttpMethod.OPTIONS)
// OPTIONS预检请求不做处理
.free(r -> {
})
.back();
})
// 异常处理
.setError(e -> {
// 由于过滤器中抛出的异常不进入全局异常处理,所以必须提供[异常处理函数]来处理[认证函数]里抛出的异常
// 在[异常处理函数]里的返回值将作为字符串输出到前端此处统一转为JSON输出前端
SaResponse saResponse = SaHolder.getResponse();
saResponse.setHeader(Header.CONTENT_TYPE.getValue(), ContentType.JSON + ";charset=" + CharsetUtil.UTF_8);
return GlobalExceptionUtil.getCommonResult((Exception) e);
});
}
/**
* RedisTemplate序列化
*
* @author xuyuxiang
* @date 2022/6/21 17:01
**/
@SuppressWarnings("ALL")
@Primary
@Bean
public RedisTemplate<String, Object> redisTemplate(@Autowired(required = false) RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(redisConnectionFactory);
StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
redisTemplate.setKeySerializer(stringRedisSerializer);
redisTemplate.setHashKeySerializer(stringRedisSerializer);
Jackson2JsonRedisSerializer<?> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class);
redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer);
redisTemplate.afterPropertiesSet();
return redisTemplate;
}
/**
* 静态资源映射
*
* @author xuyuxiang
* @date 2022/7/25 15:16
**/
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("doc.html").addResourceLocations(ResourceUtils.CLASSPATH_URL_PREFIX + "/META-INF/resources/");
registry.addResourceHandler("/webjars/**").addResourceLocations(ResourceUtils.CLASSPATH_URL_PREFIX + "/META-INF/resources/webjars/");
}
/**
* 节流防抖的AOP
*
* @author xuyuxiang
* @date 2022/9/15 21:24
*/
@Component
@Aspect
public static class CommonNoRepeatAop {
/**
* 切入点
*
* @author xuyuxiang
* @date 2022/9/15 21:27
*/
@Pointcut("@annotation(org.nl.common.annotation.CommonNoRepeat)")
private void noRepeatPointcut() {
}
/**
* 执行校验
*
* @author xuyuxiang
* @date 2022/9/15 21:27
*/
@Before("noRepeatPointcut()")
public void doBefore(JoinPoint joinPoint) {
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
Method method = methodSignature.getMethod();
CommonNoRepeat commonNoRepeat = method.getAnnotation(CommonNoRepeat.class);
HttpServletRequest request = CommonServletUtil.getRequest();
String url = request.getRequestURI();
CommonCacheOperator commonCacheOperator = SpringUtil.getBean(CommonCacheOperator.class);
JSONObject jsonObject = JSONUtil.createObj();
jsonObject.set("repeatParam", CommonJoinPointUtil.getArgsJsonString(joinPoint));
jsonObject.set("repeatTime", DateUtil.current());
// 获取该接口缓存的限流数据跟当前ip以及登录用户有关
String cacheKey = COMMON_REPEAT_SUBMIT_CACHE_KEY + CommonIpAddressUtil.getIp(request) + StrUtil.COLON;
Object loginId = StpUtil.getLoginIdDefaultNull();
if (ObjectUtil.isNotEmpty(loginId)) {
cacheKey = cacheKey + Convert.toStr(loginId) + StrUtil.COLON + url;
} else {
cacheKey = cacheKey + url;
}
Object cacheObj = commonCacheOperator.get(cacheKey);
if (ObjectUtil.isNotEmpty(cacheObj)) {
JSONObject cacheJsonObject = JSONUtil.parseObj(cacheObj);
if (cacheJsonObject.containsKey(url)) {
JSONObject existRepeatJsonObject = cacheJsonObject.getJSONObject(url);
// 如果与上次参数一致,且时间间隔小于要求的限流时长,则判定为重复提交
if (jsonObject.getStr("repeatParam").equals(existRepeatJsonObject.getStr("repeatParam"))) {
long interval = jsonObject.getLong("repeatTime") - existRepeatJsonObject.getLong("repeatTime");
if (interval < commonNoRepeat.interval()) {
long secondsParam = (commonNoRepeat.interval() - interval) / 1000;
if (secondsParam > 0) {
throw new CommonException("请求过于频繁,请" + CommonTimeFormatUtil.formatSeconds(secondsParam) + "后再试");
}
}
}
}
}
// 缓存最新的该接口的限流数据跟当前ip以及登录用户有关为防止缓存的数据过多缓存时效为1小时
commonCacheOperator.put(cacheKey, JSONUtil.createObj().set(url, jsonObject), 60 * 60);
}
}
/**
* 通用Wrapper的AOP
*
* @author xuyuxiang
* @date 2022/9/15 21:24
*/
@Component
@Aspect
public static class CommonWrapperAop {
/**
* 切入点
*
* @author xuyuxiang
* @date 2022/9/15 21:27
*/
@Pointcut("@annotation(org.nl.common.annotation.CommonWrapper)")
private void wrapperPointcut() {
}
/**
* 执行包装
*
* @author xuyuxiang
* @date 2022/9/15 21:27
*/
@Around("wrapperPointcut()")
public Object doWrapper(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
// 直接执行原有业务逻辑
Object proceedResult = proceedingJoinPoint.proceed();
return processWrapping(proceedingJoinPoint, proceedResult);
}
/**
* 具体包装过程
*
* @author xuyuxiang
* @date 2022/9/15 21:27
*/
@SuppressWarnings("ALL")
private Object processWrapping(ProceedingJoinPoint proceedingJoinPoint, Object originResult) throws IllegalAccessException, InstantiationException {
MethodSignature methodSignature = (MethodSignature) proceedingJoinPoint.getSignature();
Method method = methodSignature.getMethod();
CommonWrapper commonWrapper = method.getAnnotation(CommonWrapper.class);
Class<? extends CommonWrapperInterface<?>>[] baseWrapperClasses = commonWrapper.value();
if (ObjectUtil.isEmpty(baseWrapperClasses)) {
return originResult;
}
if (!(originResult instanceof CommonResult)) {
return originResult;
}
CommonResult commonResult = (CommonResult) originResult;
Object beWrapped = commonResult.getData();
if (ObjectUtil.isBasicType(beWrapped)) {
throw new CommonException("被包装的值不能是基本类型");
}
if (beWrapped instanceof Page) {
Page page = (Page) beWrapped;
ArrayList<Map<String, Object>> maps = new ArrayList<>();
for (Object wrappedItem : page.getRecords()) {
maps.add(this.wrapPureObject(wrappedItem, baseWrapperClasses));
}
page.setRecords(maps);
commonResult.setData(page);
} else if (beWrapped instanceof Collection) {
Collection collection = (Collection) beWrapped;
List<Map<String, Object>> maps = new ArrayList<>();
for (Object wrappedItem : collection) {
maps.add(this.wrapPureObject(wrappedItem, baseWrapperClasses));
}
commonResult.setData(maps);
} else if (ArrayUtil.isArray(beWrapped)) {
Object[] objects = this.objToArray(beWrapped);
ArrayList<Map<String, Object>> maps = new ArrayList<>();
for (Object wrappedItem : objects) {
maps.add(this.wrapPureObject(wrappedItem, baseWrapperClasses));
}
commonResult.setData(maps);
} else {
commonResult.setData(this.wrapPureObject(beWrapped, baseWrapperClasses));
}
return commonResult;
}
/**
* 原始对象包装JSONObject
*
* @author xuyuxiang
* @date 2022/9/15 21:36
*/
@SuppressWarnings("ALL")
private JSONObject wrapPureObject(Object originModel, Class<? extends CommonWrapperInterface<?>>[] baseWrapperClasses) {
JSONObject jsonObject = JSONUtil.parseObj(originModel);
try {
for (Class<? extends CommonWrapperInterface<?>> commonWrapperClass : baseWrapperClasses) {
CommonWrapperInterface commonWrapperInterface = commonWrapperClass.newInstance();
Map<String, Object> incrementFieldsMap = commonWrapperInterface.doWrap(originModel);
jsonObject.putAll(incrementFieldsMap);
}
} catch (Exception e) {
throw new CommonException("原始对象包装过程,字段转化异常:{}", e.getMessage());
}
return jsonObject;
}
/**
* Object转array
*
* @author xuyuxiang
* @date 2022/9/15 21:34
*/
private Object[] objToArray(Object object) {
int length = Array.getLength(object);
Object[] result = new Object[length];
for (int i = 0; i < result.length; i++) {
result[i] = Array.get(object, i);
}
return result;
}
}
/**
* 分页插件
*
* @author xuyuxiang
* @date 2022/3/11 10:59
**/
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor();
mybatisPlusInterceptor.addInnerInterceptor(new PaginationInnerInterceptor());
return mybatisPlusInterceptor;
}
/**
* 数据库id选择器用于Mapper.xml中
* MyBatis可以根据不同的数据库厂商执行不同的语句
*
* @author xuyuxiang
* @date 2022/1/8 2:16
*/
@Component
public static class CustomDbIdProvider implements DatabaseIdProvider {
@Override
public String getDatabaseId(DataSource dataSource) throws SQLException {
Connection conn = null;
try {
conn = dataSource.getConnection();
String url = conn.getMetaData().getURL().toLowerCase();
if (url.contains("jdbc:oracle")) {
return "oracle";
} else if (url.contains("jdbc:postgresql")) {
return "pgsql";
} else if (url.contains("jdbc:mysql")) {
return "mysql";
} else if (url.contains("jdbc:dm")) {
return "dm";
} else if (url.contains("jdbc:kingbase")) {
return "kingbase";
} else {
return "mysql";
}
} finally {
JdbcUtils.closeConnection(conn);
}
}
}
/**
* 自定义公共字段自动注入
*
* @author xuyuxiang
* @date 2020/3/31 15:42
*/
@Component
public static class CustomMetaObjectHandler implements MetaObjectHandler {
/**
* 删除标志
*/
private static final String DELETE_FLAG = "deleteFlag";
/**
* 创建人
*/
private static final String CREATE_USER = "createUser";
/**
* 创建时间
*/
private static final String CREATE_TIME = "createTime";
/**
* 更新人
*/
private static final String UPDATE_USER = "updateUser";
/**
* 更新时间
*/
private static final String UPDATE_TIME = "updateTime";
@Override
public void insertFill(MetaObject metaObject) {
try {
//为空则设置deleteFlag
Object deleteFlag = metaObject.getValue(DELETE_FLAG);
if (ObjectUtil.isNull(deleteFlag)) {
setFieldValByName(DELETE_FLAG, EnumUtil.toString(CommonDeleteFlagEnum.NOT_DELETE), metaObject);
}
} catch (ReflectionException ignored) {
}
try {
//为空则设置createUser
Object createUser = metaObject.getValue(CREATE_USER);
if (ObjectUtil.isNull(createUser)) {
setFieldValByName(CREATE_USER, this.getUserId(), metaObject);
}
} catch (ReflectionException ignored) {
}
try {
//为空则设置createTime
Object createTime = metaObject.getValue(CREATE_TIME);
if (ObjectUtil.isNull(createTime)) {
setFieldValByName(CREATE_TIME, DateTime.now(), metaObject);
}
} catch (ReflectionException ignored) {
}
}
@Override
public void updateFill(MetaObject metaObject) {
try {
//设置updateUser
setFieldValByName(UPDATE_USER, this.getUserId(), metaObject);
} catch (ReflectionException ignored) {
}
try {
//设置updateTime
setFieldValByName(UPDATE_TIME, DateTime.now(), metaObject);
} catch (ReflectionException ignored) {
}
}
/**
* 获取用户id
*/
private String getUserId() {
try {
try {
String loginId = StpUtil.getLoginIdAsString();
if (ObjectUtil.isNotEmpty(loginId)) {
return loginId;
} else {
return "-1";
}
} catch (Exception e) {
String clientLoginId = StpClientUtil.getLoginIdAsString();
if (ObjectUtil.isNotEmpty(clientLoginId)) {
return clientLoginId;
} else {
return "-1";
}
}
} catch (Exception e) {
return "-1";
}
}
}
/**
* 注册数据变化事件中心 事件发布器
*
* @author xuyuxiang
* @date 2023/3/3 14:27
**/
@Resource
public void registerListenerList(List<CommonDataChangeListener> dataChangeListenerList) {
CommonDataChangeEventCenter.registerListenerList(dataChangeListenerList);
}
}

View File

@@ -0,0 +1,75 @@
/*
* Copyright [2022] [https://www.xiaonuo.vip]
*
* Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点:
*
* 1.请不要删除和修改根目录下的LICENSE文件。
* 2.请不要删除和修改Snowy源码头部的版权声明。
* 3.本项目代码可免费商业使用,商业使用请保留源码和相关描述文件的项目出处,作者声明等。
* 4.分发源码时候,请注明软件出处 https://www.xiaonuo.vip
* 5.不可二次分发开源参与同类竞品如有想法可联系团队xiaonuobase@qq.com商议合作。
* 6.若您的项目无法满足以上几点需要更多功能代码获取Snowy商业授权许可请在官网购买授权地址为 https://www.xiaonuo.vip
*/
package org.nl.core.handler;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.http.HttpStatus;
import org.springframework.boot.web.error.ErrorAttributeOptions;
import org.springframework.boot.web.servlet.error.DefaultErrorAttributes;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.WebRequest;
import org.nl.common.exception.CommonException;
import org.nl.common.pojo.CommonResult;
import org.nl.common.util.CommonServletUtil;
import java.util.Map;
/**
* 将未知错误异常,输出格式重写为我们熟悉的响应格式
*
* @author xuyuxiang
* @date 2021/10/9 15:24
**/
@Component
public class GlobalErrorAttributesHandler extends DefaultErrorAttributes {
@Override
public Map<String, Object> getErrorAttributes(WebRequest webRequest, ErrorAttributeOptions attributeOptions) {
// 获取spring默认的返回内容
Map<String, Object> defaultErrorAttributes = super.getErrorAttributes(webRequest, attributeOptions);
// 获取其状态码
Object status = defaultErrorAttributes.get("status");
Object path = defaultErrorAttributes.get("path");
if (ObjectUtil.isNotEmpty(status)) {
// 如果其为404则处理
if (HttpStatus.HTTP_NOT_FOUND == Convert.toInt(status)) {
if(ObjectUtil.isNotEmpty(path)) {
return BeanUtil.beanToMap(CommonResult.get(HttpStatus.HTTP_NOT_FOUND, "路径不存在,请求地址:" +
Convert.toStr(path), null));
} else {
return BeanUtil.beanToMap(CommonResult.get(HttpStatus.HTTP_NOT_FOUND, "路径不存在", null));
}
} else {
return BeanUtil.beanToMap(CommonResult.get(HttpStatus.HTTP_INTERNAL_ERROR, "服务器异常,请求地址:" +
Convert.toStr(path), null));
}
}
// 如果返回的异常是CommonException则按CommonException响应的内容进行返回
Throwable throwable = this.getError(webRequest);
if (ObjectUtil.isNotEmpty(throwable)) {
if (throwable instanceof CommonException) {
CommonException commonException = (CommonException) throwable;
return BeanUtil.beanToMap(CommonResult.error(commonException.getMsg()));
} else {
return BeanUtil.beanToMap(CommonResult.get(HttpStatus.HTTP_INTERNAL_ERROR, "服务器异常,请求地址:" +
CommonServletUtil.getRequest().getRequestURL(), null));
}
} else {
// throwable为空则直接返回默认异常
return BeanUtil.beanToMap(CommonResult.error());
}
}
}

View File

@@ -0,0 +1,63 @@
/*
* Copyright [2022] [https://www.xiaonuo.vip]
*
* Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点:
*
* 1.请不要删除和修改根目录下的LICENSE文件。
* 2.请不要删除和修改Snowy源码头部的版权声明。
* 3.本项目代码可免费商业使用,商业使用请保留源码和相关描述文件的项目出处,作者声明等。
* 4.分发源码时候,请注明软件出处 https://www.xiaonuo.vip
* 5.不可二次分发开源参与同类竞品如有想法可联系团队xiaonuobase@qq.com商议合作。
* 6.若您的项目无法满足以上几点需要更多功能代码获取Snowy商业授权许可请在官网购买授权地址为 https://www.xiaonuo.vip
*/
package org.nl.core.handler;
import cn.hutool.core.util.ObjectUtil;
import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.nl.common.exception.CommonException;
import org.nl.common.pojo.CommonResult;
/**
* 全局异常页面处理器覆盖默认的Whitelabel Error Page
*
* @author xuyuxiang
* @date 2022/2/11 15:41
**/
@Slf4j
@RestController
public class GlobalErrorViewController {
/**
* Error页面视图直接响应JSON
*
* @author xuyuxiang
* @date 2022/2/11 16:11
**/
@RequestMapping("/errorView")
public CommonResult<String> globalError(HttpServletRequest request) {
CommonResult<String> commonResult = new CommonResult<>(404, "路径不存在", null);
Object model = request.getAttribute("model");
if(ObjectUtil.isNotEmpty(model)) {
if(model instanceof Exception){
if(model instanceof CommonException exception) {
Integer code = exception.getCode();
String msg = exception.getMsg();
if(ObjectUtil.isAllNotEmpty(code, msg)) {
commonResult.setCode(code).setMsg(msg);
} else if(ObjectUtil.isNotEmpty(msg)) {
commonResult = CommonResult.error(msg);
} else {
commonResult = CommonResult.error();
}
} else {
commonResult = CommonResult.error();
log.error(">>> 服务器未知异常,具体信息:", (Exception) model);
}
}
}
return commonResult;
}
}

View File

@@ -0,0 +1,74 @@
/*
* Copyright [2022] [https://www.xiaonuo.vip]
*
* Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点:
*
* 1.请不要删除和修改根目录下的LICENSE文件。
* 2.请不要删除和修改Snowy源码头部的版权声明。
* 3.本项目代码可免费商业使用,商业使用请保留源码和相关描述文件的项目出处,作者声明等。
* 4.分发源码时候,请注明软件出处 https://www.xiaonuo.vip
* 5.不可二次分发开源参与同类竞品如有想法可联系团队xiaonuobase@qq.com商议合作。
* 6.若您的项目无法满足以上几点需要更多功能代码获取Snowy商业授权许可请在官网购买授权地址为 https://www.xiaonuo.vip
*/
package org.nl.core.handler;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.util.ObjectUtil;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.web.ServerProperties;
import org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.ModelAndView;
import java.util.Collections;
import java.util.Map;
/**
* 全局异常页面处理器覆盖默认的Whitelabel Error Page
*
* @author xuyuxiang
* @date 2022/2/11 15:41
**/
@RestController
public class GlobalErrorViewHandler extends BasicErrorController {
public GlobalErrorViewHandler(@Autowired(required = false) ServerProperties serverProperties) {
super(new GlobalErrorAttributesHandler(), serverProperties.getError());
}
/**
* 覆盖默认的Json响应
*
* @author xuyuxiang
* @date 2022/2/11 15:47
**/
@Override
public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {
Map<String, Object> defaultErrorAttributes = super.getErrorAttributes(request, getErrorAttributeOptions(request, MediaType.ALL));
Integer code = Convert.toInt(defaultErrorAttributes.get("code"));
return new ResponseEntity<>(defaultErrorAttributes, HttpStatus.valueOf(ObjectUtil.isNotEmpty(code)?code:500));
}
/**
* 覆盖默认的错误页面响应JSON
*
* @author xuyuxiang
* @date 2022/2/12 21:55
*/
@Override
@RequestMapping(produces = {"text/html"})
public ModelAndView errorHtml(HttpServletRequest request, HttpServletResponse response) {
HttpStatus status = this.getStatus(request);
Map<String, Object> model = Collections.unmodifiableMap(this.getErrorAttributes(request, this.getErrorAttributeOptions(request, MediaType.TEXT_HTML)));
response.setStatus(status.value());
ModelAndView modelAndView = this.resolveErrorView(request, response, status, model);
request.setAttribute("model", model);
return modelAndView != null ? modelAndView : new ModelAndView("errorView", model);
}
}

View File

@@ -0,0 +1,40 @@
/*
* Copyright [2022] [https://www.xiaonuo.vip]
*
* Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点:
*
* 1.请不要删除和修改根目录下的LICENSE文件。
* 2.请不要删除和修改Snowy源码头部的版权声明。
* 3.本项目代码可免费商业使用,商业使用请保留源码和相关描述文件的项目出处,作者声明等。
* 4.分发源码时候,请注明软件出处 https://www.xiaonuo.vip
* 5.不可二次分发开源参与同类竞品如有想法可联系团队xiaonuobase@qq.com商议合作。
* 6.若您的项目无法满足以上几点需要更多功能代码获取Snowy商业授权许可请在官网购买授权地址为 https://www.xiaonuo.vip
*/
package org.nl.core.handler;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.nl.common.pojo.CommonResult;
/**
* 全局异常处理器
*
* @author xuyuxiang
* @date 2021/10/9 14:59
**/
@ControllerAdvice
public class GlobalExceptionHandler {
/**
* 不同异常返回不同结果
*
* @author xuyuxiang
* @date 2022/7/28 16:54
**/
@ResponseBody
@ExceptionHandler
public CommonResult<String> handleException(Exception e) {
return GlobalExceptionUtil.getCommonResult(e);
}
}

View File

@@ -0,0 +1,179 @@
/*
* Copyright [2022] [https://www.xiaonuo.vip]
*
* Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点:
*
* 1.请不要删除和修改根目录下的LICENSE文件。
* 2.请不要删除和修改Snowy源码头部的版权声明。
* 3.本项目代码可免费商业使用,商业使用请保留源码和相关描述文件的项目出处,作者声明等。
* 4.分发源码时候,请注明软件出处 https://www.xiaonuo.vip
* 5.不可二次分发开源参与同类竞品如有想法可联系团队xiaonuobase@qq.com商议合作。
* 6.若您的项目无法满足以上几点需要更多功能代码获取Snowy商业授权许可请在官网购买授权地址为 https://www.xiaonuo.vip
*/
package org.nl.core.handler;
import cn.dev33.satoken.exception.SaTokenException;
import cn.hutool.core.text.StrPool;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.http.HttpStatus;
import jakarta.validation.ConstraintViolation;
import jakarta.validation.ConstraintViolationException;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.ibatis.exceptions.PersistenceException;
import org.mybatis.spring.MyBatisSystemException;
import org.springframework.context.support.DefaultMessageSourceResolvable;
import org.springframework.http.HttpMethod;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.validation.BindException;
import org.springframework.validation.BindingResult;
import org.springframework.web.HttpMediaTypeNotSupportedException;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.multipart.MultipartException;
import org.springframework.web.multipart.support.MissingServletRequestPartException;
import org.nl.auth.core.util.AuthExceptionUtil;
import org.nl.common.exception.CommonException;
import org.nl.common.pojo.CommonResult;
import org.nl.common.util.CommonServletUtil;
import java.util.Set;
import java.util.stream.Collectors;
/**
* 全局异常处理工具类,将异常转为通用结果
*
* @author xuyuxiang
* @date 2021/12/18 16:44
*/
@Slf4j
public class GlobalExceptionUtil {
private GlobalExceptionUtil() {
}
/**
* 根据错误类型获取对应的CommonResult
*
* @author xuyuxiang
* @date 2021/10/11 15:52
**/
public static CommonResult<String> getCommonResult(Exception e) {
CommonResult<String> commonResult;
if (e instanceof HttpRequestMethodNotSupportedException) {
// 如果是请求方法异常 405
String method = CommonServletUtil.getRequest().getMethod();
if (HttpMethod.GET.toString().equals(method)) {
commonResult = CommonResult.get(HttpStatus.HTTP_BAD_METHOD, "请求方法应为POST", null);
} else if(HttpMethod.POST.toString().equals(method)) {
commonResult = CommonResult.get(HttpStatus.HTTP_BAD_METHOD, "请求方法应为GET", null);
} else {
commonResult = CommonResult.get(HttpStatus.HTTP_BAD_METHOD, "请求方法仅支持GET或POST", null);
}
} else if (e instanceof HttpMessageNotReadableException) {
log.error(">>> 参数传递格式异常:", e);
// 如果是参数传递格式不支持异常 415
if (e.getMessage().contains("JSON parse error")) {
//JSON格式转换错误特殊提示
commonResult = CommonResult.get(HttpStatus.HTTP_UNSUPPORTED_TYPE, "参数格式错误", null);
} else {
commonResult = CommonResult.get(HttpStatus.HTTP_UNSUPPORTED_TYPE, "请使用JSON方式传参", null);
}
} else if (e instanceof HttpMediaTypeNotSupportedException) {
log.error(">>> 参数传递格式异常:", e);
// 如果是JSON参数格式错误异常 415
commonResult = CommonResult.get(HttpStatus.HTTP_UNSUPPORTED_TYPE, "参数格式错误", null);
} else if (e instanceof MethodArgumentNotValidException methodArgumentNotValidException) {
// 如果是参数校验异常MethodArgumentNotValidException 415
commonResult = CommonResult.get(HttpStatus.HTTP_UNSUPPORTED_TYPE, getArgNotValidMessage(methodArgumentNotValidException.getBindingResult()), null);
} else if (e instanceof BindException bindException) {
// 如果是参数校验异常BindException 415
commonResult = CommonResult.get(HttpStatus.HTTP_UNSUPPORTED_TYPE, getArgNotValidMessage(bindException.getBindingResult()), null);
} else if (e instanceof ConstraintViolationException constraintViolationException) {
// 如果是参数校验异常ConstraintViolationException 415
commonResult = CommonResult.get(HttpStatus.HTTP_UNSUPPORTED_TYPE, getArgNotValidMessage(constraintViolationException.getConstraintViolations()), null);
} else if (e instanceof MissingServletRequestParameterException missingServletRequestParameterException) {
// 如果是参数校验异常MissingServletRequestParameterException 415
commonResult = CommonResult.get(HttpStatus.HTTP_UNSUPPORTED_TYPE, missingServletRequestParameterException.getMessage(), null);
}
else if (e instanceof MultipartException) {
log.error(">>> 文件上传参数异常:", e);
//文件上传错误特殊提示
commonResult = CommonResult.error("请使用multipart/form-data方式上传文件");
} else if (e instanceof MissingServletRequestPartException) {
log.error(">>> 文件上传参数异常:", e);
//文件上传错误特殊提示
commonResult = CommonResult.error("请选择要上传的文件并检查文件参数名称是否正确");
} else if (e instanceof SaTokenException) {
// 如果是SaToken相关异常则由AuthExceptionUtil处理
return AuthExceptionUtil.getCommonResult(e);
} else if(e instanceof MyBatisSystemException) {
// 如果是MyBatisSystemException
Throwable cause = e.getCause();
if (cause instanceof PersistenceException) {
Throwable secondCause = cause.getCause();
if (secondCause instanceof CommonException commonException) {
commonResult = CommonResult.get(commonException.getCode(), commonException.getMsg(), null);
} else {
log.error(">>> 数据操作异常:", e);
commonResult = CommonResult.error("数据操作异常");
}
} else {
log.error(">>> 数据操作异常:", e);
commonResult = CommonResult.error("数据操作异常");
}
} else if (e instanceof CommonException commonException) {
// 通用业务异常,直接返回给前端
commonResult = CommonResult.get(commonException.getCode(), commonException.getMsg(), null);
} else {
// 未知异常打印详情
log.error(">>> 服务器未知异常,请求地址:{},具体信息:", CommonServletUtil.getRequest().getRequestURL(), e);
// 未知异常返回服务器异常
commonResult = CommonResult.error();
}
return commonResult;
}
/**
* 获取请求参数不正确的提示信息,多个信息,拼接成用逗号分隔的形式
*
* @author xuyuxiang
* @date 2021/10/12 11:14
**/
public static String getArgNotValidMessage(Set<ConstraintViolation<?>> constraintViolationSet) {
if (CollectionUtils.isEmpty(constraintViolationSet)) {
return StringUtils.EMPTY;
}
// 多个错误用逗号分隔
return constraintViolationSet.stream()
.map(ConstraintViolation::getMessage)
.collect(Collectors.joining(StrPool.COMMA));
}
/**
* 获取请求参数不正确的提示信息,多个信息,拼接成用逗号分隔的形式
*
* @author xuyuxiang
* @date 2021/10/12 11:14
**/
public static String getArgNotValidMessage(BindingResult bindingResult) {
if (ObjectUtil.isNull(bindingResult)) {
return StringUtils.EMPTY;
}
// 多个错误用逗号分隔
return bindingResult.getAllErrors()
.stream()
.map(DefaultMessageSourceResolvable::getDefaultMessage)
.collect(Collectors.joining(StrPool.COMMA));
}
}

View File

@@ -0,0 +1,6 @@
#druid加密密码
java -cp druid-1.2.8.jar com.alibaba.druid.filter.config.ConfigTools your password
privateKey:MIIBVQIBADANBgkqhkiG9w0BAQEFAASCAT8wggE7AgEAAkEAxaJNW11cVWuAUcMMoQtkzRLLCRZjcqO6A33R5jlKiCifWPxjQKU8XufHcW4TukB4EqK/sKEhLS+/H/n1ZSkqZQIDAQABAkAN8PACBOjxgZ7QpbCrX5FhwfSelHy5ZoFFo8d9tQbj6dHOrfwHsRqAkAxGoZ/ivhDKUYf/G7eSwVUqv2H0T1m1AiEA9jRLwNL4/R5HhqfqNVMIF+GbuBhtM+HHPFOoOCR0f4sCIQDNf0ulYZf4DuyDiDuvpWjMtLEuNgnN0LOo1iVKFtM7zwIgfcdfwAXIEEAM2H0mSNG/e7vVeup3t56r02UFtpDhN1sCIQCGhrnnx07sJiDxLoMcRmWn9PY0sU2TvfePJLl+mhiogwIhAPPckbPRPJP52gutyn8GGNV28absrzjA6thi6KAej1aC
publicKey:MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAMWiTVtdXFVrgFHDDKELZM0SywkWY3KjugN90eY5Sogon1j8Y0ClPF7nx3FuE7pAeBKiv7ChIS0vvx/59WUpKmUCAwEAAQ==
password:DDnBgo6jKfgn7z+Qhn4/KTbzKzyT/ByFi7JDJA2w6yP/t++OQUjHJyApYeWaN4qVFV3V4fq0HbqTmkyWVj44yA==

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,6 @@
# 数据库说明
注意sql文件为mysql数据库的文件如需其他数据库文件可通过Navicat数据传输等工具传输到目标数据库。
文档地址https://xiaonuo.vip/doc
如果有其他问题请在群内提出。

View File

@@ -0,0 +1,203 @@
#########################################
# server configuration
#########################################
server.port=82
#########################################
# spring allow-circular-references
#########################################
spring.main.allow-circular-references=true
#########################################
# spring profiles configuration
#########################################
spring.profiles.active=local
#spring.profiles.active=test
#spring.profiles.active=prod
#########################################
# multipart configuration
#########################################
spring.servlet.multipart.max-request-size=100MB
spring.servlet.multipart.max-file-size=100MB
#########################################
# datasource configuration
#########################################
# mysql
spring.datasource.dynamic.datasource.master.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.dynamic.datasource.master.url=jdbc:mysql://localhost:3306/nl_tool?useUnicode=true&characterEncoding=utf-8&useSSL=false&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true&useInformationSchema=true&rewriteBatchedStatements=true
spring.datasource.dynamic.datasource.master.username=root
spring.datasource.dynamic.datasource.master.password=12356
spring.datasource.dynamic.strict=true
# postgres
#spring.datasource.dynamic.datasource.master.driver-class-name=org.postgresql.Driver
#spring.datasource.dynamic.datasource.master.url=jdbc:postgresql://localhost:5432/snowy
#spring.datasource.dynamic.datasource.master.username=postgres
#spring.datasource.dynamic.datasource.master.password=123456
#spring.datasource.dynamic.strict=true
# oracle
#spring.datasource.dynamic.datasource.master.driver-class-name=oracle.jdbc.OracleDriver
#spring.datasource.dynamic.datasource.master.url=jdbc:oracle:thin:@//127.0.0.1:1521/XE?remarksReporting=true
#spring.datasource.dynamic.datasource.master.username=SNOWY
#spring.datasource.dynamic.datasource.master.password=12345678
#spring.datasource.dynamic.strict=true
# mssql
#spring.datasource.dynamic.datasource.master.driver-class-name=com.microsoft.sqlserver.jdbc.SQLServerDriver
#spring.datasource.dynamic.datasource.master.url=jdbc:sqlserver://localhost:1433;DatabaseName=SNOWY
#spring.datasource.dynamic.datasource.master.username=sa
#spring.datasource.dynamic.datasource.master.password=123456
#spring.datasource.dynamic.strict=true
# dm database
#spring.datasource.dynamic.datasource.master.driver-class-name=dm.jdbc.driver.DmDriver
#spring.datasource.dynamic.datasource.master.url=jdbc:dm://localhost:5236/SYSDBA
#spring.datasource.dynamic.datasource.master.username=SYSDBA
#spring.datasource.dynamic.datasource.master.password=SYSDBA
#spring.datasource.dynamic.strict=true
# kingbase database
#spring.datasource.dynamic.datasource.master.driver-class-name=com.kingbase8.Driver
#spring.datasource.dynamic.datasource.master.url=jdbc:kingbase8://localhost:54321/snowy
#spring.datasource.dynamic.datasource.master.username=SYSTEM
#spring.datasource.dynamic.datasource.master.password=123456
#spring.datasource.dynamic.strict=true
# druid monitor configuration
spring.datasource.druid.stat-view-servlet.enabled=true
spring.datasource.druid.stat-view-servlet.login-username=admin
spring.datasource.druid.stat-view-servlet.login-password=123456
# druid global configuration
spring.datasource.dynamic.public-key=MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAMWiTVtdXFVrgFHDDKELZM0SywkWY3KjugN90eY5Sogon1j8Y0ClPF7nx3FuE7pAeBKiv7ChIS0vvx/59WUpKmUCAwEAAQ==
spring.datasource.dynamic.druid.initial-size=5
spring.datasource.dynamic.druid.max-active=20
spring.datasource.dynamic.druid.min-idle=5
spring.datasource.dynamic.druid.max-wait=60000
spring.datasource.dynamic.druid.pool-prepared-statements=true
spring.datasource.dynamic.druid.max-pool-prepared-statement-per-connection-size=20
spring.datasource.dynamic.druid.validation-query-timeout=2000
spring.datasource.dynamic.druid.test-on-borrow=false
spring.datasource.dynamic.druid.test-on-return=false
spring.datasource.dynamic.druid.test-while-idle=true
spring.datasource.dynamic.druid.time-between-eviction-runs-millis=60000
spring.datasource.dynamic.druid.min-evictable-idle-time-millis=300000
spring.datasource.dynamic.druid.filters=stat
spring.datasource.dynamic.druid.break-after-acquire-failure=false
#########################################
# jackson configuration
#########################################
spring.jackson.time-zone=GMT+8
spring.jackson.date-format=yyyy-MM-dd HH:mm:ss
spring.jackson.locale=zh_CN
spring.jackson.serialization.write-dates-as-timestamps=false
#########################################
# redis configuration
#########################################
spring.data.redis.database=1
spring.data.redis.host=127.0.0.1
spring.data.redis.port=6379
spring.data.redis.password=
spring.data.redis.timeout=10s
spring.data.redis.lettuce.pool.max-active=200
spring.data.redis.lettuce.pool.max-wait=-1ms
spring.data.redis.lettuce.pool.max-idle=10
spring.data.redis.lettuce.pool.min-idle=0
#########################################
# mybatis-plus configuration
#########################################
mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
mybatis-plus.configuration.jdbc-type-for-null=null
mybatis-plus.global-config.banner=false
mybatis-plus.global-config.enable-sql-runner=true
mybatis-plus.global-config.db-config.id-type=ASSIGN_ID
mybatis-plus.global-config.db-config.logic-delete-field=DELETE_FLAG
mybatis-plus.global-config.db-config.logic-delete-value=DELETED
mybatis-plus.global-config.db-config.logic-not-delete-value=NOT_DELETE
mybatis-plus.mapper-locations=classpath*:org/nl/**/mapping/*.xml
mybatis-plus.type-handlers-package=org.nl.common.handler
#########################################
# easy-trans configuration
#########################################
easy-trans.is-enable-redis=true
easy-trans.is-enable-global=true
easy-trans.is-enable-tile=true
easy-trans.is-enable-cloud=false
#########################################
# sa-token configuration
#########################################
sa-token.token-name=token
sa-token.timeout=2592000
sa-token.active-timeout=-1
sa-token.is-concurrent=true
sa-token.is-share=false
sa-token.max-login-count=-1
sa-token.token-style=random-32
sa-token.is-log=false
sa-token.is-print=false
# sa-token alone-redis configuration
sa-token.alone-redis.database=2
sa-token.alone-redis.host=${spring.data.redis.host}
sa-token.alone-redis.port=${spring.data.redis.port}
sa-token.alone-redis.password=${spring.data.redis.password}
sa-token.alone-redis.timeout=${spring.data.redis.timeout}
sa-token.alone-redis.lettuce.pool.max-active=${spring.data.redis.lettuce.pool.max-active}
sa-token.alone-redis.lettuce.pool.max-wait=${spring.data.redis.lettuce.pool.max-wait}
sa-token.alone-redis.lettuce.pool.max-idle=${spring.data.redis.lettuce.pool.max-idle}
sa-token.alone-redis.lettuce.pool.min-idle=${spring.data.redis.lettuce.pool.min-idle}
#########################################
# knife4j configuration
#########################################
knife4j.enable=true
knife4j.production=false
knife4j.basic.enable=true
knife4j.basic.username=admin
knife4j.basic.password=123456
knife4j.setting.enableOpenApi=false
knife4j.setting.enableSwaggerModels=false
knife4j.setting.enableFooter=false
knife4j.setting.enableFooterCustom=true
knife4j.setting.footerCustomContent=Apache License 2.0 | Copyright 2020-2024[SNOWY](https://www.xiaonuo.vip)
springdoc.default-flat-param-object=true
# knife4j doc groups
springdoc.group-configs[0].group=NL-PLUGIN-AUTH
springdoc.group-configs[0].display-name=${springdoc.group-configs[0].group}
springdoc.group-configs[0].packages-to-scan=org.nl.auth
springdoc.group-configs[1].group=NL-PLUGIN-BIZ
springdoc.group-configs[1].display-name=${springdoc.group-configs[1].group}
springdoc.group-configs[1].packages-to-scan=org.nl.biz
springdoc.group-configs[2].group=NL-PLUGIN-CLIENT
springdoc.group-configs[2].display-name=${springdoc.group-configs[2].group}
springdoc.group-configs[2].packages-to-scan=org.nl.client
springdoc.group-configs[3].group=NL-PLUGIN-DEV
springdoc.group-configs[3].display-name=${springdoc.group-configs[3].group}
springdoc.group-configs[3].packages-to-scan=org.nl.dev
springdoc.group-configs[4].group=NL-PLUGIN-GEN
springdoc.group-configs[4].display-name=${springdoc.group-configs[4].group}
springdoc.group-configs[4].packages-to-scan=org.nl.gen
springdoc.group-configs[5].group=NL-PLUGIN-MOBILE
springdoc.group-configs[5].display-name=${springdoc.group-configs[5].group}
springdoc.group-configs[5].packages-to-scan=org.nl.mobile
springdoc.group-configs[6].group=NL-PLUGIN-SYS
springdoc.group-configs[6].display-name=${springdoc.group-configs[6].group}
springdoc.group-configs[6].packages-to-scan=org.nl.sys
#########################################
# snowy configuration
#########################################
# common configuration
snowy.config.common.front-url=http://localhost:81
snowy.config.common.backend-url=http://localhost:82

View File

@@ -0,0 +1,118 @@
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<!--日志格式应用spring boot默认的格式也可以自己更改-->
<include resource="org/springframework/boot/logging/logback/defaults.xml"/>
<!--定义日志存放的位置,默认存放在项目启动的相对路径的目录-->
<springProperty scope="context" name="LOG_PATH" source="log.path" defaultValue="app-log"/>
<!-- ****************************************************************************************** -->
<!-- ****************************** 本地开发只在控制台打印日志 ************************************ -->
<!-- ****************************************************************************************** -->
<springProfile name="local">
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>${CONSOLE_LOG_PATTERN}</pattern>
<charset>utf-8</charset>
</encoder>
</appender>
<!--默认所有的包以info-->
<root level="info">
<appender-ref ref="STDOUT"/>
</root>
<!--各个服务的包在本地执行的时候打开debug模式-->
<logger name="org.nl" level="info" additivity="false">
<appender-ref ref="STDOUT"/>
</logger>
</springProfile>
<!-- ********************************************************************************************** -->
<!-- **** 放到服务器上不管在什么环境都只在文件记录日志控制台catalina.out打印logback捕获不到的日志 **** -->
<!-- ********************************************************************************************** -->
<springProfile name="!local">
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>${CONSOLE_LOG_PATTERN}</pattern>
<charset>utf-8</charset>
</encoder>
</appender>
<!-- 日志记录器,日期滚动记录 -->
<appender name="FILE_ERROR" class="ch.qos.logback.core.rolling.RollingFileAppender">
<!-- 正在记录的日志文件的路径及文件名 -->
<file>${LOG_PATH}/log_error.log</file>
<!-- 日志记录器的滚动策略,按日期,按大小记录 -->
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!-- 归档的日志文件的路径,%d{yyyy-MM-dd}指定日期格式,%i指定索引 -->
<fileNamePattern>${LOG_PATH}/error/log-error-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
<!-- 除按日志记录之外还配置了日志文件不能超过2M若超过2M日志文件会以索引0开始
命名日志文件例如log-error-2013-12-21.0.log -->
<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<maxFileSize>10MB</maxFileSize>
</timeBasedFileNamingAndTriggeringPolicy>
</rollingPolicy>
<!-- 追加方式记录日志 -->
<append>true</append>
<!-- 日志文件的格式 -->
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<pattern>${FILE_LOG_PATTERN}</pattern>
<charset>utf-8</charset>
</encoder>
<!-- 此日志文件只记录error级别的 -->
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>error</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
</appender>
<!-- 日志记录器,日期滚动记录 -->
<appender name="FILE_ALL" class="ch.qos.logback.core.rolling.RollingFileAppender">
<!-- 正在记录的日志文件的路径及文件名 -->
<file>${LOG_PATH}/log_total.log</file>
<!-- 日志记录器的滚动策略,按日期,按大小记录 -->
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!-- 归档的日志文件的路径,%d{yyyy-MM-dd}指定日期格式,%i指定索引 -->
<fileNamePattern>${LOG_PATH}/total/log-total-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
<!-- 除按日志记录之外还配置了日志文件不能超过2M若超过2M日志文件会以索引0开始
命名日志文件例如log-error-2013-12-21.0.log -->
<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<maxFileSize>10MB</maxFileSize>
</timeBasedFileNamingAndTriggeringPolicy>
</rollingPolicy>
<!-- 追加方式记录日志 -->
<append>true</append>
<!-- 日志文件的格式 -->
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<pattern>${FILE_LOG_PATTERN}</pattern>
<charset>utf-8</charset>
</encoder>
</appender>
<!--记录到文件时记录两类一类是error日志一个是所有日志-->
<root level="info">
<appender-ref ref="STDOUT"/>
<appender-ref ref="FILE_ERROR"/>
<appender-ref ref="FILE_ALL"/>
</root>
</springProfile>
</configuration>

Binary file not shown.

After

Width:  |  Height:  |  Size: 622 B

View File

@@ -0,0 +1,34 @@
/*
* Copyright [2022] [https://www.xiaonuo.vip]
*
* Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点:
*
* 1.请不要删除和修改根目录下的LICENSE文件。
* 2.请不要删除和修改Snowy源码头部的版权声明。
* 3.本项目代码可免费商业使用,商业使用请保留源码和相关描述文件的项目出处,作者声明等。
* 4.分发源码时候,请注明软件出处 https://www.xiaonuo.vip
* 5.不可二次分发开源参与同类竞品如有想法可联系团队xiaonuobase@qq.com商议合作。
* 6.若您的项目无法满足以上几点需要更多功能代码获取Snowy商业授权许可请在官网购买授权地址为 https://www.xiaonuo.vip
*/
package org.nl;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
/**
* 主测试类
*
* @author xuyuxiang
* @date 2022/9/17 17:09
*/
@RunWith(SpringRunner.class)
@SpringBootTest(classes = Application.class)
public class MainTest {
@Test
public void test() {
}
}

View File

@@ -0,0 +1,25 @@
/*
* Copyright [2022] [https://www.xiaonuo.vip]
*
* Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点:
*
* 1.请不要删除和修改根目录下的LICENSE文件。
* 2.请不要删除和修改Snowy源码头部的版权声明。
* 3.本项目代码可免费商业使用,商业使用请保留源码和相关描述文件的项目出处,作者声明等。
* 4.分发源码时候,请注明软件出处 https://www.xiaonuo.vip
* 5.不可二次分发开源参与同类竞品如有想法可联系团队xiaonuobase@qq.com商议合作。
* 6.若您的项目无法满足以上几点需要更多功能代码获取Snowy商业授权许可请在官网购买授权地址为 https://www.xiaonuo.vip
*/
package org.nl;
/**
* TestMain方法
*
* @author xuyuxiang
* @date 2022/9/17 17:10
*/
public class Test {
public static void main(String[] args) {
}
}