初版提交

This commit is contained in:
凌尛 2026-06-15 13:49:51 +08:00
parent 35fa3721c5
commit bde639c717
493 changed files with 50992 additions and 0 deletions

8
.gitignore vendored Normal file
View File

@ -0,0 +1,8 @@
/.run/
/ruoyi-admin/logs/
/ruoyi-admin/target/
/ruoyi-common/*/target/
/ruoyi-extend/*/target/
/ruoyi-modules/*/target/
/logs/
.idea

View File

@ -0,0 +1,83 @@
【2025-09-12 16:42:03】【日志开始,方法名称:addGoods】【centerBooksAdd】中心书库发布商品发布方法addGoods
【2025-09-12 16:42:03】【执行参数】{"taskBo":{"createDept":null,"createBy":null,"createTime":null,"updateBy":null,"updateTime":null,"id":"1966422039778070530","taskType":"1","shopIds":"1950426101074288642","shopNames":"古威音像专营店","fileName":"excel表格上传:发布商品.xlsx","dataNum":5,"taskStatus":"0","status":null,"threadId":null},"map":{"taskType":"1","way":"2","listStatus":"1","bookCategory":"0","shopIds":"1950426101074288642","data":{"total":5,"fileName":"发布商品.xlsx","url":"https://book.center.file.buzhiyushu.cn/TaskExcel/a49d39cd-f586-48db-bcd2-f39954aedadb_发布商品.xlsx"},"imageSelect":"1","deleteNum":0},"userId":"1922120835237036033"}
【2025-09-12 16:42:03】【系统异常】【异常方法】addGoods
【2025-09-12 16:42:03】【系统异常】【异常内容】org.springframework.web.client.HttpClientErrorException$BadRequest: 400 Bad Request on POST request for "http://localhost:16001/huidiao/pdd/addGoods": "{"code":400,"msg":"taskId参数不能为空","data":null}"
at org.springframework.web.client.HttpClientErrorException.create(HttpClientErrorException.java:103)
at org.springframework.web.client.DefaultResponseErrorHandler.handleError(DefaultResponseErrorHandler.java:186)
at org.springframework.web.client.DefaultResponseErrorHandler.handleError(DefaultResponseErrorHandler.java:147)
at org.springframework.web.client.RestTemplate.handleResponse(RestTemplate.java:953)
at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:902)
at org.springframework.web.client.RestTemplate.execute(RestTemplate.java:801)
at org.springframework.web.client.RestTemplate.postForEntity(RestTemplate.java:549)
at org.dromara.zhishu.util.InterfaceUtils.postForm(InterfaceUtils.java:162)
at org.dromara.zhishu.service.impl.TaskServiceImpl.addGoods(TaskServiceImpl.java:219)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:568)
at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:359)
at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:196)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163)
at org.springframework.aop.aspectj.MethodInvocationProceedingJoinPoint.proceed(MethodInvocationProceedingJoinPoint.java:89)
at org.dromara.zhishu.util.RunningLogUtils.runningLog(RunningLogUtils.java:42)
at org.dromara.zhishu.aspect.CenterAspect.addGoods(CenterAspect.java:88)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:568)
at org.springframework.aop.aspectj.AbstractAspectJAdvice.invokeAdviceMethodWithGivenArgs(AbstractAspectJAdvice.java:638)
at org.springframework.aop.aspectj.AbstractAspectJAdvice.invokeAdviceMethod(AbstractAspectJAdvice.java:628)
at org.springframework.aop.aspectj.AspectJAroundAdvice.invoke(AspectJAroundAdvice.java:71)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:173)
at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:97)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184)
at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:727)
at org.dromara.zhishu.service.impl.TaskServiceImpl$$SpringCGLIB$$0.addGoods(<generated>)
at org.dromara.zhishu.util.TaskRunnable.run(TaskRunnable.java:39)
at java.base/java.lang.Thread.run(Thread.java:842)
【2025-09-12 16:42:03】【日志结束,方法名称:addGoods】【centerBooksAdd】中心书库发布商品发布方法addGoods
【2025-09-12 16:44:17】【日志开始,方法名称:addGoods】【centerBooksAdd】中心书库发布商品发布方法addGoods
【2025-09-12 16:44:17】【执行参数】{"taskBo":{"createDept":null,"createBy":null,"createTime":null,"updateBy":null,"updateTime":null,"id":"1966422601206657025","taskType":"1","shopIds":"1950426101074288642","shopNames":"古威音像专营店","fileName":"excel表格上传:发布商品.xlsx","dataNum":5,"taskStatus":"0","status":null,"threadId":null},"map":{"taskType":"1","way":"2","listStatus":"1","bookCategory":"0","shopIds":"1950426101074288642","data":{"total":5,"fileName":"发布商品.xlsx","url":"https://book.center.file.buzhiyushu.cn/TaskExcel/e9850515-b6d0-4edd-a0df-47597e0e1679_发布商品.xlsx"},"imageSelect":"1","deleteNum":0},"userId":"1922120835237036033"}
【2025-09-12 16:45:02】【系统异常】【异常方法】addGoods
【2025-09-12 16:45:02】【系统异常】【异常内容】org.springframework.web.client.HttpClientErrorException$BadRequest: 400 Bad Request on POST request for "http://localhost:16001/huidiao/pdd/addGoods": "{"code":400,"msg":"taskId参数不能为空","data":null}"
at org.springframework.web.client.HttpClientErrorException.create(HttpClientErrorException.java:103)
at org.springframework.web.client.DefaultResponseErrorHandler.handleError(DefaultResponseErrorHandler.java:186)
at org.springframework.web.client.DefaultResponseErrorHandler.handleError(DefaultResponseErrorHandler.java:147)
at org.springframework.web.client.RestTemplate.handleResponse(RestTemplate.java:953)
at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:902)
at org.springframework.web.client.RestTemplate.execute(RestTemplate.java:801)
at org.springframework.web.client.RestTemplate.postForEntity(RestTemplate.java:549)
at org.dromara.zhishu.util.InterfaceUtils.postForm(InterfaceUtils.java:162)
at org.dromara.zhishu.service.impl.TaskServiceImpl.addGoods(TaskServiceImpl.java:219)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:568)
at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:359)
at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:196)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163)
at org.springframework.aop.aspectj.MethodInvocationProceedingJoinPoint.proceed(MethodInvocationProceedingJoinPoint.java:89)
at org.dromara.zhishu.util.RunningLogUtils.runningLog(RunningLogUtils.java:42)
at org.dromara.zhishu.aspect.CenterAspect.addGoods(CenterAspect.java:88)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:568)
at org.springframework.aop.aspectj.AbstractAspectJAdvice.invokeAdviceMethodWithGivenArgs(AbstractAspectJAdvice.java:638)
at org.springframework.aop.aspectj.AbstractAspectJAdvice.invokeAdviceMethod(AbstractAspectJAdvice.java:628)
at org.springframework.aop.aspectj.AspectJAroundAdvice.invoke(AspectJAroundAdvice.java:71)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:173)
at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:97)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184)
at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:727)
at org.dromara.zhishu.service.impl.TaskServiceImpl$$SpringCGLIB$$0.addGoods(<generated>)
at org.dromara.zhishu.util.TaskRunnable.run(TaskRunnable.java:39)
at java.base/java.lang.Thread.run(Thread.java:842)
【2025-09-12 16:45:02】【日志结束,方法名称:addGoods】【centerBooksAdd】中心书库发布商品发布方法addGoods
【2025-09-12 16:46:53】【日志开始,方法名称:addGoods】【centerBooksAdd】中心书库发布商品发布方法addGoods
【2025-09-12 16:46:53】【执行参数】{"taskBo":{"createDept":null,"createBy":null,"createTime":null,"updateBy":null,"updateTime":null,"id":"1966423256906452994","taskType":"1","shopIds":"1950426101074288642","shopNames":"古威音像专营店","fileName":"excel表格上传:发布商品.xlsx","dataNum":5,"taskStatus":"0","status":null,"threadId":null},"map":{"taskType":"1","way":"2","listStatus":"1","bookCategory":"0","shopIds":"1950426101074288642","data":{"total":5,"fileName":"发布商品.xlsx","url":"https://book.center.file.buzhiyushu.cn/TaskExcel/938cddf9-3fe6-4e69-ac9d-08d0d465fe2b_发布商品.xlsx"},"imageSelect":"1","deleteNum":0},"userId":"1922120835237036033"}
【2025-09-12 16:46:59】【返回参数】null
【2025-09-12 16:46:59】【日志结束,方法名称:addGoods】【centerBooksAdd】中心书库发布商品发布方法addGoods

View File

@ -0,0 +1,9 @@
【2025-09-24 10:19:26】【日志开始,方法名称:addGoods】【centerBooksAdd】中心书库发布商品发布方法addGoods
【2025-09-24 10:19:26】【执行参数】{"taskBo":{"createDept":null,"createBy":null,"createTime":null,"updateBy":null,"updateTime":null,"id":"1970674404790181890","taskType":"1","shopIds":"1969343852652916737","shopNames":"测试咸鱼B","fileName":"excel表格上传:task_template_1758680286171.xlsx","dataNum":2,"taskStatus":"0","status":null,"threadId":null,"relationId":null},"map":{"taskType":"1","way":"2","listStatus":"1","bookCategory":"0","shopIds":"1969343852652916737","data":{"total":2,"fileName":"task_template_1758680286171.xlsx","url":"https://book.center.file.buzhiyushu.cn/TaskExcel/4846c4c8-b5e4-4bad-b774-70c782989cdc_task_template_1758680286171.xlsx"},"imageSelect":"1","deleteNum":0},"userId":"1922120835237036033"}
【2025-09-24 10:19:26】【返回参数】null
【2025-09-24 10:19:26】【日志结束,方法名称:addGoods】【centerBooksAdd】中心书库发布商品发布方法addGoods
【2025-09-24 10:49:40】【日志开始,方法名称:addGoods】【centerBooksAdd】中心书库发布商品发布方法addGoods
【2025-09-24 10:49:40】【执行参数】{"taskBo":{"createDept":null,"createBy":null,"createTime":null,"updateBy":null,"updateTime":null,"id":"1970682012628492289","taskType":"1","shopIds":"1969343852652916737","shopNames":"测试咸鱼B","fileName":"excel表格上传:task_template_1758680286171.xlsx","dataNum":2,"taskStatus":"0","status":null,"threadId":null,"relationId":null},"map":{"taskType":"1","way":"2","listStatus":"1","bookCategory":"0","shopIds":"1969343852652916737","data":{"total":2,"fileName":"task_template_1758680286171.xlsx","url":"https://book.center.file.buzhiyushu.cn/TaskExcel/ade02a9e-1b3f-47b5-8cd0-de6d58ce15e9_task_template_1758680286171.xlsx"},"imageSelect":"1","deleteNum":0},"userId":"1922120835237036033"}
【2025-09-24 10:49:40】【返回参数】null
【2025-09-24 10:49:40】【日志结束,方法名称:addGoods】【centerBooksAdd】中心书库发布商品发布方法addGoods

View File

@ -0,0 +1,9 @@
【2025-09-26 14:34:54】【日志开始,方法名称:addGoods】【centerBooksAdd】中心书库发布商品发布方法addGoods
【2025-09-26 14:34:54】【执行参数】{"taskBo":{"createDept":null,"createBy":null,"createTime":null,"updateBy":null,"updateTime":null,"id":"1971463472444612609","taskType":"1","shopIds":"1950426101074288642","shopNames":"古威音像专营店","fileName":"excel表格上传:task_template_1758680286171.xlsx","dataNum":6,"taskStatus":"0","status":null,"threadId":null,"relationId":null},"map":{"taskType":"1","way":"0","listStatus":"1","bookCategory":"0","shopIds":"1950426101074288642","data":{"total":6,"fileName":"task_template_1758680286171.xlsx","url":"https://book.center.file.buzhiyushu.cn/TaskExcel/f21d67f0-4f18-4262-a8dc-581a9585075a_task_template_1758680286171.xlsx"},"imageSelect":"1","deleteNum":0},"userId":"1922120835237036033"}
【2025-09-26 14:36:57】【日志开始,方法名称:addGoods】【centerBooksAdd】中心书库发布商品发布方法addGoods
【2025-09-26 14:36:57】【执行参数】{"taskBo":{"createDept":null,"createBy":null,"createTime":null,"updateBy":null,"updateTime":null,"id":"1971463986884431874","taskType":"1","shopIds":"1950426101074288642","shopNames":"古威音像专营店","fileName":"excel表格上传:task_template_1758680286171.xlsx","dataNum":6,"taskStatus":"0","status":null,"threadId":null,"relationId":null},"map":{"taskType":"1","way":"0","listStatus":"0","bookCategory":"0","shopIds":"1950426101074288642","data":{"total":6,"fileName":"task_template_1758680286171.xlsx","url":"https://book.center.file.buzhiyushu.cn/TaskExcel/fff1d06c-2ae0-41b4-91db-436acd3f4514_task_template_1758680286171.xlsx"},"imageSelect":"1","deleteNum":0},"userId":"1922120835237036033"}
【2025-09-30 09:35:42】【日志开始,方法名称:addGoods】【centerBooksAdd】中心书库发布商品发布方法addGoods
【2025-09-30 09:35:42】【执行参数】{"taskBo":{"createDept":null,"createBy":null,"createTime":null,"updateBy":null,"updateTime":null,"id":"1972837725932933122","taskType":"1","shopIds":"1972584272471408642","shopNames":"咸鱼-与书","fileName":"excel表格上传:task_template_1759135960072.xlsx","dataNum":3,"taskStatus":"0","status":null,"threadId":null,"relationId":null},"map":{"taskType":"1","way":"2","listStatus":"1","bookCategory":"0","shopIds":"1972584272471408642","data":{"total":3,"fileName":"task_template_1759135960072.xlsx","url":"https://book.center.file.buzhiyushu.cn/TaskExcel/117f4be8-f6c5-42b6-aa4d-0efbe85156f5_task_template_1759135960072.xlsx"},"imageSelect":"1","deleteNum":0},"userId":"1922120835237036033"}
【2025-09-30 09:35:45】【返回参数】null
【2025-09-30 09:35:45】【日志结束,方法名称:addGoods】【centerBooksAdd】中心书库发布商品发布方法addGoods

View File

@ -0,0 +1,505 @@
【2026-03-10 10:41:50】【日志开始,方法名称:addGoods】【centerBooksAdd】中心书库发布商品发布方法addGoods
【2026-03-10 10:41:50】【执行参数】{"taskBo":{"createDept":null,"createBy":null,"createTime":null,"updateBy":null,"updateTime":null,"id":null,"taskType":null,"shopIds":null,"shopNames":null,"fileName":null,"dataNum":null,"taskStatus":null,"status":null,"threadId":null,"relationId":null},"map":{"taskType":"1","priceAdjustments":[{"userId":"1922120835237036033","proportion":100,"addNum":0}],"data":{"total":5,"fileName":"task_template_1768886957287.xlsx","url":"https://book.center.file.buzhiyushu.cn/TaskExcel/edc618cb-e314-4d78-b42d-a91901cceb56_task_template_1768886957287.xlsx"},"imageSelect":"1","deleteNum":0,"shopIds":"2031193954362281985","listStatus":"0","synchronizationType":"1","bookCategory":"0","way":"0","createResStr":"{\"code\":\"200\",\"data\":\"2031198802537304066\",\"msg\":\"成功\"}\n"},"userId":"1922120835237036033"}
【2026-03-10 10:42:04】【返回参数】null
【2026-03-10 10:42:04】【日志结束,方法名称:addGoods】【centerBooksAdd】中心书库发布商品发布方法addGoods
【2026-03-10 10:45:13】【日志开始,方法名称:addGoods】【centerBooksAdd】中心书库发布商品发布方法addGoods
【2026-03-10 10:45:13】【执行参数】{"taskBo":{"createDept":null,"createBy":null,"createTime":null,"updateBy":null,"updateTime":null,"id":null,"taskType":null,"shopIds":null,"shopNames":null,"fileName":null,"dataNum":null,"taskStatus":null,"status":null,"threadId":null,"relationId":null},"map":{"taskType":"1","priceAdjustments":[{"userId":"1922120835237036033","proportion":100,"addNum":0}],"data":{"total":1,"fileName":"task_template_1768886957287.xlsx","url":"https://book.center.file.buzhiyushu.cn/TaskExcel/9a8dcb84-4208-4182-a8f4-8be5d5f63fc1_task_template_1768886957287.xlsx"},"imageSelect":"1","deleteNum":0,"shopIds":"2031193954362281985","listStatus":"0","synchronizationType":"1","bookCategory":"0","way":"0","createResStr":"{\"code\":\"200\",\"data\":\"2031199655822315521\",\"msg\":\"成功\"}\n"},"userId":"1922120835237036033"}
【2026-03-10 11:02:45】【日志开始,方法名称:addGoods】【centerBooksAdd】中心书库发布商品发布方法addGoods
【2026-03-10 11:02:45】【执行参数】{"taskBo":{"createDept":null,"createBy":null,"createTime":null,"updateBy":null,"updateTime":null,"id":null,"taskType":null,"shopIds":null,"shopNames":null,"fileName":null,"dataNum":null,"taskStatus":null,"status":null,"threadId":null,"relationId":null},"map":{"taskType":"1","priceAdjustments":[{"userId":"1922120835237036033","proportion":100,"addNum":0}],"data":{"total":1,"fileName":"新发布商品模板.xlsx","url":"https://book.center.file.buzhiyushu.cn/TaskExcel/2006cdcc-e7b2-477b-bfb2-508ed917c365_新发布商品模板.xlsx"},"imageSelect":"1","deleteNum":0,"shopIds":"2031193954362281985","listStatus":"0","synchronizationType":"1","bookCategory":"0","way":"0","createResStr":"{\"code\":\"200\",\"data\":\"2031204066179108866\",\"msg\":\"成功\"}\n"},"userId":"1922120835237036033"}
【2026-03-10 11:04:16】【日志开始,方法名称:addGoods】【centerBooksAdd】中心书库发布商品发布方法addGoods
【2026-03-10 11:04:16】【执行参数】{"taskBo":{"createDept":null,"createBy":null,"createTime":null,"updateBy":null,"updateTime":null,"id":null,"taskType":null,"shopIds":null,"shopNames":null,"fileName":null,"dataNum":null,"taskStatus":null,"status":null,"threadId":null,"relationId":null},"map":{"taskType":"1","priceAdjustments":[{"userId":"1922120835237036033","proportion":100,"addNum":0}],"data":{"total":1,"fileName":"新发布商品模板.xlsx","url":"https://book.center.file.buzhiyushu.cn/TaskExcel/52c54894-830c-44bb-a683-1ae46287e465_新发布商品模板.xlsx"},"imageSelect":"1","deleteNum":0,"shopIds":"2031193954362281985","listStatus":"0","synchronizationType":"1","bookCategory":"0","way":"0","createResStr":"{\"code\":\"200\",\"data\":\"2031204449580437506\",\"msg\":\"成功\"}\n"},"userId":"1922120835237036033"}
【2026-03-10 15:17:41】【日志开始,方法名称:addGoods】【centerBooksAdd】中心书库发布商品发布方法addGoods
【2026-03-10 15:17:41】【执行参数】{"taskBo":{"createDept":null,"createBy":null,"createTime":null,"updateBy":null,"updateTime":null,"id":null,"taskType":null,"shopIds":null,"shopNames":null,"fileName":null,"dataNum":null,"taskStatus":null,"status":null,"threadId":null,"relationId":null},"map":{"taskType":"1","priceAdjustments":[{"userId":"1922120835237036033","proportion":100,"addNum":0}],"data":{"total":1,"fileName":"新发布商品模板.xlsx","url":"https://book.center.file.buzhiyushu.cn/TaskExcel/757d04a7-df86-4a9a-959c-3de34bc7459d_新发布商品模板.xlsx"},"imageSelect":"1","deleteNum":0,"shopIds":"2031193954362281985","listStatus":"0","synchronizationType":"1","bookCategory":"0","way":"0","createResStr":"{\"code\":\"200\",\"data\":\"2031268236266221570\",\"msg\":\"成功\"}\n"},"userId":"1922120835237036033"}
【2026-03-10 15:17:56】【系统异常】【异常方法】addGoods
【2026-03-10 15:17:56】【系统异常】【异常内容】org.springframework.web.client.HttpClientErrorException$BadRequest: 400 Bad Request on POST request for "http://36.212.20.113:8182/api/goods/simple": "{"code":400,"message":"shopid和isbn不能为空","success":false}"
at org.springframework.web.client.HttpClientErrorException.create(HttpClientErrorException.java:103)
at org.springframework.web.client.DefaultResponseErrorHandler.handleError(DefaultResponseErrorHandler.java:186)
at org.springframework.web.client.DefaultResponseErrorHandler.handleError(DefaultResponseErrorHandler.java:147)
at org.springframework.web.client.RestTemplate.handleResponse(RestTemplate.java:953)
at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:902)
at org.springframework.web.client.RestTemplate.execute(RestTemplate.java:801)
at org.springframework.web.client.RestTemplate.postForEntity(RestTemplate.java:549)
at org.dromara.zhishu.util.InterfaceUtils.postForm(InterfaceUtils.java:249)
at org.dromara.zhishu.service.impl.TaskServiceImpl.addGoods(TaskServiceImpl.java:251)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:568)
at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:359)
at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:196)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163)
at org.springframework.aop.aspectj.MethodInvocationProceedingJoinPoint.proceed(MethodInvocationProceedingJoinPoint.java:89)
at org.dromara.zhishu.util.RunningLogUtils.runningLog(RunningLogUtils.java:42)
at org.dromara.zhishu.aspect.CenterAspect.addGoods(CenterAspect.java:88)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:568)
at org.springframework.aop.aspectj.AbstractAspectJAdvice.invokeAdviceMethodWithGivenArgs(AbstractAspectJAdvice.java:638)
at org.springframework.aop.aspectj.AbstractAspectJAdvice.invokeAdviceMethod(AbstractAspectJAdvice.java:628)
at org.springframework.aop.aspectj.AspectJAroundAdvice.invoke(AspectJAroundAdvice.java:71)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:173)
at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:97)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184)
at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:727)
at org.dromara.zhishu.service.impl.TaskServiceImpl$$SpringCGLIB$$0.addGoods(<generated>)
at org.dromara.zhishu.util.TaskRunnable.run(TaskRunnable.java:39)
at java.base/java.lang.Thread.run(Thread.java:842)
【2026-03-10 15:17:56】【日志结束,方法名称:addGoods】【centerBooksAdd】中心书库发布商品发布方法addGoods
【2026-03-10 15:18:14】【日志开始,方法名称:addGoods】【centerBooksAdd】中心书库发布商品发布方法addGoods
【2026-03-10 15:18:14】【执行参数】{"taskBo":{"createDept":null,"createBy":null,"createTime":null,"updateBy":null,"updateTime":null,"id":null,"taskType":null,"shopIds":null,"shopNames":null,"fileName":null,"dataNum":null,"taskStatus":null,"status":null,"threadId":null,"relationId":null},"map":{"taskType":"1","priceAdjustments":[{"userId":"1922120835237036033","proportion":100,"addNum":0}],"data":{"total":1,"fileName":"新发布商品模板.xlsx","url":"https://book.center.file.buzhiyushu.cn/TaskExcel/54a70299-eb39-43f9-b726-32964f9d1c0d_新发布商品模板.xlsx"},"imageSelect":"1","deleteNum":0,"shopIds":"2031193954362281985","listStatus":"0","synchronizationType":"1","bookCategory":"0","way":"0","createResStr":"{\"code\":\"200\",\"data\":\"2031268371826126849\",\"msg\":\"成功\"}\n"},"userId":"1922120835237036033"}
【2026-03-10 15:18:53】【系统异常】【异常方法】addGoods
【2026-03-10 15:18:53】【系统异常】【异常内容】org.springframework.web.client.HttpClientErrorException$BadRequest: 400 Bad Request on POST request for "http://36.212.20.113:8182/api/goods/simple": "{"code":400,"message":"shopid和isbn不能为空","success":false}"
at org.springframework.web.client.HttpClientErrorException.create(HttpClientErrorException.java:103)
at org.springframework.web.client.DefaultResponseErrorHandler.handleError(DefaultResponseErrorHandler.java:186)
at org.springframework.web.client.DefaultResponseErrorHandler.handleError(DefaultResponseErrorHandler.java:147)
at org.springframework.web.client.RestTemplate.handleResponse(RestTemplate.java:953)
at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:902)
at org.springframework.web.client.RestTemplate.execute(RestTemplate.java:801)
at org.springframework.web.client.RestTemplate.postForEntity(RestTemplate.java:549)
at org.dromara.zhishu.util.InterfaceUtils.postForm(InterfaceUtils.java:249)
at org.dromara.zhishu.service.impl.TaskServiceImpl.addGoods(TaskServiceImpl.java:251)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:568)
at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:359)
at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:196)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163)
at org.springframework.aop.aspectj.MethodInvocationProceedingJoinPoint.proceed(MethodInvocationProceedingJoinPoint.java:89)
at org.dromara.zhishu.util.RunningLogUtils.runningLog(RunningLogUtils.java:42)
at org.dromara.zhishu.aspect.CenterAspect.addGoods(CenterAspect.java:88)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:568)
at org.springframework.aop.aspectj.AbstractAspectJAdvice.invokeAdviceMethodWithGivenArgs(AbstractAspectJAdvice.java:638)
at org.springframework.aop.aspectj.AbstractAspectJAdvice.invokeAdviceMethod(AbstractAspectJAdvice.java:628)
at org.springframework.aop.aspectj.AspectJAroundAdvice.invoke(AspectJAroundAdvice.java:71)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:173)
at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:97)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184)
at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:727)
at org.dromara.zhishu.service.impl.TaskServiceImpl$$SpringCGLIB$$0.addGoods(<generated>)
at org.dromara.zhishu.util.TaskRunnable.run(TaskRunnable.java:39)
at java.base/java.lang.Thread.run(Thread.java:842)
【2026-03-10 15:18:53】【日志结束,方法名称:addGoods】【centerBooksAdd】中心书库发布商品发布方法addGoods
【2026-03-10 15:20:10】【日志开始,方法名称:addGoods】【centerBooksAdd】中心书库发布商品发布方法addGoods
【2026-03-10 15:20:10】【执行参数】{"taskBo":{"createDept":null,"createBy":null,"createTime":null,"updateBy":null,"updateTime":null,"id":null,"taskType":null,"shopIds":null,"shopNames":null,"fileName":null,"dataNum":null,"taskStatus":null,"status":null,"threadId":null,"relationId":null},"map":{"taskType":"1","priceAdjustments":[{"userId":"1922120835237036033","proportion":100,"addNum":0}],"data":{"total":1,"fileName":"新发布商品模板.xlsx","url":"https://book.center.file.buzhiyushu.cn/TaskExcel/b4ceb153-1151-498f-9d7c-5c44b686048c_新发布商品模板.xlsx"},"imageSelect":"1","deleteNum":0,"shopIds":"2031193954362281985","listStatus":"0","synchronizationType":"1","bookCategory":"0","way":"0","createResStr":"{\"code\":\"200\",\"data\":\"2031268858075983874\",\"msg\":\"成功\"}\n"},"userId":"1922120835237036033"}
【2026-03-10 15:23:45】【日志开始,方法名称:addGoods】【centerBooksAdd】中心书库发布商品发布方法addGoods
【2026-03-10 15:23:45】【执行参数】{"taskBo":{"createDept":null,"createBy":null,"createTime":null,"updateBy":null,"updateTime":null,"id":null,"taskType":null,"shopIds":null,"shopNames":null,"fileName":null,"dataNum":null,"taskStatus":null,"status":null,"threadId":null,"relationId":null},"map":{"taskType":"1","priceAdjustments":[{"userId":"1922120835237036033","proportion":100,"addNum":0}],"data":{"total":1,"fileName":"新发布商品模板.xlsx","url":"https://book.center.file.buzhiyushu.cn/TaskExcel/d3c3a434-66d5-4926-b299-697fb57e1b97_新发布商品模板.xlsx"},"imageSelect":"1","deleteNum":0,"shopIds":"2031193954362281985","listStatus":"0","synchronizationType":"1","bookCategory":"0","way":"2","createResStr":"{\"code\":\"200\",\"data\":\"2031269763131289602\",\"msg\":\"成功\"}\n"},"userId":"1922120835237036033"}
【2026-03-11 11:49:23】【日志开始,方法名称:addGoods】【centerBooksAdd】中心书库发布商品发布方法addGoods
【2026-03-11 11:49:23】【执行参数】{"taskBo":{"createDept":null,"createBy":null,"createTime":null,"updateBy":null,"updateTime":null,"id":null,"taskType":null,"shopIds":null,"shopNames":null,"fileName":null,"dataNum":null,"taskStatus":null,"status":null,"threadId":null,"relationId":null},"map":{"taskType":"1","priceAdjustments":[{"userId":"1922120835237036033","proportion":100,"addNum":0}],"data":{"total":1,"fileName":"新发布商品模板.xlsx","url":"https://book.center.file.buzhiyushu.cn/TaskExcel/3e1aca3d-ee2a-4fcd-8222-741e4976060f_新发布商品模板.xlsx"},"imageSelect":"1","deleteNum":0,"shopIds":"2031193954362281985","listStatus":"0","synchronizationType":"1","bookCategory":"0","way":"2","createResStr":"{\"code\":\"200\",\"data\":\"2031578199664582658\",\"msg\":\"成功\"}\n"},"userId":"1922120835237036033"}
【2026-03-11 11:56:16】【日志开始,方法名称:addGoods】【centerBooksAdd】中心书库发布商品发布方法addGoods
【2026-03-11 11:56:16】【执行参数】{"taskBo":{"createDept":null,"createBy":null,"createTime":null,"updateBy":null,"updateTime":null,"id":null,"taskType":null,"shopIds":null,"shopNames":null,"fileName":null,"dataNum":null,"taskStatus":null,"status":null,"threadId":null,"relationId":null},"map":{"taskType":"1","priceAdjustments":[{"userId":"1922120835237036033","proportion":100,"addNum":0}],"data":{"total":2,"fileName":"新发布商品模板.xlsx","url":"https://book.center.file.buzhiyushu.cn/TaskExcel/b16816c2-6a67-4fb0-8412-d13932127937_新发布商品模板.xlsx"},"imageSelect":"1","deleteNum":0,"shopIds":"2031193954362281985","listStatus":"0","synchronizationType":"1","bookCategory":"0","way":"0","createResStr":"{\"code\":\"200\",\"data\":\"2031579932725829634\",\"msg\":\"成功\"}\n"},"userId":"1922120835237036033"}
【2026-03-11 11:57:48】【返回参数】null
【2026-03-11 11:57:48】【日志结束,方法名称:addGoods】【centerBooksAdd】中心书库发布商品发布方法addGoods
【2026-03-11 13:06:06】【日志开始,方法名称:addGoods】【centerBooksAdd】中心书库发布商品发布方法addGoods
【2026-03-11 13:06:06】【执行参数】{"taskBo":{"createDept":null,"createBy":null,"createTime":null,"updateBy":null,"updateTime":null,"id":null,"taskType":null,"shopIds":null,"shopNames":null,"fileName":null,"dataNum":null,"taskStatus":null,"status":null,"threadId":null,"relationId":null},"map":{"taskType":"1","priceAdjustments":[{"userId":"1922120835237036033","proportion":100,"addNum":0}],"data":{"total":2,"fileName":"新发布商品模板.xlsx","url":"https://book.center.file.buzhiyushu.cn/TaskExcel/8154c95b-49a7-4abb-ad57-aca463465197_新发布商品模板.xlsx"},"imageSelect":"1","deleteNum":0,"shopIds":"2031193954362281985","listStatus":"0","synchronizationType":"1","bookCategory":"0","way":"0","createResStr":"{\"code\":\"200\",\"data\":\"2031597508189184001\",\"msg\":\"成功\"}\n"},"userId":"1922120835237036033"}
【2026-03-11 13:06:39】【返回参数】null
【2026-03-11 13:06:39】【日志结束,方法名称:addGoods】【centerBooksAdd】中心书库发布商品发布方法addGoods
【2026-03-11 13:11:49】【日志开始,方法名称:addGoods】【centerBooksAdd】中心书库发布商品发布方法addGoods
【2026-03-11 13:11:49】【执行参数】{"taskBo":{"createDept":null,"createBy":null,"createTime":null,"updateBy":null,"updateTime":null,"id":null,"taskType":null,"shopIds":null,"shopNames":null,"fileName":null,"dataNum":null,"taskStatus":null,"status":null,"threadId":null,"relationId":null},"map":{"taskType":"1","priceAdjustments":[{"userId":"1922120835237036033","proportion":100,"addNum":0}],"data":{"total":2,"fileName":"新发布商品模板.xlsx","url":"https://book.center.file.buzhiyushu.cn/TaskExcel/b3d32db8-f407-4ef5-9cf2-30452fa01b23_新发布商品模板.xlsx"},"imageSelect":"1","deleteNum":0,"shopIds":"2031193954362281985","listStatus":"0","synchronizationType":"1","bookCategory":"0","way":"0","createResStr":"{\"code\":\"200\",\"data\":\"2031598944276930561\",\"msg\":\"成功\"}\n"},"userId":"1922120835237036033"}
【2026-03-11 13:12:16】【返回参数】null
【2026-03-11 13:12:16】【日志结束,方法名称:addGoods】【centerBooksAdd】中心书库发布商品发布方法addGoods
【2026-03-11 13:13:56】【日志开始,方法名称:addGoods】【centerBooksAdd】中心书库发布商品发布方法addGoods
【2026-03-11 13:13:57】【执行参数】{"taskBo":{"createDept":null,"createBy":null,"createTime":null,"updateBy":null,"updateTime":null,"id":null,"taskType":null,"shopIds":null,"shopNames":null,"fileName":null,"dataNum":null,"taskStatus":null,"status":null,"threadId":null,"relationId":null},"map":{"taskType":"1","priceAdjustments":[{"userId":"1922120835237036033","proportion":100,"addNum":0}],"data":{"total":2,"fileName":"新发布商品模板.xlsx","url":"https://book.center.file.buzhiyushu.cn/TaskExcel/a93fd262-827b-496b-a101-c859c4055275_新发布商品模板.xlsx"},"imageSelect":"1","deleteNum":0,"shopIds":"2031193954362281985","listStatus":"0","synchronizationType":"1","bookCategory":"0","way":"0","createResStr":"{\"code\":\"200\",\"data\":\"2031599480594194433\",\"msg\":\"成功\"}\n"},"userId":"1922120835237036033"}
【2026-03-11 13:14:08】【返回参数】null
【2026-03-11 13:14:08】【日志结束,方法名称:addGoods】【centerBooksAdd】中心书库发布商品发布方法addGoods
【2026-03-11 13:19:22】【日志开始,方法名称:addGoods】【centerBooksAdd】中心书库发布商品发布方法addGoods
【2026-03-11 13:19:22】【执行参数】{"taskBo":{"createDept":null,"createBy":null,"createTime":null,"updateBy":null,"updateTime":null,"id":null,"taskType":null,"shopIds":null,"shopNames":null,"fileName":null,"dataNum":null,"taskStatus":null,"status":null,"threadId":null,"relationId":null},"map":{"taskType":"1","priceAdjustments":[{"userId":"1922120835237036033","proportion":100,"addNum":0}],"data":{"total":2,"fileName":"新发布商品模板.xlsx","url":"https://book.center.file.buzhiyushu.cn/TaskExcel/cba5c4e9-8a3c-4f12-9081-935e558b60c0_新发布商品模板.xlsx"},"imageSelect":"1","deleteNum":0,"shopIds":"2031193954362281985","listStatus":"0","synchronizationType":"1","bookCategory":"0","way":"0","createResStr":"{\"code\":\"200\",\"data\":\"2031600837287960578\",\"msg\":\"成功\"}\n"},"userId":"1922120835237036033"}
【2026-03-11 13:19:42】【返回参数】null
【2026-03-11 13:19:42】【日志结束,方法名称:addGoods】【centerBooksAdd】中心书库发布商品发布方法addGoods
【2026-03-11 13:28:08】【日志开始,方法名称:addGoods】【centerBooksAdd】中心书库发布商品发布方法addGoods
【2026-03-11 13:28:08】【执行参数】{"taskBo":{"createDept":null,"createBy":null,"createTime":null,"updateBy":null,"updateTime":null,"id":null,"taskType":null,"shopIds":null,"shopNames":null,"fileName":null,"dataNum":null,"taskStatus":null,"status":null,"threadId":null,"relationId":null},"map":{"taskType":"1","priceAdjustments":[{"userId":"1922120835237036033","proportion":100,"addNum":0}],"data":{"total":2,"fileName":"新发布商品模板.xlsx","url":"https://book.center.file.buzhiyushu.cn/TaskExcel/44748e83-c690-412b-bc69-32c2a99236f8_新发布商品模板.xlsx"},"imageSelect":"1","deleteNum":0,"shopIds":"2031193954362281985","listStatus":"1","synchronizationType":"1","bookCategory":"0","way":"2","createResStr":"{\"code\":\"200\",\"data\":\"2031603053692411906\",\"msg\":\"成功\"}\n"},"userId":"1922120835237036033"}
【2026-03-11 13:28:58】【返回参数】null
【2026-03-11 13:28:58】【日志结束,方法名称:addGoods】【centerBooksAdd】中心书库发布商品发布方法addGoods
【2026-03-11 13:30:17】【日志开始,方法名称:addGoods】【centerBooksAdd】中心书库发布商品发布方法addGoods
【2026-03-11 13:30:17】【执行参数】{"taskBo":{"createDept":null,"createBy":null,"createTime":null,"updateBy":null,"updateTime":null,"id":null,"taskType":null,"shopIds":null,"shopNames":null,"fileName":null,"dataNum":null,"taskStatus":null,"status":null,"threadId":null,"relationId":null},"map":{"taskType":"1","priceAdjustments":[{"userId":"1922120835237036033","proportion":100,"addNum":0}],"data":{"total":2,"fileName":"新发布商品模板.xlsx","url":"https://book.center.file.buzhiyushu.cn/TaskExcel/8988633a-a17d-492e-9d84-216922f317b9_新发布商品模板.xlsx"},"imageSelect":"1","deleteNum":0,"shopIds":"2031193954362281985","listStatus":"0","synchronizationType":"1","bookCategory":"0","way":"0","createResStr":"{\"code\":\"200\",\"data\":\"2031603593130237954\",\"msg\":\"成功\"}\n"},"userId":"1922120835237036033"}
【2026-03-11 13:37:13】【返回参数】null
【2026-03-11 13:37:13】【日志结束,方法名称:addGoods】【centerBooksAdd】中心书库发布商品发布方法addGoods
【2026-03-11 13:38:31】【日志开始,方法名称:addGoods】【centerBooksAdd】中心书库发布商品发布方法addGoods
【2026-03-11 13:38:31】【执行参数】{"taskBo":{"createDept":null,"createBy":null,"createTime":null,"updateBy":null,"updateTime":null,"id":null,"taskType":null,"shopIds":null,"shopNames":null,"fileName":null,"dataNum":null,"taskStatus":null,"status":null,"threadId":null,"relationId":null},"map":{"taskType":"1","priceAdjustments":[{"userId":"1922120835237036033","proportion":100,"addNum":0}],"data":{"total":2,"fileName":"新发布商品模板.xlsx","url":"https://book.center.file.buzhiyushu.cn/TaskExcel/c306572e-806c-400b-b74c-bf10918af179_新发布商品模板.xlsx"},"imageSelect":"1","deleteNum":0,"shopIds":"2031193954362281985","listStatus":"0","synchronizationType":"1","bookCategory":"0","way":"0","createResStr":"{\"code\":\"200\",\"data\":\"2031605666571837441\",\"msg\":\"成功\"}\n"},"userId":"1922120835237036033"}
【2026-03-11 13:39:03】【返回参数】null
【2026-03-11 13:39:03】【日志结束,方法名称:addGoods】【centerBooksAdd】中心书库发布商品发布方法addGoods
【2026-03-11 13:45:07】【日志开始,方法名称:addGoods】【centerBooksAdd】中心书库发布商品发布方法addGoods
【2026-03-11 13:45:07】【执行参数】{"taskBo":{"createDept":null,"createBy":null,"createTime":null,"updateBy":null,"updateTime":null,"id":null,"taskType":null,"shopIds":null,"shopNames":null,"fileName":null,"dataNum":null,"taskStatus":null,"status":null,"threadId":null,"relationId":null},"map":{"taskType":"1","priceAdjustments":[{"userId":"1922120835237036033","proportion":100,"addNum":0}],"data":{"total":2,"fileName":"新发布商品模板.xlsx","url":"https://book.center.file.buzhiyushu.cn/TaskExcel/b0b22d40-0786-48d6-88f2-100c56de4dbc_新发布商品模板.xlsx"},"imageSelect":"1","deleteNum":0,"shopIds":"2031193954362281985","listStatus":"0","synchronizationType":"1","bookCategory":"0","way":"0","createResStr":"{\"code\":\"200\",\"data\":\"2031607327323283457\",\"msg\":\"成功\"}\n"},"userId":"1922120835237036033"}
【2026-03-11 13:47:02】【返回参数】null
【2026-03-11 13:47:02】【日志结束,方法名称:addGoods】【centerBooksAdd】中心书库发布商品发布方法addGoods
【2026-03-11 13:48:18】【日志开始,方法名称:addGoods】【centerBooksAdd】中心书库发布商品发布方法addGoods
【2026-03-11 13:48:18】【执行参数】{"taskBo":{"createDept":null,"createBy":null,"createTime":null,"updateBy":null,"updateTime":null,"id":null,"taskType":null,"shopIds":null,"shopNames":null,"fileName":null,"dataNum":null,"taskStatus":null,"status":null,"threadId":null,"relationId":null},"map":{"taskType":"1","priceAdjustments":[{"userId":"1922120835237036033","proportion":100,"addNum":0}],"data":{"total":2,"fileName":"新发布商品模板.xlsx","url":"https://book.center.file.buzhiyushu.cn/TaskExcel/b5ccee78-9fdc-43b2-befd-6024e88c1cb6_新发布商品模板.xlsx"},"imageSelect":"1","deleteNum":0,"shopIds":"2031193954362281985","listStatus":"0","synchronizationType":"1","bookCategory":"0","way":"0","createResStr":"{\"code\":\"200\",\"data\":\"2031608125490946049\",\"msg\":\"成功\"}\n"},"userId":"1922120835237036033"}
【2026-03-11 13:51:14】【返回参数】null
【2026-03-11 13:51:14】【日志结束,方法名称:addGoods】【centerBooksAdd】中心书库发布商品发布方法addGoods
【2026-03-11 13:52:48】【日志开始,方法名称:addGoods】【centerBooksAdd】中心书库发布商品发布方法addGoods
【2026-03-11 13:52:48】【执行参数】{"taskBo":{"createDept":null,"createBy":null,"createTime":null,"updateBy":null,"updateTime":null,"id":null,"taskType":null,"shopIds":null,"shopNames":null,"fileName":null,"dataNum":null,"taskStatus":null,"status":null,"threadId":null,"relationId":null},"map":{"taskType":"1","priceAdjustments":[{"userId":"1922120835237036033","proportion":100,"addNum":0}],"data":{"total":2,"fileName":"新发布商品模板.xlsx","url":"https://book.center.file.buzhiyushu.cn/TaskExcel/6567e176-543b-443f-ad1b-de074fd6ed4e_新发布商品模板.xlsx"},"imageSelect":"1","deleteNum":0,"shopIds":"2031193954362281985","listStatus":"0","synchronizationType":"1","bookCategory":"0","way":"2","createResStr":"{\"code\":\"200\",\"data\":\"2031609259815297026\",\"msg\":\"成功\"}\n"},"userId":"1922120835237036033"}
【2026-03-11 13:54:39】【返回参数】null
【2026-03-11 13:54:39】【日志结束,方法名称:addGoods】【centerBooksAdd】中心书库发布商品发布方法addGoods
【2026-03-11 13:55:54】【日志开始,方法名称:addGoods】【centerBooksAdd】中心书库发布商品发布方法addGoods
【2026-03-11 13:55:54】【执行参数】{"taskBo":{"createDept":null,"createBy":null,"createTime":null,"updateBy":null,"updateTime":null,"id":null,"taskType":null,"shopIds":null,"shopNames":null,"fileName":null,"dataNum":null,"taskStatus":null,"status":null,"threadId":null,"relationId":null},"map":{"taskType":"1","priceAdjustments":[{"userId":"1922120835237036033","proportion":100,"addNum":0}],"data":{"total":2,"fileName":"新发布商品模板.xlsx","url":"https://book.center.file.buzhiyushu.cn/TaskExcel/a13d29e6-07ed-44de-a288-89f0d141c385_新发布商品模板.xlsx"},"imageSelect":"1","deleteNum":0,"shopIds":"2031193954362281985","listStatus":"0","synchronizationType":"1","bookCategory":"0","way":"0","createResStr":"{\"code\":\"200\",\"data\":\"2031610038005489665\",\"msg\":\"成功\"}\n"},"userId":"1922120835237036033"}
【2026-03-11 13:56:08】【返回参数】null
【2026-03-11 13:56:08】【日志结束,方法名称:addGoods】【centerBooksAdd】中心书库发布商品发布方法addGoods
【2026-03-11 13:56:54】【日志开始,方法名称:addGoods】【centerBooksAdd】中心书库发布商品发布方法addGoods
【2026-03-11 13:56:54】【执行参数】{"taskBo":{"createDept":null,"createBy":null,"createTime":null,"updateBy":null,"updateTime":null,"id":null,"taskType":null,"shopIds":null,"shopNames":null,"fileName":null,"dataNum":null,"taskStatus":null,"status":null,"threadId":null,"relationId":null},"map":{"taskType":"1","priceAdjustments":[{"userId":"1922120835237036033","proportion":100,"addNum":0}],"data":{"total":2,"fileName":"新发布商品模板.xlsx","url":"https://book.center.file.buzhiyushu.cn/TaskExcel/1f88128b-844a-4338-b72a-de3752797227_新发布商品模板.xlsx"},"imageSelect":"1","deleteNum":0,"shopIds":"2031193954362281985","listStatus":"0","synchronizationType":"1","bookCategory":"0","way":"0","createResStr":"{\"code\":\"200\",\"data\":\"2031610292738154498\",\"msg\":\"成功\"}\n"},"userId":"1922120835237036033"}
【2026-03-11 14:01:07】【返回参数】null
【2026-03-11 14:01:07】【日志结束,方法名称:addGoods】【centerBooksAdd】中心书库发布商品发布方法addGoods
【2026-03-11 14:02:45】【日志开始,方法名称:addGoods】【centerBooksAdd】中心书库发布商品发布方法addGoods
【2026-03-11 14:02:45】【执行参数】{"taskBo":{"createDept":null,"createBy":null,"createTime":null,"updateBy":null,"updateTime":null,"id":null,"taskType":null,"shopIds":null,"shopNames":null,"fileName":null,"dataNum":null,"taskStatus":null,"status":null,"threadId":null,"relationId":null},"map":{"taskType":"1","priceAdjustments":[{"proportion":100,"addNum":0}],"data":{"total":2,"fileName":"新发布商品模板.xlsx","url":"https://book.center.file.buzhiyushu.cn/TaskExcel/a25752e5-d676-49f0-a886-5676fa417800_新发布商品模板.xlsx"},"imageSelect":"1","deleteNum":0,"shopIds":"2031193954362281985","listStatus":"1","synchronizationType":"1","bookCategory":"0","way":"2","createResStr":"{\"code\":\"200\",\"data\":\"2031611765333123073\",\"msg\":\"成功\"}\n"},"userId":"1922120835237036033"}
【2026-03-11 14:02:59】【返回参数】null
【2026-03-11 14:02:59】【日志结束,方法名称:addGoods】【centerBooksAdd】中心书库发布商品发布方法addGoods
【2026-03-11 14:10:55】【日志开始,方法名称:addGoods】【centerBooksAdd】中心书库发布商品发布方法addGoods
【2026-03-11 14:10:55】【执行参数】{"taskBo":{"createDept":null,"createBy":null,"createTime":null,"updateBy":null,"updateTime":null,"id":null,"taskType":null,"shopIds":null,"shopNames":null,"fileName":null,"dataNum":null,"taskStatus":null,"status":null,"threadId":null,"relationId":null},"map":{"taskType":"1","priceAdjustments":[{"userId":"1922120835237036033","proportion":100,"addNum":0}],"data":{"total":2,"fileName":"新发布商品模板.xlsx","url":"https://book.center.file.buzhiyushu.cn/TaskExcel/ca7e431b-db89-42f9-9fce-a118e24777f5_新发布商品模板.xlsx"},"imageSelect":"1","deleteNum":0,"shopIds":"2031193954362281985","listStatus":"0","synchronizationType":"1","bookCategory":"0","way":"2","createResStr":"{\"code\":\"200\",\"data\":\"2031613817295691777\",\"msg\":\"成功\"}\n"},"userId":"1922120835237036033"}
【2026-03-11 14:10:55】【返回参数】null
【2026-03-11 14:10:55】【日志结束,方法名称:addGoods】【centerBooksAdd】中心书库发布商品发布方法addGoods
【2026-03-12 18:28:20】【日志开始,方法名称:addGoods】【centerBooksAdd】中心书库发布商品发布方法addGoods
【2026-03-12 18:28:20】【执行参数】{"taskBo":{"createDept":null,"createBy":null,"createTime":null,"updateBy":null,"updateTime":null,"id":null,"taskType":null,"shopIds":null,"shopNames":null,"fileName":null,"dataNum":null,"taskStatus":null,"status":null,"threadId":null,"relationId":null},"map":{"taskType":"1","priceAdjustments":[{"userId":"1922120835237036033","proportion":100,"addNum":0}],"data":{"total":2,"fileName":"新发布商品模板.xlsx","url":"https://book.center.file.buzhiyushu.cn/TaskExcel/0078d6dc-0376-4923-870a-30dc1895cee6_新发布商品模板.xlsx"},"imageSelect":"1","deleteNum":0,"shopIds":"2031193954362281985","listStatus":"1","synchronizationType":"1","bookCategory":"0","way":"0","createResStr":"{\"code\":\"200\",\"data\":\"2032040987478331394\",\"msg\":\"成功\"}\n"},"userId":"1922120835237036033"}
【2026-03-12 18:28:23】【系统异常】【异常方法】addGoods
【2026-03-12 18:28:23】【系统异常】【异常内容】org.springframework.web.client.ResourceAccessException: I/O error on POST request for "http://36.212.20.113:8182/api/goods/simple": Connection refused: no further information
at org.springframework.web.client.RestTemplate.createResourceAccessException(RestTemplate.java:926)
at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:906)
at org.springframework.web.client.RestTemplate.execute(RestTemplate.java:801)
at org.springframework.web.client.RestTemplate.postForEntity(RestTemplate.java:549)
at org.dromara.zhishu.util.InterfaceUtils.postForm(InterfaceUtils.java:249)
at org.dromara.zhishu.service.impl.TaskServiceImpl.addGoods(TaskServiceImpl.java:257)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:568)
at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:359)
at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:196)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163)
at org.springframework.aop.aspectj.MethodInvocationProceedingJoinPoint.proceed(MethodInvocationProceedingJoinPoint.java:89)
at org.dromara.zhishu.util.RunningLogUtils.runningLog(RunningLogUtils.java:42)
at org.dromara.zhishu.aspect.CenterAspect.addGoods(CenterAspect.java:88)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:568)
at org.springframework.aop.aspectj.AbstractAspectJAdvice.invokeAdviceMethodWithGivenArgs(AbstractAspectJAdvice.java:638)
at org.springframework.aop.aspectj.AbstractAspectJAdvice.invokeAdviceMethod(AbstractAspectJAdvice.java:628)
at org.springframework.aop.aspectj.AspectJAroundAdvice.invoke(AspectJAroundAdvice.java:71)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:173)
at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:97)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184)
at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:727)
at org.dromara.zhishu.service.impl.TaskServiceImpl$$SpringCGLIB$$0.addGoods(<generated>)
at org.dromara.zhishu.util.TaskRunnable.run(TaskRunnable.java:39)
at java.base/java.lang.Thread.run(Thread.java:842)
Caused by: java.net.ConnectException: Connection refused: no further information
at java.base/sun.nio.ch.Net.pollConnect(Native Method)
at java.base/sun.nio.ch.Net.pollConnectNow(Net.java:672)
at java.base/sun.nio.ch.NioSocketImpl.timedFinishConnect(NioSocketImpl.java:554)
at java.base/sun.nio.ch.NioSocketImpl.connect(NioSocketImpl.java:602)
at java.base/java.net.Socket.connect(Socket.java:633)
at java.base/sun.net.NetworkClient.doConnect(NetworkClient.java:178)
at java.base/sun.net.www.http.HttpClient.openServer(HttpClient.java:534)
at java.base/sun.net.www.http.HttpClient.openServer(HttpClient.java:639)
at java.base/sun.net.www.http.HttpClient.<init>(HttpClient.java:282)
at java.base/sun.net.www.http.HttpClient.New(HttpClient.java:387)
at java.base/sun.net.www.http.HttpClient.New(HttpClient.java:409)
at java.base/sun.net.www.protocol.http.HttpURLConnection.getNewHttpClient(HttpURLConnection.java:1308)
at java.base/sun.net.www.protocol.http.HttpURLConnection.plainConnect0(HttpURLConnection.java:1241)
at java.base/sun.net.www.protocol.http.HttpURLConnection.plainConnect(HttpURLConnection.java:1127)
at java.base/sun.net.www.protocol.http.HttpURLConnection.connect(HttpURLConnection.java:1056)
at org.springframework.http.client.SimpleClientHttpRequest.executeInternal(SimpleClientHttpRequest.java:79)
at org.springframework.http.client.AbstractStreamingClientHttpRequest.executeInternal(AbstractStreamingClientHttpRequest.java:71)
at org.springframework.http.client.AbstractClientHttpRequest.execute(AbstractClientHttpRequest.java:81)
at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:900)
... 28 more
【2026-03-12 18:28:23】【日志结束,方法名称:addGoods】【centerBooksAdd】中心书库发布商品发布方法addGoods
【2026-03-12 18:31:42】【日志开始,方法名称:addGoods】【centerBooksAdd】中心书库发布商品发布方法addGoods
【2026-03-12 18:31:42】【执行参数】{"taskBo":{"createDept":null,"createBy":null,"createTime":null,"updateBy":null,"updateTime":null,"id":null,"taskType":null,"shopIds":null,"shopNames":null,"fileName":null,"dataNum":null,"taskStatus":null,"status":null,"threadId":null,"relationId":null},"map":{"taskType":"1","priceAdjustments":[{"userId":"1922120835237036033","proportion":100,"addNum":0}],"data":{"total":2,"fileName":"新发布商品模板.xlsx","url":"https://book.center.file.buzhiyushu.cn/TaskExcel/deb13d5a-0421-422f-944d-744da141a4e7_新发布商品模板.xlsx"},"imageSelect":"1","deleteNum":0,"shopIds":"2031193954362281985","listStatus":"1","synchronizationType":"1","bookCategory":"0","way":"0","createResStr":"{\"code\":\"200\",\"data\":\"2032041836002807810\",\"msg\":\"成功\"}\n"},"userId":"1922120835237036033"}
【2026-03-12 18:31:45】【系统异常】【异常方法】addGoods
【2026-03-12 18:31:45】【系统异常】【异常内容】org.springframework.web.client.ResourceAccessException: I/O error on POST request for "http://36.212.20.113:8182/api/goods/simple": Connection refused: no further information
at org.springframework.web.client.RestTemplate.createResourceAccessException(RestTemplate.java:926)
at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:906)
at org.springframework.web.client.RestTemplate.execute(RestTemplate.java:801)
at org.springframework.web.client.RestTemplate.postForEntity(RestTemplate.java:549)
at org.dromara.zhishu.util.InterfaceUtils.postForm(InterfaceUtils.java:249)
at org.dromara.zhishu.service.impl.TaskServiceImpl.addGoods(TaskServiceImpl.java:257)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:568)
at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:359)
at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:196)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163)
at org.springframework.aop.aspectj.MethodInvocationProceedingJoinPoint.proceed(MethodInvocationProceedingJoinPoint.java:89)
at org.dromara.zhishu.util.RunningLogUtils.runningLog(RunningLogUtils.java:42)
at org.dromara.zhishu.aspect.CenterAspect.addGoods(CenterAspect.java:88)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:568)
at org.springframework.aop.aspectj.AbstractAspectJAdvice.invokeAdviceMethodWithGivenArgs(AbstractAspectJAdvice.java:638)
at org.springframework.aop.aspectj.AbstractAspectJAdvice.invokeAdviceMethod(AbstractAspectJAdvice.java:628)
at org.springframework.aop.aspectj.AspectJAroundAdvice.invoke(AspectJAroundAdvice.java:71)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:173)
at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:97)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184)
at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:727)
at org.dromara.zhishu.service.impl.TaskServiceImpl$$SpringCGLIB$$0.addGoods(<generated>)
at org.dromara.zhishu.util.TaskRunnable.run(TaskRunnable.java:39)
at java.base/java.lang.Thread.run(Thread.java:842)
Caused by: java.net.ConnectException: Connection refused: no further information
at java.base/sun.nio.ch.Net.pollConnect(Native Method)
at java.base/sun.nio.ch.Net.pollConnectNow(Net.java:672)
at java.base/sun.nio.ch.NioSocketImpl.timedFinishConnect(NioSocketImpl.java:554)
at java.base/sun.nio.ch.NioSocketImpl.connect(NioSocketImpl.java:602)
at java.base/java.net.Socket.connect(Socket.java:633)
at java.base/sun.net.NetworkClient.doConnect(NetworkClient.java:178)
at java.base/sun.net.www.http.HttpClient.openServer(HttpClient.java:534)
at java.base/sun.net.www.http.HttpClient.openServer(HttpClient.java:639)
at java.base/sun.net.www.http.HttpClient.<init>(HttpClient.java:282)
at java.base/sun.net.www.http.HttpClient.New(HttpClient.java:387)
at java.base/sun.net.www.http.HttpClient.New(HttpClient.java:409)
at java.base/sun.net.www.protocol.http.HttpURLConnection.getNewHttpClient(HttpURLConnection.java:1308)
at java.base/sun.net.www.protocol.http.HttpURLConnection.plainConnect0(HttpURLConnection.java:1241)
at java.base/sun.net.www.protocol.http.HttpURLConnection.plainConnect(HttpURLConnection.java:1127)
at java.base/sun.net.www.protocol.http.HttpURLConnection.connect(HttpURLConnection.java:1056)
at org.springframework.http.client.SimpleClientHttpRequest.executeInternal(SimpleClientHttpRequest.java:79)
at org.springframework.http.client.AbstractStreamingClientHttpRequest.executeInternal(AbstractStreamingClientHttpRequest.java:71)
at org.springframework.http.client.AbstractClientHttpRequest.execute(AbstractClientHttpRequest.java:81)
at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:900)
... 28 more
【2026-03-12 18:31:45】【日志结束,方法名称:addGoods】【centerBooksAdd】中心书库发布商品发布方法addGoods
【2026-03-12 18:33:04】【日志开始,方法名称:addGoods】【centerBooksAdd】中心书库发布商品发布方法addGoods
【2026-03-12 18:33:04】【执行参数】{"taskBo":{"createDept":null,"createBy":null,"createTime":null,"updateBy":null,"updateTime":null,"id":null,"taskType":null,"shopIds":null,"shopNames":null,"fileName":null,"dataNum":null,"taskStatus":null,"status":null,"threadId":null,"relationId":null},"map":{"taskType":"1","priceAdjustments":[{"userId":"1922120835237036033","proportion":100,"addNum":0}],"data":{"total":2,"fileName":"新发布商品模板.xlsx","url":"https://book.center.file.buzhiyushu.cn/TaskExcel/d50eaeb5-592d-4412-8230-902ba2adfa5b_新发布商品模板.xlsx"},"imageSelect":"1","deleteNum":0,"shopIds":"2031193954362281985","listStatus":"1","synchronizationType":"1","bookCategory":"0","way":"0","createResStr":"{\"code\":\"200\",\"data\":\"2032042179696660482\",\"msg\":\"成功\"}\n"},"userId":"1922120835237036033"}
【2026-03-12 18:33:07】【系统异常】【异常方法】addGoods
【2026-03-12 18:33:07】【系统异常】【异常内容】org.springframework.web.client.ResourceAccessException: I/O error on POST request for "http://36.212.20.113:8182/api/goods/simple": Connection refused: no further information
at org.springframework.web.client.RestTemplate.createResourceAccessException(RestTemplate.java:926)
at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:906)
at org.springframework.web.client.RestTemplate.execute(RestTemplate.java:801)
at org.springframework.web.client.RestTemplate.postForEntity(RestTemplate.java:549)
at org.dromara.zhishu.util.InterfaceUtils.postForm(InterfaceUtils.java:249)
at org.dromara.zhishu.service.impl.TaskServiceImpl.addGoods(TaskServiceImpl.java:257)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:568)
at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:359)
at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:196)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163)
at org.springframework.aop.aspectj.MethodInvocationProceedingJoinPoint.proceed(MethodInvocationProceedingJoinPoint.java:89)
at org.dromara.zhishu.util.RunningLogUtils.runningLog(RunningLogUtils.java:42)
at org.dromara.zhishu.aspect.CenterAspect.addGoods(CenterAspect.java:88)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:568)
at org.springframework.aop.aspectj.AbstractAspectJAdvice.invokeAdviceMethodWithGivenArgs(AbstractAspectJAdvice.java:638)
at org.springframework.aop.aspectj.AbstractAspectJAdvice.invokeAdviceMethod(AbstractAspectJAdvice.java:628)
at org.springframework.aop.aspectj.AspectJAroundAdvice.invoke(AspectJAroundAdvice.java:71)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:173)
at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:97)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184)
at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:727)
at org.dromara.zhishu.service.impl.TaskServiceImpl$$SpringCGLIB$$0.addGoods(<generated>)
at org.dromara.zhishu.util.TaskRunnable.run(TaskRunnable.java:39)
at java.base/java.lang.Thread.run(Thread.java:842)
Caused by: java.net.ConnectException: Connection refused: no further information
at java.base/sun.nio.ch.Net.pollConnect(Native Method)
at java.base/sun.nio.ch.Net.pollConnectNow(Net.java:672)
at java.base/sun.nio.ch.NioSocketImpl.timedFinishConnect(NioSocketImpl.java:554)
at java.base/sun.nio.ch.NioSocketImpl.connect(NioSocketImpl.java:602)
at java.base/java.net.Socket.connect(Socket.java:633)
at java.base/sun.net.NetworkClient.doConnect(NetworkClient.java:178)
at java.base/sun.net.www.http.HttpClient.openServer(HttpClient.java:534)
at java.base/sun.net.www.http.HttpClient.openServer(HttpClient.java:639)
at java.base/sun.net.www.http.HttpClient.<init>(HttpClient.java:282)
at java.base/sun.net.www.http.HttpClient.New(HttpClient.java:387)
at java.base/sun.net.www.http.HttpClient.New(HttpClient.java:409)
at java.base/sun.net.www.protocol.http.HttpURLConnection.getNewHttpClient(HttpURLConnection.java:1308)
at java.base/sun.net.www.protocol.http.HttpURLConnection.plainConnect0(HttpURLConnection.java:1241)
at java.base/sun.net.www.protocol.http.HttpURLConnection.plainConnect(HttpURLConnection.java:1127)
at java.base/sun.net.www.protocol.http.HttpURLConnection.connect(HttpURLConnection.java:1056)
at org.springframework.http.client.SimpleClientHttpRequest.executeInternal(SimpleClientHttpRequest.java:79)
at org.springframework.http.client.AbstractStreamingClientHttpRequest.executeInternal(AbstractStreamingClientHttpRequest.java:71)
at org.springframework.http.client.AbstractClientHttpRequest.execute(AbstractClientHttpRequest.java:81)
at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:900)
... 28 more
【2026-03-12 18:33:07】【日志结束,方法名称:addGoods】【centerBooksAdd】中心书库发布商品发布方法addGoods
【2026-03-12 18:35:30】【日志开始,方法名称:addGoods】【centerBooksAdd】中心书库发布商品发布方法addGoods
【2026-03-12 18:35:30】【执行参数】{"taskBo":{"createDept":null,"createBy":null,"createTime":null,"updateBy":null,"updateTime":null,"id":null,"taskType":null,"shopIds":null,"shopNames":null,"fileName":null,"dataNum":null,"taskStatus":null,"status":null,"threadId":null,"relationId":null},"map":{"taskType":"1","priceAdjustments":[{"userId":"1922120835237036033","proportion":100,"addNum":0}],"data":{"total":99,"fileName":"测试.xlsx","url":"https://book.center.file.buzhiyushu.cn/TaskExcel/96a41f23-a8e2-4c4d-9348-4eea95a1217f_测试.xlsx"},"imageSelect":"1","deleteNum":0,"shopIds":"2031193954362281985","listStatus":"1","synchronizationType":"1","bookCategory":"0","way":"0","createResStr":"{\"code\":\"200\",\"data\":\"2032042728559087617\",\"msg\":\"成功\"}\n"},"userId":"1922120835237036033"}
【2026-03-12 18:35:42】【系统异常】【异常方法】addGoods
【2026-03-12 18:35:42】【系统异常】【异常内容】org.springframework.web.client.ResourceAccessException: I/O error on POST request for "http://36.212.20.113:8182/api/goods/simple": Connection refused: no further information
at org.springframework.web.client.RestTemplate.createResourceAccessException(RestTemplate.java:926)
at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:906)
at org.springframework.web.client.RestTemplate.execute(RestTemplate.java:801)
at org.springframework.web.client.RestTemplate.postForEntity(RestTemplate.java:549)
at org.dromara.zhishu.util.InterfaceUtils.postForm(InterfaceUtils.java:249)
at org.dromara.zhishu.service.impl.TaskServiceImpl.addGoods(TaskServiceImpl.java:257)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:568)
at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:359)
at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:196)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163)
at org.springframework.aop.aspectj.MethodInvocationProceedingJoinPoint.proceed(MethodInvocationProceedingJoinPoint.java:89)
at org.dromara.zhishu.util.RunningLogUtils.runningLog(RunningLogUtils.java:42)
at org.dromara.zhishu.aspect.CenterAspect.addGoods(CenterAspect.java:88)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:568)
at org.springframework.aop.aspectj.AbstractAspectJAdvice.invokeAdviceMethodWithGivenArgs(AbstractAspectJAdvice.java:638)
at org.springframework.aop.aspectj.AbstractAspectJAdvice.invokeAdviceMethod(AbstractAspectJAdvice.java:628)
at org.springframework.aop.aspectj.AspectJAroundAdvice.invoke(AspectJAroundAdvice.java:71)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:173)
at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:97)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184)
at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:727)
at org.dromara.zhishu.service.impl.TaskServiceImpl$$SpringCGLIB$$0.addGoods(<generated>)
at org.dromara.zhishu.util.TaskRunnable.run(TaskRunnable.java:39)
at java.base/java.lang.Thread.run(Thread.java:842)
Caused by: java.net.ConnectException: Connection refused: no further information
at java.base/sun.nio.ch.Net.pollConnect(Native Method)
at java.base/sun.nio.ch.Net.pollConnectNow(Net.java:672)
at java.base/sun.nio.ch.NioSocketImpl.timedFinishConnect(NioSocketImpl.java:554)
at java.base/sun.nio.ch.NioSocketImpl.connect(NioSocketImpl.java:602)
at java.base/java.net.Socket.connect(Socket.java:633)
at java.base/sun.net.NetworkClient.doConnect(NetworkClient.java:178)
at java.base/sun.net.www.http.HttpClient.openServer(HttpClient.java:534)
at java.base/sun.net.www.http.HttpClient.openServer(HttpClient.java:639)
at java.base/sun.net.www.http.HttpClient.<init>(HttpClient.java:282)
at java.base/sun.net.www.http.HttpClient.New(HttpClient.java:387)
at java.base/sun.net.www.http.HttpClient.New(HttpClient.java:409)
at java.base/sun.net.www.protocol.http.HttpURLConnection.getNewHttpClient(HttpURLConnection.java:1308)
at java.base/sun.net.www.protocol.http.HttpURLConnection.plainConnect0(HttpURLConnection.java:1241)
at java.base/sun.net.www.protocol.http.HttpURLConnection.plainConnect(HttpURLConnection.java:1127)
at java.base/sun.net.www.protocol.http.HttpURLConnection.connect(HttpURLConnection.java:1056)
at org.springframework.http.client.SimpleClientHttpRequest.executeInternal(SimpleClientHttpRequest.java:79)
at org.springframework.http.client.AbstractStreamingClientHttpRequest.executeInternal(AbstractStreamingClientHttpRequest.java:71)
at org.springframework.http.client.AbstractClientHttpRequest.execute(AbstractClientHttpRequest.java:81)
at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:900)
... 28 more
【2026-03-12 18:35:42】【日志结束,方法名称:addGoods】【centerBooksAdd】中心书库发布商品发布方法addGoods
【2026-03-12 18:41:23】【日志开始,方法名称:addGoods】【centerBooksAdd】中心书库发布商品发布方法addGoods
【2026-03-12 18:41:23】【执行参数】{"taskBo":{"createDept":null,"createBy":null,"createTime":null,"updateBy":null,"updateTime":null,"id":null,"taskType":null,"shopIds":null,"shopNames":null,"fileName":null,"dataNum":null,"taskStatus":null,"status":null,"threadId":null,"relationId":null},"map":{"taskType":"1","priceAdjustments":[{"userId":"1922120835237036033","proportion":100,"addNum":0}],"data":{"total":99,"fileName":"测试.xlsx","url":"https://book.center.file.buzhiyushu.cn/TaskExcel/71b0a7dd-63b4-4d02-b8e9-18ff5bb565b5_测试.xlsx"},"imageSelect":"1","deleteNum":0,"shopIds":"2031193954362281985","listStatus":"1","synchronizationType":"1","bookCategory":"0","way":"0","createResStr":"{\"code\":\"200\",\"data\":\"2032043516362625025\",\"msg\":\"成功\"}\n"},"userId":"1922120835237036033"}
【2026-03-12 18:43:28】【系统异常】【异常方法】addGoods
【2026-03-12 18:43:28】【系统异常】【异常内容】org.springframework.web.client.ResourceAccessException: I/O error on POST request for "http://36.212.20.113:8182/api/goods/simple": Connection refused: no further information
at org.springframework.web.client.RestTemplate.createResourceAccessException(RestTemplate.java:926)
at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:906)
at org.springframework.web.client.RestTemplate.execute(RestTemplate.java:801)
at org.springframework.web.client.RestTemplate.postForEntity(RestTemplate.java:549)
at org.dromara.zhishu.util.InterfaceUtils.postForm(InterfaceUtils.java:249)
at org.dromara.zhishu.service.impl.TaskServiceImpl.addGoods(TaskServiceImpl.java:257)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:568)
at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:359)
at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:196)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163)
at org.springframework.aop.aspectj.MethodInvocationProceedingJoinPoint.proceed(MethodInvocationProceedingJoinPoint.java:89)
at org.dromara.zhishu.util.RunningLogUtils.runningLog(RunningLogUtils.java:42)
at org.dromara.zhishu.aspect.CenterAspect.addGoods(CenterAspect.java:88)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:568)
at org.springframework.aop.aspectj.AbstractAspectJAdvice.invokeAdviceMethodWithGivenArgs(AbstractAspectJAdvice.java:638)
at org.springframework.aop.aspectj.AbstractAspectJAdvice.invokeAdviceMethod(AbstractAspectJAdvice.java:628)
at org.springframework.aop.aspectj.AspectJAroundAdvice.invoke(AspectJAroundAdvice.java:71)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:173)
at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:97)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184)
at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:727)
at org.dromara.zhishu.service.impl.TaskServiceImpl$$SpringCGLIB$$0.addGoods(<generated>)
at org.dromara.zhishu.util.TaskRunnable.run(TaskRunnable.java:39)
at java.base/java.lang.Thread.run(Thread.java:842)
Caused by: java.net.ConnectException: Connection refused: no further information
at java.base/sun.nio.ch.Net.pollConnect(Native Method)
at java.base/sun.nio.ch.Net.pollConnectNow(Net.java:672)
at java.base/sun.nio.ch.NioSocketImpl.timedFinishConnect(NioSocketImpl.java:554)
at java.base/sun.nio.ch.NioSocketImpl.connect(NioSocketImpl.java:602)
at java.base/java.net.Socket.connect(Socket.java:633)
at java.base/sun.net.NetworkClient.doConnect(NetworkClient.java:178)
at java.base/sun.net.www.http.HttpClient.openServer(HttpClient.java:534)
at java.base/sun.net.www.http.HttpClient.openServer(HttpClient.java:639)
at java.base/sun.net.www.http.HttpClient.<init>(HttpClient.java:282)
at java.base/sun.net.www.http.HttpClient.New(HttpClient.java:387)
at java.base/sun.net.www.http.HttpClient.New(HttpClient.java:409)
at java.base/sun.net.www.protocol.http.HttpURLConnection.getNewHttpClient(HttpURLConnection.java:1308)
at java.base/sun.net.www.protocol.http.HttpURLConnection.plainConnect0(HttpURLConnection.java:1241)
at java.base/sun.net.www.protocol.http.HttpURLConnection.plainConnect(HttpURLConnection.java:1127)
at java.base/sun.net.www.protocol.http.HttpURLConnection.connect(HttpURLConnection.java:1056)
at org.springframework.http.client.SimpleClientHttpRequest.executeInternal(SimpleClientHttpRequest.java:79)
at org.springframework.http.client.AbstractStreamingClientHttpRequest.executeInternal(AbstractStreamingClientHttpRequest.java:71)
at org.springframework.http.client.AbstractClientHttpRequest.execute(AbstractClientHttpRequest.java:81)
at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:900)
... 28 more
【2026-03-12 18:43:28】【日志结束,方法名称:addGoods】【centerBooksAdd】中心书库发布商品发布方法addGoods
【2026-03-12 18:45:36】【日志开始,方法名称:addGoods】【centerBooksAdd】中心书库发布商品发布方法addGoods
【2026-03-12 18:45:36】【执行参数】{"taskBo":{"createDept":null,"createBy":null,"createTime":null,"updateBy":null,"updateTime":null,"id":null,"taskType":null,"shopIds":null,"shopNames":null,"fileName":null,"dataNum":null,"taskStatus":null,"status":null,"threadId":null,"relationId":null},"map":{"taskType":"1","priceAdjustments":[{"userId":"1922120835237036033","proportion":100,"addNum":0}],"data":{"total":2,"fileName":"新发布商品模板.xlsx","url":"https://book.center.file.buzhiyushu.cn/TaskExcel/45f786a2-c38a-4683-b531-f0efd0fb3f5d_新发布商品模板.xlsx"},"imageSelect":"1","deleteNum":0,"shopIds":"2031193954362281985","listStatus":"1","synchronizationType":"1","bookCategory":"0","way":"0","createResStr":"{\"code\":\"200\",\"data\":\"2032045336577314818\",\"msg\":\"成功\"}\n"},"userId":"1922120835237036033"}
【2026-03-12 18:54:07】【系统异常】【异常方法】addGoods
【2026-03-12 18:54:07】【系统异常】【异常内容】org.springframework.web.client.ResourceAccessException: I/O error on POST request for "http://36.212.20.113:8182/api/goods/simple": Connection refused: no further information
at org.springframework.web.client.RestTemplate.createResourceAccessException(RestTemplate.java:926)
at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:906)
at org.springframework.web.client.RestTemplate.execute(RestTemplate.java:801)
at org.springframework.web.client.RestTemplate.postForEntity(RestTemplate.java:549)
at org.dromara.zhishu.util.InterfaceUtils.postForm(InterfaceUtils.java:249)
at org.dromara.zhishu.service.impl.TaskServiceImpl.addGoods(TaskServiceImpl.java:257)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:568)
at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:359)
at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:196)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163)
at org.springframework.aop.aspectj.MethodInvocationProceedingJoinPoint.proceed(MethodInvocationProceedingJoinPoint.java:89)
at org.dromara.zhishu.util.RunningLogUtils.runningLog(RunningLogUtils.java:42)
at org.dromara.zhishu.aspect.CenterAspect.addGoods(CenterAspect.java:88)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:568)
at org.springframework.aop.aspectj.AbstractAspectJAdvice.invokeAdviceMethodWithGivenArgs(AbstractAspectJAdvice.java:638)
at org.springframework.aop.aspectj.AbstractAspectJAdvice.invokeAdviceMethod(AbstractAspectJAdvice.java:628)
at org.springframework.aop.aspectj.AspectJAroundAdvice.invoke(AspectJAroundAdvice.java:71)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:173)
at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:97)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184)
at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:727)
at org.dromara.zhishu.service.impl.TaskServiceImpl$$SpringCGLIB$$0.addGoods(<generated>)
at org.dromara.zhishu.util.TaskRunnable.run(TaskRunnable.java:39)
at java.base/java.lang.Thread.run(Thread.java:842)
Caused by: java.net.ConnectException: Connection refused: no further information
at java.base/sun.nio.ch.Net.pollConnect(Native Method)
at java.base/sun.nio.ch.Net.pollConnectNow(Net.java:672)
at java.base/sun.nio.ch.NioSocketImpl.timedFinishConnect(NioSocketImpl.java:554)
at java.base/sun.nio.ch.NioSocketImpl.connect(NioSocketImpl.java:602)
at java.base/java.net.Socket.connect(Socket.java:633)
at java.base/sun.net.NetworkClient.doConnect(NetworkClient.java:178)
at java.base/sun.net.www.http.HttpClient.openServer(HttpClient.java:534)
at java.base/sun.net.www.http.HttpClient.openServer(HttpClient.java:639)
at java.base/sun.net.www.http.HttpClient.<init>(HttpClient.java:282)
at java.base/sun.net.www.http.HttpClient.New(HttpClient.java:387)
at java.base/sun.net.www.http.HttpClient.New(HttpClient.java:409)
at java.base/sun.net.www.protocol.http.HttpURLConnection.getNewHttpClient(HttpURLConnection.java:1308)
at java.base/sun.net.www.protocol.http.HttpURLConnection.plainConnect0(HttpURLConnection.java:1241)
at java.base/sun.net.www.protocol.http.HttpURLConnection.plainConnect(HttpURLConnection.java:1127)
at java.base/sun.net.www.protocol.http.HttpURLConnection.connect(HttpURLConnection.java:1056)
at org.springframework.http.client.SimpleClientHttpRequest.executeInternal(SimpleClientHttpRequest.java:79)
at org.springframework.http.client.AbstractStreamingClientHttpRequest.executeInternal(AbstractStreamingClientHttpRequest.java:71)
at org.springframework.http.client.AbstractClientHttpRequest.execute(AbstractClientHttpRequest.java:81)
at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:900)
... 28 more
【2026-03-12 18:54:07】【日志结束,方法名称:addGoods】【centerBooksAdd】中心书库发布商品发布方法addGoods
【2026-03-12 18:54:32】【日志开始,方法名称:addGoods】【centerBooksAdd】中心书库发布商品发布方法addGoods
【2026-03-12 18:54:32】【执行参数】{"taskBo":{"createDept":null,"createBy":null,"createTime":null,"updateBy":null,"updateTime":null,"id":null,"taskType":null,"shopIds":null,"shopNames":null,"fileName":null,"dataNum":null,"taskStatus":null,"status":null,"threadId":null,"relationId":null},"map":{"taskType":"1","priceAdjustments":[{"userId":"1922120835237036033","proportion":100,"addNum":0}],"data":{"total":2,"fileName":"新发布商品模板.xlsx","url":"https://book.center.file.buzhiyushu.cn/TaskExcel/d9d2150a-fd73-437d-8e0f-3fa7575c8c1c_新发布商品模板.xlsx"},"imageSelect":"1","deleteNum":0,"shopIds":"2031193954362281985","listStatus":"1","synchronizationType":"1","bookCategory":"0","way":"0","createResStr":"{\"code\":\"200\",\"data\":\"2032047582354477058\",\"msg\":\"成功\"}\n"},"userId":"1922120835237036033"}
【2026-03-12 18:54:49】【返回参数】null
【2026-03-12 18:54:49】【日志结束,方法名称:addGoods】【centerBooksAdd】中心书库发布商品发布方法addGoods
【2026-04-09 10:06:42】【日志开始,方法名称:addGoods】【centerBooksAdd】中心书库发布商品发布方法addGoods
【2026-04-09 10:06:42】【执行参数】{"taskBo":{"createDept":null,"createBy":null,"createTime":null,"updateBy":null,"updateTime":null,"id":null,"taskType":null,"shopIds":null,"shopNames":null,"fileName":null,"dataNum":null,"taskStatus":null,"status":null,"threadId":null,"relationId":null},"map":{"taskType":"1","priceAdjustments":[{"userId":"1922120835237036033","proportion":100,"addNum":0}],"data":{"total":2,"fileName":"新发布商品模板.xlsx","url":"https://book.center.file.buzhiyushu.cn/TaskExcel/be0b550a-666f-4226-be61-2a7301a586b3_新发布商品模板.xlsx"},"imageSelect":"1","deleteNum":0,"shopIds":"2037060396836171778","listStatus":"0","synchronizationType":"1","bookCategory":"0","way":"2","createResStr":"{\"code\":\"200\",\"data\":\"2042061610371973122\",\"msg\":\"成功\"}\n"},"userId":"1922120835237036033"}
【2026-04-09 10:06:56】【返回参数】null
【2026-04-09 10:06:56】【日志结束,方法名称:addGoods】【centerBooksAdd】中心书库发布商品发布方法addGoods

View File

@ -0,0 +1,26 @@
package org.dromara;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.domain.EntityScan;
import org.springframework.boot.context.metrics.buffering.BufferingApplicationStartup;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
/**
* 启动程序
*
* @author Lion Li
*/
@SpringBootApplication
@EntityScan("org.dromara.zhishu.domain.bo") // 添加这行
@EnableJpaRepositories("org.dromara.zhishu.util") // 确保扫描到Repository
public class DromaraApplication {
public static void main(String[] args) {
SpringApplication application = new SpringApplication(DromaraApplication.class);
application.setApplicationStartup(new BufferingApplicationStartup(2048));
application.run(args);
System.out.println("(♥◠‿◠)ノ゙ RuoYi-Vue-Plus启动成功 ლ(´ڡ`ლ)゙");
}
}

View File

@ -0,0 +1,32 @@
package org.dromara.web.controller;
import cn.dev33.satoken.annotation.SaIgnore;
import org.dromara.common.core.config.RuoYiConfig;
import org.dromara.common.core.utils.StringUtils;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* 首页
*
* @author Lion Li
*/
@SaIgnore
@RequiredArgsConstructor
@RestController
public class IndexController {
/**
* 系统基础配置
*/
private final RuoYiConfig ruoyiConfig;
/**
* 访问首页提示语
*/
@GetMapping("/")
public String index() {
return StringUtils.format("欢迎使用{}后台管理框架当前版本v{},请通过前端地址访问。", ruoyiConfig.getName(), ruoyiConfig.getVersion());
}
}

View File

@ -0,0 +1,25 @@
package org.dromara.web.domain.vo;
import lombok.Data;
/**
* 验证码信息
*
* @author Michelle.Chung
*/
@Data
public class CaptchaVo {
/**
* 是否开启验证码
*/
private Boolean captchaEnabled = true;
private String uuid;
/**
* 验证码图片
*/
private String img;
}

View File

@ -0,0 +1,58 @@
package org.dromara.web.domain.vo;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
/**
* 登录验证信息
*
* @author Michelle.Chung
*/
@Data
public class LoginVo {
/**
* 授权令牌
*/
@JsonProperty("access_token")
private String accessToken;
/**
* 刷新令牌
*/
@JsonProperty("refresh_token")
private String refreshToken;
/**
* 授权令牌 access_token 的有效期
*/
@JsonProperty("expire_in")
private Long expireIn;
/**
* 刷新令牌 refresh_token 的有效期
*/
@JsonProperty("refresh_expire_in")
private Long refreshExpireIn;
/**
* 应用id
*/
@JsonProperty("client_id")
private String clientId;
/**
* 令牌权限
*/
private String scope;
/**
* 用户 openid
*/
private String openid;
private String phoneNumber;
private String nickName;
private Long userId;
private String userName;
}

View File

@ -0,0 +1,11 @@
package org.dromara.web.domain.vo.dto;
import lombok.Data;
import java.io.Serializable;
@Data
public class UserLoginDTO implements Serializable {
private String code;
}

View File

@ -0,0 +1,376 @@
package org.dromara.web.service;
import cn.dev33.satoken.secure.BCrypt;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.pdd.pop.sdk.common.util.JsonUtil;
import lombok.RequiredArgsConstructor;
import org.dromara.common.core.constant.Constants;
import org.dromara.common.core.constant.GlobalConstants;
import org.dromara.common.core.domain.model.RegisterBody;
import org.dromara.common.core.enums.UserType;
import org.dromara.common.core.exception.user.CaptchaException;
import org.dromara.common.core.exception.user.CaptchaExpireException;
import org.dromara.common.core.exception.user.UserException;
import org.dromara.common.core.utils.DateUtils;
import org.dromara.common.core.utils.MessageUtils;
import org.dromara.common.core.utils.ServletUtils;
import org.dromara.common.core.utils.SpringUtils;
import org.dromara.common.core.utils.StringUtils;
import org.dromara.common.log.event.LogininforEvent;
import org.dromara.common.redis.utils.RedisUtils;
import org.dromara.common.tenant.helper.TenantHelper;
import org.dromara.common.web.config.properties.CaptchaProperties;
import org.dromara.system.domain.SysRole;
import org.dromara.system.domain.SysUser;
import org.dromara.system.domain.SysUserRole;
import org.dromara.system.domain.bo.SysUserBo;
import org.dromara.system.mapper.SysRoleMapper;
import org.dromara.system.mapper.SysUserMapper;
import org.dromara.system.mapper.SysUserRoleMapper;
import org.dromara.system.service.ISysConfigService;
import org.dromara.system.service.ISysUserService;
import org.dromara.web.domain.vo.dto.WxRegisterDTO;
import org.dromara.zhishu.domain.TInviteCode;
import org.dromara.zhishu.domain.TUserPlatform;
import org.dromara.zhishu.domain.bo.ShopBo;
import org.dromara.zhishu.domain.bo.ShopDetailBo;
import org.dromara.zhishu.domain.bo.SpecBo;
import org.dromara.zhishu.domain.vo.MessageSubscribeVo;
import org.dromara.zhishu.mapper.TUserPlatformMapper;
import org.dromara.zhishu.service.*;
import org.dromara.zhishu.util.InterfaceUtils;
import org.dromara.zhishu.util.MapUtils;
import org.dromara.zhishu.util.UrlUtil;
import org.springframework.stereotype.Service;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import static org.dromara.zhishu.util.InterfaceUtils.getInterface;
/**
* 注册校验方法
*
* @author Lion Li
*/
@RequiredArgsConstructor
@Service
public class SysRegisterService {
private final ISysUserService userService;
private final SysUserMapper userMapper;
private final CaptchaProperties captchaProperties;
private final ITInviteCodeService inviteCodeService;
private final SysRoleMapper sysRoleMapper;
private final SysUserRoleMapper sysUserRoleMapper;
private final TUserPlatformMapper userPlatformMapper;
private final IShopService shopService;
private final IShopDetailService shopDetailService;
private final ISpecService specService;
private final TShopMessageSubscribeService tShopMessageSubscribeService;
private final ISysConfigService configService;
/**
* 注册
*/
public void register(RegisterBody registerBody) {
String tenantId = registerBody.getTenantId();
String username = registerBody.getUsername();
String password = registerBody.getPassword();
String phoneNumber = registerBody.getPhoneNumber();
String inviteCode = registerBody.getInviteCode();
// 校验用户类型是否存在
String userType = UserType.getUserType(registerBody.getUserType()).getUserType();
boolean captchaEnabled = captchaProperties.getEnable();
// 验证码开关
if (captchaEnabled) {
validateCaptcha(tenantId, username, registerBody.getCode(), registerBody.getUuid());
}
// 如果设置了邀请码必填则校验邀请码
if (StringUtils.isNotBlank(inviteCode)) {
TInviteCode code = inviteCodeService.validateInviteCode(inviteCode);
if (code == null) {
throw new UserException("邀请码无效或已过期");
}
}
SysUserBo sysUser = new SysUserBo();
sysUser.setUserName(username);
sysUser.setNickName(username);
sysUser.setPhonenumber(phoneNumber);
sysUser.setPassword(BCrypt.hashpw(password));
sysUser.setUserType(userType);
boolean exist = TenantHelper.dynamic(tenantId, () -> {
return userMapper.exists(new LambdaQueryWrapper<SysUser>()
.eq(SysUser::getUserName, sysUser.getUserName()));
});
if (exist) {
throw new UserException("user.register.save.error", username);
}
boolean exist1 = TenantHelper.dynamic(tenantId, () -> {
return userMapper.exists(new LambdaQueryWrapper<SysUser>()
.eq(SysUser::getPhonenumber, sysUser.getPhonenumber()));
});
if (exist1) {
throw new UserException("手机号已存在", phoneNumber);
}
boolean regFlag = userService.registerUser(sysUser, tenantId);
if (!regFlag) {
throw new UserException("user.register.error");
}
recordLogininfor(tenantId, username, Constants.REGISTER, MessageUtils.message("user.register.success"));
Long userId = userMapper.selectName(sysUser.getUserName());
// 处理拼多多相关参数
String pddMallId = registerBody.getPddMallId();
String pddMallName = registerBody.getPddMallName();
String pddType = registerBody.getPddType();
String token = registerBody.getAccessToken();
String skuSpec = registerBody.getSkuSpec();
// 检查是否是拼多多授权注册
if (StringUtils.isNotBlank(pddType) && "2".equals(pddType)) {
// 设置特定角色
String roleKey = "OnlineStore";
SysRole role = sysRoleMapper.selectRoleByRoleKey(roleKey);
if (role != null && role.getRoleId() != null) {
sysUserRoleMapper.insertUserRole(userId, role.getRoleId());
} else {
// 如果特定角色不存在使用默认角色
Long defaultRole = 1906216348137664514L;
userMapper.insertRole(userId, defaultRole);
}
if (StringUtils.isNotBlank(pddMallId) && StringUtils.isNotBlank(pddMallName)) {
// 添加用户平台关联
createUserPlatform(userId, pddMallId, pddMallName);
// 创建店铺
createShop(userId, pddMallId, pddMallName, token, phoneNumber, skuSpec);
// System.out.println("拼多多店铺创建成功 - 用户ID: " + userId + ", 店铺ID: " + pddMallId + ", 店铺名称: " + pddMallName);
}
} else {
// 设置普通用户角色
String roleKey = "OnlineStore";
SysRole role = sysRoleMapper.selectRoleByRoleKey(roleKey);
sysUserRoleMapper.insertUserRole(userId, role.getRoleId());
}
try{
Map regMap = new HashMap();
regMap.put("username",username);
regMap.put("password",password);
regMap.put("about_id",userId+"");
regMap.put("fid","0");
regMap.put("name",username);
regMap.put("phone",phoneNumber);
regMap.put("from","ERP");
// 调用接口
String res = InterfaceUtils.postForm(UrlUtil.getNewWarehouse(),"/api/employee/reg",regMap);
System.out.println(res);
}catch (Exception e){
System.out.println("调用租户注册接口异常");
}
// 处理邀请码
if (StringUtils.isNotBlank(inviteCode)) {
inviteCodeService.useInviteCode(inviteCode, userId);
}
}
/**
* 创建店铺
*/
private void createShop(Long userId, String pddMallId, String pddMallName, String token, String phoneNumber, String skuSpec) {
String pddIp = configService.selectConfigByKey("pdd.ip");
// System.out.println("获取拼多多IP配置: " + pddIp);
Date expirationTime;
//获取店铺订阅服务
String vasData = getInterface(pddIp, "/api/pdd/auth/getVasOrder?mallId=" + pddMallId);
Map vasMap = JsonUtil.transferToObj(vasData, Map.class);
//成功 调用新后台
Map vas = (Map) vasMap.get("data");
// System.out.println("获取订阅服务数据:" + JsonUtil.transferToJson(vas));
vas = MapUtils.convertKeys(vas);
// System.out.println("转变格式:" + JsonUtil.transferToJson(vas));
//计算到期时间
long payTime = Long.parseLong(vas.get("payTime").toString());
long timeLength = Long.parseLong(vas.get("timeLength").toString());
long newTimestamp = payTime + (timeLength * 1000);
expirationTime = new Date(newTimestamp);
ShopBo bo = new ShopBo();
bo.setShopType("1");
bo.setShopGroup("默认分组");
bo.setShopName(pddMallName);
bo.setPassword("123456");
bo.setShopAliasName(pddMallName);
bo.setCreateBy(userId);
bo.setUpdateBy(userId);
bo.setAccount(phoneNumber);
bo.setSkuSpec(skuSpec);
// bo.setToken(token);
bo.setTenant_id("000000");
bo.setAddTime(DateUtils.parseDate(DateUtils.getTime()));
bo.setUserId(userId);
bo.setShopAuthorize("1");
bo.setExpirationTime(expirationTime);
bo.setMallId(Long.valueOf(pddMallId));
bo.setUserId(userId);
// 如果有token则设置为已授权
if (StringUtils.isNotBlank(token)) {
bo.setToken(token);
bo.setShopAuthorize("1"); // 已授权
} else {
bo.setShopAuthorize("0"); // 未授权
}
shopService.insertByBo(bo);
// ShopBo shopBo = new ShopBo();
// shopBo.setId(id);
// shopBo.setMallId(Long.valueOf(pddMallId));
// shopBo.setToken(token);
// shopBo.setShopAliasName(pddMallName);
// shopBo.setShopAuthorize("1");
// shopBo.setExpirationTime(DateUtils.getNextDay());
// 绑定店铺
System.out.println("开始绑定店铺");
int mark = shopService.selectShopAssociationMall(bo);
System.out.println("店铺绑定结果: " + mark);
//
// // 处理订阅消息
// System.out.println("开始处理订阅消息");
// MessageSubscribeVo messageSubscribeVo = accessTokenVo.getMessageSubscribeVo();
// tShopMessageSubscribeService.insertOrUpdate(List.of(messageSubscribeVo), id);
// System.out.println("订阅消息处理完成");
}
/**
* 添加用户平台关联
*/
private void createUserPlatform(Long userId, String pddMallId, String pddMallName) {
TUserPlatform userPlatform = new TUserPlatform();
userPlatform.setType("1"); // 拼多多
userPlatform.setUserId(userId);
userPlatform.setPlatformId(pddMallId);
userPlatform.setPlatformName(pddMallName);
userPlatform.setStatus("0");
userPlatform.setDelFlag("0");
userPlatformMapper.insertTUserPlatform(userPlatform);
}
/**
* 校验验证码
*
* @param username 用户名
* @param code 验证码
* @param uuid 唯一标识
*/
public void validateCaptcha(String tenantId, String username, String code, String uuid) {
String verifyKey = GlobalConstants.CAPTCHA_CODE_KEY + StringUtils.blankToDefault(uuid, "");
String captcha = RedisUtils.getCacheObject(verifyKey);
RedisUtils.deleteObject(verifyKey);
if (captcha == null) {
recordLogininfor(tenantId, username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.expire"));
throw new CaptchaExpireException();
}
if (!code.equalsIgnoreCase(captcha)) {
recordLogininfor(tenantId, username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.error"));
throw new CaptchaException();
}
}
/**
* 记录登录信息
*
* @param tenantId 租户ID
* @param username 用户名
* @param status 状态
* @param message 消息内容
* @return
*/
private void recordLogininfor(String tenantId, String username, String status, String message) {
LogininforEvent logininforEvent = new LogininforEvent();
logininforEvent.setTenantId(tenantId);
logininforEvent.setUsername(username);
logininforEvent.setStatus(status);
logininforEvent.setMessage(message);
logininforEvent.setRequest(ServletUtils.getRequest());
SpringUtils.context().publishEvent(logininforEvent);
}
/**
* 微信注册
*/
public void WxRegister(WxRegisterDTO wxRegisterDTO) {
String tenantId = "000000";
String username = wxRegisterDTO.getUsername();
String phoneNumber = wxRegisterDTO.getPhoneNumber();
String password = wxRegisterDTO.getPassword();
String clientId = wxRegisterDTO.getClientId();
String inviteCode = wxRegisterDTO.getInviteCode();
if (StringUtils.isBlank(clientId)) {
throw new UserException("auth.clientid.not.blank");
}
// 如果设置了邀请码必填则校验邀请码
if (StringUtils.isNotBlank(inviteCode)) {
TInviteCode code = inviteCodeService.validateInviteCode(inviteCode);
if (code == null) {
throw new UserException("邀请码无效或已过期");
}
}
boolean captchaEnabled = captchaProperties.getEnable();
// 验证码开关
if (captchaEnabled) {
validateCaptcha(tenantId, username, wxRegisterDTO.getCode(), wxRegisterDTO.getUuid());
}
String checkUserName = userMapper.CheckUserName(username);
if (StringUtils.isNotBlank(checkUserName)) {
throw new UserException("用户名已存在,请更换用户名重新注册", username);
}
String checkPhoneNumber = userMapper.selectPhoneNumber(phoneNumber);
if (StringUtils.isNotBlank(checkPhoneNumber)) {
throw new UserException("手机号已存在,请更换手机号重新注册", phoneNumber);
}
SysUserBo sysUser = new SysUserBo();
sysUser.setUserName(username);
sysUser.setNickName(username);
sysUser.setPhonenumber(phoneNumber);
sysUser.setPassword(BCrypt.hashpw(password));
sysUser.setUserType("sys_user");
boolean regFlag = userService.registerUser(sysUser, tenantId);
if (!regFlag) {
throw new UserException("user.register.error");
}
recordLogininfor(tenantId, username, Constants.REGISTER, MessageUtils.message("user.register.success"));
Long userId = userMapper.selectName(sysUser.getUserName());
Long role = 1906216348137664514L;
userMapper.insertRole(userId, role);
// 处理邀请码
if (StringUtils.isNotBlank(inviteCode)) {
inviteCodeService.useInviteCode(inviteCode, userId);
}
}
}

View File

@ -0,0 +1,102 @@
package org.dromara.web.service.impl;
import cn.dev33.satoken.secure.BCrypt;
import cn.dev33.satoken.stp.SaLoginModel;
import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.core.util.ObjectUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.dromara.common.core.constant.SystemConstants;
import org.dromara.common.core.domain.model.AndroidLoginBody;
import org.dromara.common.core.domain.model.AndroidLoginUser;
import org.dromara.common.core.exception.ServiceException;
import org.dromara.common.core.utils.StringUtils;
import org.dromara.common.core.utils.ValidatorUtils;
import org.dromara.common.json.utils.JsonUtils;
import org.dromara.common.satoken.utils.LoginHelper;
import org.dromara.system.domain.vo.SysClientVo;
import org.dromara.system.domain.vo.SysUserVo;
import org.dromara.system.service.ISysUserService;
import org.dromara.web.domain.vo.LoginVo;
import org.dromara.web.service.IAuthStrategy;
import org.dromara.web.service.SysLoginService;
import org.springframework.stereotype.Service;
/**
* 安卓认证策略
*
* @author Michelle.Chung
*/
@Slf4j
@Service("android" + IAuthStrategy.BASE_NAME)
@RequiredArgsConstructor
public class AndroidAuthStrategy implements IAuthStrategy {
private final SysLoginService loginService;
private final ISysUserService userService;
@Override
public LoginVo login(String body, SysClientVo client) {
AndroidLoginBody loginBody = JsonUtils.parseObject(body, AndroidLoginBody.class);
ValidatorUtils.validate(loginBody);
String phoneNumber = loginBody.getPhoneNumber();
String password = loginBody.getPassword();
// 参数校验
if (StringUtils.isBlank(phoneNumber) || StringUtils.isBlank(password)) {
throw new ServiceException("手机号或密码不能为空");
}
// 根据手机号查询用户
SysUserVo user = userService.selectUserByPhonenumber(phoneNumber);
if (ObjectUtil.isNull(user)) {
log.info("登录用户手机号:{} 不存在.", phoneNumber);
throw new ServiceException("用户不存在");
}
// 验证密码是否正确
if (!BCrypt.checkpw(password, user.getPassword())) {
log.info("登录用户:{} 密码错误.", phoneNumber);
throw new ServiceException("手机号或密码错误");
}
// 检查用户状态
if (SystemConstants.DISABLE.equals(user.getStatus())) {
log.info("登录用户:{} 已被停用.", phoneNumber);
throw new ServiceException("账号已被停用");
}
// 构建登录用户信息
AndroidLoginUser loginUser = new AndroidLoginUser();
loginUser.setTenantId(user.getTenantId());
loginUser.setUserId(user.getUserId());
loginUser.setUserName(user.getUserName());
loginUser.setNickname(user.getNickName());
loginUser.setUserType(user.getUserType());
loginUser.setPhoneNumber(user.getPhonenumber());
loginUser.setClientKey(client.getClientKey());
loginUser.setDeviceType(client.getDeviceType());
// 配置登录模型
SaLoginModel model = new SaLoginModel();
model.setDevice(client.getDeviceType());
model.setTimeout(client.getTimeout());
model.setActiveTimeout(client.getActiveTimeout());
model.setExtra(LoginHelper.CLIENT_KEY, client.getClientId());
// 执行登录
LoginHelper.login(loginUser, model);
// 返回登录信息
LoginVo loginVo = new LoginVo();
loginVo.setAccessToken(StpUtil.getTokenValue());
loginVo.setExpireIn(StpUtil.getTokenTimeout());
loginVo.setClientId(client.getClientId());
loginVo.setPhoneNumber(user.getPhonenumber());
loginVo.setNickName(user.getNickName());
loginVo.setUserId(user.getUserId());
return loginVo;
}
}

View File

@ -0,0 +1,102 @@
package org.dromara.web.service.impl;
import cn.dev33.satoken.stp.SaLoginModel;
import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.core.util.ObjectUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.dromara.common.core.constant.Constants;
import org.dromara.common.core.constant.GlobalConstants;
import org.dromara.common.core.constant.SystemConstants;
import org.dromara.common.core.domain.model.EmailLoginBody;
import org.dromara.common.core.domain.model.LoginUser;
import org.dromara.common.core.enums.LoginType;
import org.dromara.common.core.exception.user.CaptchaExpireException;
import org.dromara.common.core.exception.user.UserException;
import org.dromara.common.core.utils.MessageUtils;
import org.dromara.common.core.utils.StringUtils;
import org.dromara.common.core.utils.ValidatorUtils;
import org.dromara.common.json.utils.JsonUtils;
import org.dromara.common.redis.utils.RedisUtils;
import org.dromara.common.satoken.utils.LoginHelper;
import org.dromara.common.tenant.helper.TenantHelper;
import org.dromara.system.domain.SysUser;
import org.dromara.system.domain.vo.SysClientVo;
import org.dromara.system.domain.vo.SysUserVo;
import org.dromara.system.mapper.SysUserMapper;
import org.dromara.web.domain.vo.LoginVo;
import org.dromara.web.service.IAuthStrategy;
import org.dromara.web.service.SysLoginService;
import org.springframework.stereotype.Service;
/**
* 邮件认证策略
*
* @author Michelle.Chung
*/
@Slf4j
@Service("email" + IAuthStrategy.BASE_NAME)
@RequiredArgsConstructor
public class EmailAuthStrategy implements IAuthStrategy {
private final SysLoginService loginService;
private final SysUserMapper userMapper;
@Override
public LoginVo login(String body, SysClientVo client) {
EmailLoginBody loginBody = JsonUtils.parseObject(body, EmailLoginBody.class);
ValidatorUtils.validate(loginBody);
String tenantId = loginBody.getTenantId();
String email = loginBody.getEmail();
String emailCode = loginBody.getEmailCode();
LoginUser loginUser = TenantHelper.dynamic(tenantId, () -> {
SysUserVo user = loadUserByEmail(email);
loginService.checkLogin(LoginType.EMAIL, tenantId, user.getUserName(), () -> !validateEmailCode(tenantId, email, emailCode));
// 此处可根据登录用户的数据不同 自行创建 loginUser 属性不够用继承扩展就行了
return loginService.buildLoginUser(user);
});
loginUser.setClientKey(client.getClientKey());
loginUser.setDeviceType(client.getDeviceType());
SaLoginModel model = new SaLoginModel();
model.setDevice(client.getDeviceType());
// 自定义分配 不同用户体系 不同 token 授权时间 不设置默认走全局 yml 配置
// 例如: 后台用户30分钟过期 app用户1天过期
model.setTimeout(client.getTimeout());
model.setActiveTimeout(client.getActiveTimeout());
model.setExtra(LoginHelper.CLIENT_KEY, client.getClientId());
// 生成token
LoginHelper.login(loginUser, model);
LoginVo loginVo = new LoginVo();
loginVo.setAccessToken(StpUtil.getTokenValue());
loginVo.setExpireIn(StpUtil.getTokenTimeout());
loginVo.setClientId(client.getClientId());
return loginVo;
}
/**
* 校验邮箱验证码
*/
private boolean validateEmailCode(String tenantId, String email, String emailCode) {
String code = RedisUtils.getCacheObject(GlobalConstants.CAPTCHA_CODE_KEY + email);
if (StringUtils.isBlank(code)) {
loginService.recordLogininfor(tenantId, email, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.expire"));
throw new CaptchaExpireException();
}
return code.equals(emailCode);
}
private SysUserVo loadUserByEmail(String email) {
SysUserVo user = userMapper.selectVoOne(new LambdaQueryWrapper<SysUser>().eq(SysUser::getEmail, email));
if (ObjectUtil.isNull(user)) {
log.info("登录用户:{} 不存在.", email);
throw new UserException("user.not.exists", email);
} else if (SystemConstants.DISABLE.equals(user.getStatus())) {
log.info("登录用户:{} 已被停用.", email);
throw new UserException("user.blocked", email);
}
return user;
}
}

View File

@ -0,0 +1,102 @@
package org.dromara.web.service.impl;
import cn.dev33.satoken.stp.SaLoginModel;
import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.core.util.ObjectUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.dromara.common.core.constant.Constants;
import org.dromara.common.core.constant.GlobalConstants;
import org.dromara.common.core.constant.SystemConstants;
import org.dromara.common.core.domain.model.LoginUser;
import org.dromara.common.core.domain.model.SmsLoginBody;
import org.dromara.common.core.enums.LoginType;
import org.dromara.common.core.exception.user.CaptchaExpireException;
import org.dromara.common.core.exception.user.UserException;
import org.dromara.common.core.utils.MessageUtils;
import org.dromara.common.core.utils.StringUtils;
import org.dromara.common.core.utils.ValidatorUtils;
import org.dromara.common.json.utils.JsonUtils;
import org.dromara.common.redis.utils.RedisUtils;
import org.dromara.common.satoken.utils.LoginHelper;
import org.dromara.common.tenant.helper.TenantHelper;
import org.dromara.system.domain.SysUser;
import org.dromara.system.domain.vo.SysClientVo;
import org.dromara.system.domain.vo.SysUserVo;
import org.dromara.system.mapper.SysUserMapper;
import org.dromara.web.domain.vo.LoginVo;
import org.dromara.web.service.IAuthStrategy;
import org.dromara.web.service.SysLoginService;
import org.springframework.stereotype.Service;
/**
* 短信认证策略
*
* @author Michelle.Chung
*/
@Slf4j
@Service("sms" + IAuthStrategy.BASE_NAME)
@RequiredArgsConstructor
public class SmsAuthStrategy implements IAuthStrategy {
private final SysLoginService loginService;
private final SysUserMapper userMapper;
@Override
public LoginVo login(String body, SysClientVo client) {
SmsLoginBody loginBody = JsonUtils.parseObject(body, SmsLoginBody.class);
ValidatorUtils.validate(loginBody);
String tenantId = loginBody.getTenantId();
String phonenumber = loginBody.getPhonenumber();
String smsCode = loginBody.getSmsCode();
LoginUser loginUser = TenantHelper.dynamic(tenantId, () -> {
SysUserVo user = loadUserByPhonenumber(phonenumber);
loginService.checkLogin(LoginType.SMS, tenantId, user.getUserName(), () -> !validateSmsCode(tenantId, phonenumber, smsCode));
// 此处可根据登录用户的数据不同 自行创建 loginUser 属性不够用继承扩展就行了
return loginService.buildLoginUser(user);
});
loginUser.setClientKey(client.getClientKey());
loginUser.setDeviceType(client.getDeviceType());
SaLoginModel model = new SaLoginModel();
model.setDevice(client.getDeviceType());
// 自定义分配 不同用户体系 不同 token 授权时间 不设置默认走全局 yml 配置
// 例如: 后台用户30分钟过期 app用户1天过期
model.setTimeout(client.getTimeout());
model.setActiveTimeout(client.getActiveTimeout());
model.setExtra(LoginHelper.CLIENT_KEY, client.getClientId());
// 生成token
LoginHelper.login(loginUser, model);
LoginVo loginVo = new LoginVo();
loginVo.setAccessToken(StpUtil.getTokenValue());
loginVo.setExpireIn(StpUtil.getTokenTimeout());
loginVo.setClientId(client.getClientId());
return loginVo;
}
/**
* 校验短信验证码
*/
private boolean validateSmsCode(String tenantId, String phonenumber, String smsCode) {
String code = RedisUtils.getCacheObject(GlobalConstants.CAPTCHA_CODE_KEY + phonenumber);
if (StringUtils.isBlank(code)) {
loginService.recordLogininfor(tenantId, phonenumber, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.expire"));
throw new CaptchaExpireException();
}
return code.equals(smsCode);
}
private SysUserVo loadUserByPhonenumber(String phonenumber) {
SysUserVo user = userMapper.selectVoOne(new LambdaQueryWrapper<SysUser>().eq(SysUser::getPhonenumber, phonenumber));
if (ObjectUtil.isNull(user)) {
log.info("登录用户:{} 不存在.", phonenumber);
throw new UserException("user.not.exists", phonenumber);
} else if (SystemConstants.DISABLE.equals(user.getStatus())) {
log.info("登录用户:{} 已被停用.", phonenumber);
throw new UserException("user.blocked", phonenumber);
}
return user;
}
}

View File

@ -0,0 +1,292 @@
--- # 临时文件存储位置 避免临时文件被系统清理报错
spring.servlet.multipart.location: /ruoyi/server/temp
--- # 监控中心配置
spring.boot.admin.client:
# 增加客户端开关
enabled: false
url: http://localhost:9090/admin
instance:
service-host-type: IP
metadata:
username: ${spring.boot.admin.client.username}
userpassword: ${spring.boot.admin.client.password}
username: @monitor.username@
password: @monitor.password@
--- # snail-job 配置
snail-job:
enabled: false
# 需要在 SnailJob 后台组管理创建对应名称的组,然后创建任务的时候选择对应的组,才能正确分派任务
group: "ruoyi_group"
# SnailJob 接入验证令牌 详见 script/sql/ry_job.sql `sj_group_config`表
token: "SJ_cKqBTPzCsWA3VyuCfFoccmuIEGXjr5KT"
server:
host: 127.0.0.1
port: 17888
# 命名空间UUID 详见 script/sql/ry_job.sql `sj_namespace`表`unique_id`字段
namespace: ${spring.profiles.active}
# 随主应用端口漂移
port: 2${server.port}
# 客户端ip指定
host:
# RPC类型: netty, grpc
rpc-type: grpc
--- # 数据源配置
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://146.56.227.42:3306/zhishu?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true&rewriteBatchedStatements=true&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true
# url: jdbc:mysql://111.229.25.150:3306/zhishu?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true&rewriteBatchedStatements=true&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true
username: zhishu
password: XsRR4K3ATizyc5BK
# # 腾讯云数据库
slave:
lazy: true
type: ${spring.datasource.type}
driverClassName: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://nj-cynosdbmysql-grp-1v6vxn5f.sql.tencentcdb.com:26247/task?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true&rewriteBatchedStatements=true&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true
username: root
password: Long6166@@
taskSlave:
lazy: true
type: ${spring.datasource.type}
driverClassName: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://36.212.12.247:3306/zhishu_slave?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true&rewriteBatchedStatements=true&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true
username: zhishu_slave
password: 7DpixiEdCs5p3PEr
psi:
lazy: true
type: ${spring.datasource.type}
driverClassName: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://175.27.224.66:3306/psi?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true&rewriteBatchedStatements=true&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true
username: root
password: 5e07c0eec1770c94
# oracle:
# type: ${spring.datasource.type}
# driverClassName: oracle.jdbc.OracleDriver
# url: jdbc:oracle:thin:@//localhost:1521/XE
# username: ROOT
# password: root
# 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
# 多久检查一次连接的活性
keepaliveTime: 30000
--- # redis 单机配置(单机与集群只能开启一个另一个需要注释掉)
spring.data:
redis:
# 地址
host: 146.56.227.42
# 端口默认为6379
port: 6379
# 数据库索引
database: 0
# redis 密码必须配置
password: Qq123123
# 连接超时时间
timeout: 10s
# 是否开启ssl
ssl.enabled: false
# redisson 配置
redisson:
# redis key前缀
keyPrefix:
# 线程池数量
threads: 16
# Netty线程池数量
nettyThreads: 32
# 单节点配置
singleServerConfig:
# 客户端名称
clientName: ${ruoyi.name}
# 最小空闲连接数
connectionMinimumIdleSize: 32
# 连接池大小
connectionPoolSize: 64
# 连接空闲超时,单位:毫秒
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://sms4j.com/doc3/ 差异配置文档地址 支持单厂商多配置,可以配置多个同时使用
sms:
# 配置源类型用于标定配置来源(interface,yaml)
config-type: yaml
# 用于标定yml中的配置是否开启短信拦截接口配置不受此限制
restricted: true
# 短信拦截限制单手机号每分钟最大发送,只对开启了拦截的配置有效
minute-max: 1
# 短信拦截限制单手机号每日最大发送量,只对开启了拦截的配置有效
account-max: 30
# 以下配置来自于 org.dromara.sms4j.provider.config.BaseConfig类中
blends:
# 唯一ID 用于发送短信寻找具体配置 随便定义别用中文即可
# 可以同时存在两个相同厂商 例如: ali1 ali2 两个不同的阿里短信账号 也可用于区分租户
config1:
# 框架定义的厂商名称标识,标定此配置是哪个厂商,详细请看厂商标识介绍部分
supplier: alibaba
# 有些称为accessKey有些称之为apiKey也有称为sdkKey或者appId。
access-key-id: 您的accessKey
# 称为accessSecret有些称之为apiSecret
access-key-secret: 您的accessKeySecret
signature: 您的短信签名
sdk-app-id: 您的sdkAppId
config2:
# 厂商标识,标定此配置是哪个厂商,详细请看厂商标识介绍部分
supplier: tencent
access-key-id: 您的accessKey
access-key-secret: 您的accessKeySecret
signature: 您的短信签名
sdk-app-id: 您的sdkAppId
--- # 三方授权
justauth:
# 前端外网访问地址
address: http://localhost:80
type:
maxkey:
# maxkey 服务器地址
# 注意 如下均配置均不需要修改 maxkey 已经内置好了数据
server-url: http://sso.maxkey.top
client-id: 876892492581044224
client-secret: x1Y5MTMwNzIwMjMxNTM4NDc3Mzche8
redirect-uri: ${justauth.address}/social-callback?source=maxkey
topiam:
# topiam 服务器地址
server-url: http://127.0.0.1:1989/api/v1/authorize/y0q************spq***********8ol
client-id: 449c4*********937************759
client-secret: ac7***********1e0************28d
redirect-uri: ${justauth.address}/social-callback?source=topiam
scopes: [ openid, email, phone, profile ]
qq:
client-id: 10**********6
client-secret: 1f7d08**********5b7**********29e
redirect-uri: ${justauth.address}/social-callback?source=qq
union-id: false
weibo:
client-id: 10**********6
client-secret: 1f7d08**********5b7**********29e
redirect-uri: ${justauth.address}/social-callback?source=weibo
gitee:
client-id: 91436b7940090d09c72c7daf85b959cfd5f215d67eea73acbf61b6b590751a98
client-secret: 02c6fcfd70342980cd8dd2f2c06c1a350645d76c754d7a264c4e125f9ba915ac
redirect-uri: ${justauth.address}/social-callback?source=gitee
dingtalk:
client-id: 10**********6
client-secret: 1f7d08**********5b7**********29e
redirect-uri: ${justauth.address}/social-callback?source=dingtalk
baidu:
client-id: 10**********6
client-secret: 1f7d08**********5b7**********29e
redirect-uri: ${justauth.address}/social-callback?source=baidu
csdn:
client-id: 10**********6
client-secret: 1f7d08**********5b7**********29e
redirect-uri: ${justauth.address}/social-callback?source=csdn
coding:
client-id: 10**********6
client-secret: 1f7d08**********5b7**********29e
redirect-uri: ${justauth.address}/social-callback?source=coding
coding-group-name: xx
oschina:
client-id: 10**********6
client-secret: 1f7d08**********5b7**********29e
redirect-uri: ${justauth.address}/social-callback?source=oschina
alipay_wallet:
client-id: 10**********6
client-secret: 1f7d08**********5b7**********29e
redirect-uri: ${justauth.address}/social-callback?source=alipay_wallet
alipay-public-key: MIIB**************DAQAB
wechat_open:
client-id: 10**********6
client-secret: 1f7d08**********5b7**********29e
redirect-uri: ${justauth.address}/social-callback?source=wechat_open
wechat_mp:
client-id: 10**********6
client-secret: 1f7d08**********5b7**********29e
redirect-uri: ${justauth.address}/social-callback?source=wechat_mp
wechat_enterprise:
client-id: 10**********6
client-secret: 1f7d08**********5b7**********29e
redirect-uri: ${justauth.address}/social-callback?source=wechat_enterprise
agent-id: 1000002
gitlab:
client-id: 10**********6
client-secret: 1f7d08**********5b7**********29e
redirect-uri: ${justauth.address}/social-callback?source=gitlab
# zhishu业务配置
zhishu:
url: ./file/
filterUrl: ./file/fiter/
kfz-service-url: http://146.56.227.42:8095
history-shop-god-s-excel-file-path: ./ShopGoodsData/HistoryGoodsData
new-shop-god-s-excel-file-path: ./ShopGoodsData/NewShopGoodsData
changed-shop-god-s-excel-file-path: ./ShopGoodsData/ChangedShopGoodsData
error-shop-god-s-excel-file-path: ./ShopGoodsData/ErrorShopGoodsData

View File

@ -0,0 +1,8 @@
Application Version: ${revision}
Spring Boot Version: ${spring-boot.version}
__________ _____.___.__ ____ ____ __________.__
\______ \__ __ ____\__ | |__| \ \ / /_ __ ____ \______ \ | __ __ ______
| _/ | \/ _ \/ | | | ______ \ Y / | \_/ __ \ ______ | ___/ | | | \/ ___/
| | \ | ( <_> )____ | | /_____/ \ /| | /\ ___/ /_____/ | | | |_| | /\___ \
|____|_ /____/ \____// ______|__| \___/ |____/ \___ > |____| |____/____//____ >
\/ \/ \/ \/

View File

@ -0,0 +1,61 @@
#错误消息
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{0} has been deleted
user.blocked=Sorry, your account: {0} has been disabled. Please contact the administrator
role.blocked=Role disabledplease 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 exitplease login again
user.unknown.error=Unknown error, please login again
auth.grant.type.error=Auth grant type error
auth.grant.type.blocked=Auth grant type disabled
auth.grant.type.not.blank=Auth grant type cannot be blank
auth.clientid.not.blank=Auth clientid cannot be blank
##文件上传消息
upload.exceed.maxSize=The uploaded file size exceeds the limit file size<br/>the maximum allowed file size is{0}MB
upload.filename.exceed.length=The maximum length of uploaded file name is {0} characters
##权限
no.permission=You do not have permission to the dataplease contact your administrator to add permissions [{0}]
no.create.permission=You do not have permission to create dataplease contact your administrator to add permissions [{0}]
no.update.permission=You do not have permission to modify dataplease contact your administrator to add permissions [{0}]
no.delete.permission=You do not have permission to delete dataplease contact your administrator to add permissions [{0}]
no.export.permission=You do not have permission to export dataplease contact your administrator to add permissions [{0}]
no.view.permission=You do not have permission to view dataplease 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
social.source.not.blank=Social login platform [source] cannot be blank
social.code.not.blank=Social login platform [code] cannot be blank
social.state.not.blank=Social login platform [state] cannot be blank
##租户
tenant.number.not.blank=Tenant number cannot be blank
tenant.not.exists=Sorry, your tenant does not exist. Please contact the administrator
tenant.blocked=Sorry, your tenant is disabled. Please contact the administrator
tenant.expired=Sorry, your tenant has expired. Please contact the administrator.

Binary file not shown.

View File

@ -0,0 +1,129 @@
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<property name="log.path" value="./logs"/>
<property name="console.log.pattern"
value="%cyan(%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>

View File

@ -0,0 +1,100 @@
package org.dromara.test;
import org.dromara.common.core.config.RuoYiConfig;
import org.dromara.zhishu.util.CnumberUtils;
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;
@Autowired
private CnumberUtils cnumberUtils;
@DisplayName("测试 @SpringBootTest @Test @DisplayName 注解")
@Test
public void testTest() {
String number= cnumberUtils.getArtNo(8.5, 1914486937581867009L,"1451236547842", 1916429009234714626L, null, null, null);
// String number7= cnumberUtils.getArtNo(8.5, 1L, null, 1906189381131055106L, null, null);
// String number1= cnumberUtils.getArtNo(8.5, 1L, null, 1906189381131055106L, 1906190045781438466L, null);
// String number2= cnumberUtils.getArtNo(8.5, 1L, null, 1906189381131055106L, 1906190045781438466L, 1759L);
// String number3= cnumberUtils.getArtNo(8.5, 1L, "7507301079", null, null, null);
// String number4= cnumberUtils.getArtNo(8.5, 1L, "1234567819", 1906189381131055106L, null, null);
// String number5= cnumberUtils.getArtNo(8.5, 1L, "1234567811", 1906189381131055106L, 1906190045781438466L, null);
// String number6= cnumberUtils.getArtNo(8.5, 1L, "1234567811", 1906189381131055106L, 1906190045781438466L, 1759L);
System.out.println(number);
// System.out.println(number6);
// System.out.println(number1);
// System.out.println(number2);
// System.out.println(number3);
// System.out.println(number4);
// System.out.println(number5);
// System.out.println(number6);
// System.out.println(number7);
System.out.println(ruoYiConfig);
}
@Disabled
@DisplayName("测试 @Disabled 注解")
@Test
public void testDisabled() {
System.out.println(ruoYiConfig);
}
// 设置测试方法的最大执行时间为2秒如果超过这个时间测试将被视为失败
@Timeout(value = 2L, unit = TimeUnit.SECONDS)
// 为测试方法设置显示名称提高测试报告的可读性
@DisplayName("测试 @Timeout 注解")
// 标识这是一个JUnit测试方法
@Test
public void testTimeout() throws InterruptedException {
// 模拟一个长时间的任务以测试@Timeout注解的效果
Thread.sleep(3000);
// 打印配置信息这里假设ruoYiConfig是一个已经初始化的配置对象
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 ==================");
}
}

View File

@ -0,0 +1,54 @@
package org.dromara.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 ==================");
}
}

Binary file not shown.

View File

@ -0,0 +1,4 @@
3
SIMSUN.TTC -misc-simsun-medium-r-normal--0-0-0-0-p-0-iso10646-1
SIMSUN.TTC -misc-simsun-medium-r-normal--0-0-0-0-p-0-iso8859-1
SIMSUN.TTC -misc-simsun-medium-r-normal--0-0-0-0-p-0-koi8-r

View File

@ -0,0 +1,52 @@
package org.dromara.common.core.config;
import cn.hutool.core.util.ArrayUtil;
import org.dromara.common.core.exception.ServiceException;
import org.dromara.common.core.utils.SpringUtils;
import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.core.task.VirtualThreadTaskExecutor;
import org.springframework.scheduling.annotation.AsyncConfigurer;
import java.util.Arrays;
import java.util.concurrent.Executor;
/**
* 异步配置
* <p>
* 如果未使用虚拟线程则生效
*
* @author Lion Li
*/
@AutoConfiguration
public class AsyncConfig implements AsyncConfigurer {
/**
* 自定义 @Async 注解使用系统线程池
*/
@Override
public Executor getAsyncExecutor() {
if(SpringUtils.isVirtual()) {
return new VirtualThreadTaskExecutor("async-");
}
return SpringUtils.getBean("scheduledExecutorService");
}
/**
* 异步执行异常处理
*/
@Override
public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
return (throwable, method, objects) -> {
throwable.printStackTrace();
StringBuilder sb = new StringBuilder();
sb.append("Exception message - ").append(throwable.getMessage())
.append(", Method name - ").append(method.getName());
if (ArrayUtil.isNotEmpty(objects)) {
sb.append(", Parameter value - ").append(Arrays.toString(objects));
}
throw new ServiceException(sb.toString());
};
}
}

View File

@ -0,0 +1,83 @@
package org.dromara.common.core.constant;
/**
* 缓存组名称常量
* <p>
* key 格式为 cacheNames#ttl#maxIdleTime#maxSize
* <p>
* ttl 过期时间 如果设置为0则不过期 默认为0
* maxIdleTime 最大空闲时间 根据LRU算法清理空闲数据 如果设置为0则不检测 默认为0
* maxSize 组最大长度 根据LRU算法清理溢出数据 如果设置为0则无限长 默认为0
* <p>
* 例子: test#60stest#0#60stest#0#1m#1000test#1h#0#500
*
* @author Lion Li
*/
public interface CacheNames {
/**
* 演示案例
*/
String DEMO_CACHE = "demo:cache#60s#10m#20";
/**
* 系统配置
*/
String SYS_CONFIG = "sys_config";
/**
* 数据字典
*/
String SYS_DICT = "sys_dict";
/**
* 租户
*/
String SYS_TENANT = GlobalConstants.GLOBAL_REDIS_KEY + "sys_tenant#30d";
/**
* 客户端
*/
String SYS_CLIENT = GlobalConstants.GLOBAL_REDIS_KEY + "sys_client#30d";
/**
* 用户账户
*/
String SYS_USER_NAME = "sys_user_name#30d";
/**
* 用户名称
*/
String SYS_NICKNAME = "sys_nickname#30d";
/**
* 部门
*/
String SYS_DEPT = "sys_dept#30d";
/**
* OSS内容
*/
String SYS_OSS = "sys_oss#30d";
/**
* 角色自定义权限
*/
String SYS_ROLE_CUSTOM = "sys_role_custom#30d";
/**
* 部门及以下权限
*/
String SYS_DEPT_AND_CHILD = "sys_dept_and_child#30d";
/**
* OSS配置
*/
String SYS_OSS_CONFIG = GlobalConstants.GLOBAL_REDIS_KEY + "sys_oss_config";
/**
* 在线用户
*/
String ONLINE_TOKEN = "online_tokens";
}

View File

@ -0,0 +1,34 @@
package org.dromara.common.core.constant;
/**
* 全局的key常量 (业务无关的key)
*
* @author Lion Li
*/
public interface GlobalConstants {
/**
* 全局 redis key (业务无关的key)
*/
String GLOBAL_REDIS_KEY = "global:";
/**
* 验证码 redis key
*/
String CAPTCHA_CODE_KEY = GLOBAL_REDIS_KEY + "captcha_codes:";
/**
* 防重提交 redis key
*/
String REPEAT_SUBMIT_KEY = GLOBAL_REDIS_KEY + "repeat_submit:";
/**
* 限流 redis key
*/
String RATE_LIMIT_KEY = GLOBAL_REDIS_KEY + "rate_limit:";
/**
* 三方认证 redis key
*/
String SOCIAL_AUTH_CODE_KEY = GLOBAL_REDIS_KEY + "social_auth_codes:";
}

View File

@ -0,0 +1,54 @@
package org.dromara.common.core.constant;
import cn.hutool.core.lang.RegexPool;
/**
* 常用正则表达式字符串
* <p>
* 常用正则表达式集合更多正则见: https://any86.github.io/any-rule/
*
* @author Feng
*/
public interface RegexConstants extends RegexPool {
/**
* 字典类型必须以字母开头且只能为小写字母数字下滑线
*/
String DICTIONARY_TYPE = "^[a-z][a-z0-9_]*$";
/**
* 权限标识必须符合 tool:build:list 格式或者空字符串
*/
String PERMISSION_STRING = "^(|^[a-zA-Z0-9_]+:[a-zA-Z0-9_]+:[a-zA-Z0-9_]+)$";
/**
* 身份证号码后6位
*/
String ID_CARD_LAST_6 = "^(([0-2][1-9])|10|20|30|31)\\d{3}[0-9Xx]$";
/**
* QQ号码
*/
String QQ_NUMBER = "^[1-9][0-9]\\d{4,9}$";
/**
* 邮政编码
*/
String POSTAL_CODE = "^[1-9]\\d{5}$";
/**
* 注册账号
*/
String ACCOUNT = "^[a-zA-Z][a-zA-Z0-9_]{4,15}$";
/**
* 密码包含至少8个字符包括大写字母小写字母数字和特殊字符
*/
String PASSWORD = "^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d)(?=.*[@$!%*?&])[A-Za-z\\d@$!%*?&]{8,}$";
/**
* 通用状态0表示正常1表示停用
*/
String STATUS = "^[01]$";
}

View File

@ -0,0 +1,37 @@
package org.dromara.common.core.domain.dto;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serial;
import java.io.Serializable;
/**
* 部门
*
* @author AprilWind
*/
@Data
@NoArgsConstructor
public class DeptDTO implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 部门ID
*/
private Long deptId;
/**
* 父部门ID
*/
private Long parentId;
/**
* 部门名称
*/
private String deptName;
}

View File

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

View File

@ -0,0 +1,45 @@
package org.dromara.common.core.domain.dto;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
/**
* 启动流程对象
*
* @author may
*/
@Data
public class StartProcessDTO implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 业务唯一值id
*/
private String businessId;
/**
* 流程定义编码
*/
private String flowCode;
/**
* 流程变量前端会提交一个元素{'entity': {业务详情数据对象}}
*/
private Map<String, Object> variables;
public Map<String, Object> getVariables() {
if (variables == null) {
return new HashMap<>(16);
}
variables.entrySet().removeIf(entry -> Objects.isNull(entry.getValue()));
return variables;
}
}

View File

@ -0,0 +1,73 @@
package org.dromara.common.core.domain.dto;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serial;
import java.io.Serializable;
import java.util.Date;
/**
* 用户
*
* @author Michelle.Chung
*/
@Data
@NoArgsConstructor
public class UserDTO implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 用户ID
*/
private Long userId;
/**
* 部门ID
*/
private Long deptId;
/**
* 用户账号
*/
private String userName;
/**
* 用户昵称
*/
private String nickName;
/**
* 用户类型sys_user系统用户
*/
private String userType;
/**
* 用户邮箱
*/
private String email;
/**
* 手机号码
*/
private String phonenumber;
/**
* 用户性别0男 1女 2未知
*/
private String sex;
/**
* 帐号状态0正常 1停用
*/
private String status;
/**
* 创建时间
*/
private Date createTime;
}

View File

@ -0,0 +1,34 @@
package org.dromara.common.core.domain.event;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
/**
* 删除流程监听
*
* @author AprilWind
*/
@Data
public class ProcessDeleteEvent implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 租户ID
*/
private String tenantId;
/**
* 流程定义编码
*/
private String flowCode;
/**
* 业务id
*/
private String businessId;
}

View File

@ -0,0 +1,50 @@
package org.dromara.common.core.domain.event;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
import java.util.Map;
/**
* 总体流程监听
*
* @author may
*/
@Data
public class ProcessEvent implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 租户ID
*/
private String tenantId;
/**
* 流程定义编码
*/
private String flowCode;
/**
* 业务id
*/
private String businessId;
/**
* 状态
*/
private String status;
/**
* 办理参数
*/
private Map<String, Object> params;
/**
* 当为true时为申请人节点办理
*/
private boolean submit;
}

View File

@ -0,0 +1,27 @@
package org.dromara.common.core.domain.model;
import jakarta.validation.constraints.NotBlank;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* 安卓登录对象
*
* @author Michelle.Chung
*/
@Data
@EqualsAndHashCode(callSuper = true)
public class AndroidLoginBody extends LoginBody {
/**
* 手机号
*/
@NotBlank(message = "手机号不能为空")
private String phoneNumber;
/**
* 用户密码
*/
@NotBlank(message = "密码不能为空")
private String password;
}

View File

@ -0,0 +1,31 @@
package org.dromara.common.core.domain.model;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import java.io.Serial;
/**
* 安卓登录用户身份权限
*
* @author Michelle.Chung
*/
@Data
@EqualsAndHashCode(callSuper = true)
@NoArgsConstructor
public class AndroidLoginUser extends LoginUser {
@Serial
private static final long serialVersionUID = 1L;
/**
* 手机号
*/
private String phoneNumber;
/**
* 用户名
*/
private String userName;
}

View File

@ -0,0 +1,37 @@
package org.dromara.common.core.domain.model;
import jakarta.validation.constraints.NotBlank;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.hibernate.validator.constraints.Length;
/**
* 密码登录对象
*
* @author Lion Li
*/
@Data
@EqualsAndHashCode(callSuper = true)
public class PasswordLoginBody extends LoginBody {
// /**
// * 用户名
// */
// @NotBlank(message = "{user.username.not.blank}")
// @Length(min = 2, max = 20, message = "{user.username.length.valid}")
// private String username;
/**
* 用户密码
*/
@NotBlank(message = "{user.password.not.blank}")
@Length(min = 5, max = 20, message = "{user.password.length.valid}")
private String password;
/**
* 手机号
*/
@NotBlank(message = "{user.username.not.blank}")
private String phoneNumber;
}

View File

@ -0,0 +1,61 @@
package org.dromara.common.core.domain.model;
import jakarta.validation.constraints.NotBlank;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.hibernate.validator.constraints.Length;
/**
* 用户注册对象
*
* @author Lion Li
*/
@Data
@EqualsAndHashCode(callSuper = true)
public class RegisterBody extends LoginBody {
/**
* 用户名
*/
@NotBlank(message = "{user.username.not.blank}")
@Length(min = 2, max = 20, message = "{user.username.length.valid}")
private String username;
/**
* 用户密码
*/
@NotBlank(message = "{user.password.not.blank}")
@Length(min = 5, max = 20, message = "{user.password.length.valid}")
private String password;
private String userType;
private String phoneNumber;
/**
* 邀请码
*/
private String inviteCode;
/**
* 拼多多店铺ID
*/
private String pddMallId;
/**
* 拼多多店铺名称
*/
private String pddMallName;
/**
* 拼多多类型
*/
private String pddType;
/**
* 授权token
*/
private String accessToken;
private String skuSpec;
}

View File

@ -0,0 +1,31 @@
package org.dromara.common.core.domain.model;
import jakarta.validation.constraints.NotBlank;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* 三方登录对象
*
* @author Lion Li
*/
@Data
@EqualsAndHashCode(callSuper = true)
public class XcxLoginBody extends LoginBody {
/**
* 小程序id(多个小程序时使用)
*/
private String appid;
/**
* 小程序code
*/
@NotBlank(message = "{xcx.code.not.blank}")
private String xcxCode;
/**
* 用户密码
*/
private String password;
}

View File

@ -0,0 +1,28 @@
package org.dromara.common.core.domain.model;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import java.io.Serial;
/**
* 小程序登录用户身份权限
*
* @author Lion Li
*/
@Data
@EqualsAndHashCode(callSuper = true)
@NoArgsConstructor
public class XcxLoginUser extends LoginUser {
@Serial
private static final long serialVersionUID = 1L;
/**
* openid
*/
private String openid;
private String phoneNumber;
private String userName;
}

View File

@ -0,0 +1,215 @@
package org.dromara.common.core.enums;
import cn.hutool.core.util.StrUtil;
import lombok.AllArgsConstructor;
import lombok.Getter;
import org.dromara.common.core.exception.ServiceException;
import org.dromara.common.core.utils.StringUtils;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;
/**
* 业务状态枚举
*
* @author may
*/
@Getter
@AllArgsConstructor
public enum BusinessStatusEnum {
/**
* 已撤销
*/
CANCEL("cancel", "已撤销"),
/**
* 草稿
*/
DRAFT("draft", "草稿"),
/**
* 待审核
*/
WAITING("waiting", "待审核"),
/**
* 已完成
*/
FINISH("finish", "已完成"),
/**
* 已作废
*/
INVALID("invalid", "已作废"),
/**
* 已退回
*/
BACK("back", "已退回"),
/**
* 已终止
*/
TERMINATION("termination", "已终止");
/**
* 状态
*/
private final String status;
/**
* 描述
*/
private final String desc;
private static final Map<String, BusinessStatusEnum> STATUS_MAP = Arrays.stream(BusinessStatusEnum.values())
.collect(Collectors.toConcurrentMap(BusinessStatusEnum::getStatus, Function.identity()));
/**
* 根据状态获取对应的 BusinessStatusEnum 枚举
*
* @param status 业务状态码
* @return 对应的 BusinessStatusEnum 枚举如果找不到则返回 null
*/
public static BusinessStatusEnum getByStatus(String status) {
// 使用 STATUS_MAP 获取对应的枚举若找不到则返回 null
return STATUS_MAP.get(status);
}
/**
* 根据状态获取对应的业务状态描述信息
*
* @param status 业务状态码
* @return 返回业务状态描述若状态码为空或未找到对应的枚举返回空字符串
*/
public static String findByStatus(String status) {
if (StringUtils.isBlank(status)) {
return StrUtil.EMPTY;
}
BusinessStatusEnum statusEnum = STATUS_MAP.get(status);
return (statusEnum != null) ? statusEnum.getDesc() : StrUtil.EMPTY;
}
/**
* 判断是否为指定的状态之一草稿已撤销或已退回
*
* @param status 要检查的状态
* @return 如果状态为草稿已撤销或已退回之一则返回 true否则返回 false
*/
public static boolean isDraftOrCancelOrBack(String status) {
return DRAFT.status.equals(status) || CANCEL.status.equals(status) || BACK.status.equals(status);
}
/**
* 判断是否为撤销退回作废终止
*
* @param status status
* @return 结果
*/
public static boolean initialState(String status) {
return CANCEL.status.equals(status) || BACK.status.equals(status) || INVALID.status.equals(status) || TERMINATION.status.equals(status);
}
/**
* 获取运行中的实例状态列表
*
* @return 包含运行中实例状态的不可变列表
* 包含 DRAFTWAITINGBACK CANCEL 状态
*/
public static List<String> runningStatus() {
return Arrays.asList(DRAFT.status, WAITING.status, BACK.status, CANCEL.status);
}
/**
* 获取结束实例的状态列表
*
* @return 包含结束实例状态的不可变列表
* 包含 FINISHINVALID TERMINATION 状态
*/
public static List<String> finishStatus() {
return Arrays.asList(FINISH.status, INVALID.status, TERMINATION.status);
}
/**
* 启动流程校验
*
* @param status 状态
*/
public static void checkStartStatus(String status) {
if (WAITING.getStatus().equals(status)) {
throw new ServiceException("该单据已提交过申请,正在审批中!");
} else if (FINISH.getStatus().equals(status)) {
throw new ServiceException("该单据已完成申请!");
} else if (INVALID.getStatus().equals(status)) {
throw new ServiceException("该单据已作废!");
} else if (TERMINATION.getStatus().equals(status)) {
throw new ServiceException("该单据已终止!");
} else if (StringUtils.isBlank(status)) {
throw new ServiceException("流程状态为空!");
}
}
/**
* 撤销流程校验
*
* @param status 状态
*/
public static void checkCancelStatus(String status) {
if (CANCEL.getStatus().equals(status)) {
throw new ServiceException("该单据已撤销!");
} else if (FINISH.getStatus().equals(status)) {
throw new ServiceException("该单据已完成申请!");
} else if (INVALID.getStatus().equals(status)) {
throw new ServiceException("该单据已作废!");
} else if (TERMINATION.getStatus().equals(status)) {
throw new ServiceException("该单据已终止!");
} else if (BACK.getStatus().equals(status)) {
throw new ServiceException("该单据已退回!");
} else if (StringUtils.isBlank(status)) {
throw new ServiceException("流程状态为空!");
}
}
/**
* 驳回流程校验
*
* @param status 状态
*/
public static void checkBackStatus(String status) {
if (BACK.getStatus().equals(status)) {
throw new ServiceException("该单据已退回!");
} else if (FINISH.getStatus().equals(status)) {
throw new ServiceException("该单据已完成申请!");
} else if (INVALID.getStatus().equals(status)) {
throw new ServiceException("该单据已作废!");
} else if (TERMINATION.getStatus().equals(status)) {
throw new ServiceException("该单据已终止!");
} else if (CANCEL.getStatus().equals(status)) {
throw new ServiceException("该单据已撤销!");
} else if (StringUtils.isBlank(status)) {
throw new ServiceException("流程状态为空!");
}
}
/**
* 作废,终止流程校验
*
* @param status 状态
*/
public static void checkInvalidStatus(String status) {
if (FINISH.getStatus().equals(status)) {
throw new ServiceException("该单据已完成申请!");
} else if (INVALID.getStatus().equals(status)) {
throw new ServiceException("该单据已作废!");
} else if (TERMINATION.getStatus().equals(status)) {
throw new ServiceException("该单据已终止!");
} else if (StringUtils.isBlank(status)) {
throw new ServiceException("流程状态为空!");
}
}
}

View File

@ -0,0 +1,146 @@
package org.dromara.common.core.enums;
import lombok.AllArgsConstructor;
import lombok.Getter;
import org.dromara.common.core.utils.StringUtils;
/*
* 日期格式
* "yyyy"4位数的年份例如2023年表示为"2023"
* "yy"2位数的年份例如2023年表示为"23"
* "MM"2位数的月份取值范围为01到12例如7月表示为"07"
* "M"不带前导零的月份取值范围为1到12例如7月表示为"7"
* "dd"2位数的日期取值范围为01到31例如22日表示为"22"
* "d"不带前导零的日期取值范围为1到31例如22日表示为"22"
* "EEEE"星期的全名例如星期三表示为"Wednesday"
* "E"星期的缩写例如星期三表示为"Wed"
* "DDD" "D"一年中的第几天取值范围为001到366例如第200天表示为"200"
* 时间格式
* "HH"24小时制的小时数取值范围为00到23例如下午5点表示为"17"
* "hh"12小时制的小时数取值范围为01到12例如下午5点表示为"05"
* "mm"分钟数取值范围为00到59例如30分钟表示为"30"
* "ss"秒数取值范围为00到59例如45秒表示为"45"
* "SSS"毫秒数取值范围为000到999例如123毫秒表示为"123"
*/
/**
* 日期格式与时间格式枚举
*/
@Getter
@AllArgsConstructor
public enum FormatsType {
/**
* 例如2023年表示为"23"
*/
YY("yy"),
/**
* 例如2023年表示为"2023"
*/
YYYY("yyyy"),
/**
* 例例如2023年7月可以表示为 "2023-07"
*/
YYYY_MM("yyyy-MM"),
/**
* 例如日期 "2023年7月22日" 可以表示为 "2023-07-22"
*/
YYYY_MM_DD("yyyy-MM-dd"),
/**
* 例如当前时间如果是 "2023年7月22日下午3点30分"则可以表示为 "2023-07-22 15:30"
*/
YYYY_MM_DD_HH_MM("yyyy-MM-dd HH:mm"),
/**
* 例如当前时间如果是 "2023年7月22日下午3点30分45秒"则可以表示为 "2023-07-22 15:30:45"
*/
YYYY_MM_DD_HH_MM_SS("yyyy-MM-dd HH:mm:ss"),
/**
* 例如下午3点30分45秒表示为 "15:30:45"
*/
HH_MM_SS("HH:mm:ss"),
/**
* 例例如2023年7月可以表示为 "2023/07"
*/
YYYY_MM_SLASH("yyyy/MM"),
/**
* 例如日期 "2023年7月22日" 可以表示为 "2023/07/22"
*/
YYYY_MM_DD_SLASH("yyyy/MM/dd"),
/**
* 例如当前时间如果是 "2023年7月22日下午3点30分45秒"则可以表示为 "2023/07/22 15:30:45"
*/
YYYY_MM_DD_HH_MM_SLASH("yyyy/MM/dd HH:mm"),
/**
* 例如当前时间如果是 "2023年7月22日下午3点30分45秒"则可以表示为 "2023/07/22 15:30:45"
*/
YYYY_MM_DD_HH_MM_SS_SLASH("yyyy/MM/dd HH:mm:ss"),
/**
* 例例如2023年7月可以表示为 "2023.07"
*/
YYYY_MM_DOT("yyyy.MM"),
/**
* 例如日期 "2023年7月22日" 可以表示为 "2023.07.22"
*/
YYYY_MM_DD_DOT("yyyy.MM.dd"),
/**
* 例如当前时间如果是 "2023年7月22日下午3点30分"则可以表示为 "2023.07.22 15:30"
*/
YYYY_MM_DD_HH_MM_DOT("yyyy.MM.dd HH:mm"),
/**
* 例如当前时间如果是 "2023年7月22日下午3点30分45秒"则可以表示为 "2023.07.22 15:30:45"
*/
YYYY_MM_DD_HH_MM_SS_DOT("yyyy.MM.dd HH:mm:ss"),
/**
* 例如2023年7月可以表示为 "202307"
*/
YYYYMM("yyyyMM"),
/**
* 例如2023年7月22日可以表示为 "20230722"
*/
YYYYMMDD("yyyyMMdd"),
/**
* 例如2023年7月22日下午3点可以表示为 "2023072215"
*/
YYYYMMDDHH("yyyyMMddHH"),
/**
* 例如2023年7月22日下午3点30分可以表示为 "202307221530"
*/
YYYYMMDDHHMM("yyyyMMddHHmm"),
/**
* 例如2023年7月22日下午3点30分45秒可以表示为 "20230722153045"
*/
YYYYMMDDHHMMSS("yyyyMMddHHmmss");
/**
* 时间格式
*/
private final String timeFormat;
public static FormatsType getFormatsType(String str) {
for (FormatsType value : values()) {
if (StringUtils.contains(str, value.getTimeFormat())) {
return value;
}
}
throw new RuntimeException("'FormatsType' not found By " + str);
}
}

View File

@ -0,0 +1,74 @@
package org.dromara.common.core.exception.base;
import lombok.AllArgsConstructor;
import org.dromara.common.core.utils.MessageUtils;
import org.dromara.common.core.utils.StringUtils;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import java.io.Serial;
/**
* 基础异常
*
* @author ruoyi
*/
@Data
@EqualsAndHashCode(callSuper = true)
@NoArgsConstructor
@AllArgsConstructor
public class BaseException extends RuntimeException {
@Serial
private static final long serialVersionUID = 1L;
/**
* 所属模块
*/
private String module;
/**
* 错误码
*/
private String code;
/**
* 错误码对应的参数
*/
private Object[] args;
/**
* 错误消息
*/
private String defaultMessage;
public BaseException(String module, String code, Object[] args) {
this(module, code, args, null);
}
public BaseException(String module, String defaultMessage) {
this(module, null, null, defaultMessage);
}
public BaseException(String code, Object[] args) {
this(null, code, args, null);
}
public BaseException(String defaultMessage) {
this(null, null, null, defaultMessage);
}
@Override
public String getMessage() {
String message = null;
if (!StringUtils.isEmpty(code)) {
message = MessageUtils.message(code, args);
}
if (message == null) {
message = defaultMessage;
}
return message;
}
}

View File

@ -0,0 +1,21 @@
package org.dromara.common.core.exception.file;
import org.dromara.common.core.exception.base.BaseException;
import java.io.Serial;
/**
* 文件信息异常类
*
* @author ruoyi
*/
public class FileException extends BaseException {
@Serial
private static final long serialVersionUID = 1L;
public FileException(String code, Object[] args) {
super("file", code, args, null);
}
}

View File

@ -0,0 +1,52 @@
package org.dromara.common.core.factory;
import cn.hutool.core.lang.PatternPool;
import org.dromara.common.core.constant.RegexConstants;
import java.util.regex.Pattern;
/**
* 正则表达式模式池工厂
* <p>初始化的时候将正则表达式加入缓存池当中</p>
* <p>提高正则表达式的性能避免重复编译相同的正则表达式</p>
*
* @author 21001
*/
public class RegexPatternPoolFactory extends PatternPool {
/**
* 字典类型必须以字母开头且只能为小写字母数字下滑线
*/
public static final Pattern DICTIONARY_TYPE = get(RegexConstants.DICTIONARY_TYPE);
/**
* 身份证号码后6位
*/
public static final Pattern ID_CARD_LAST_6 = get(RegexConstants.ID_CARD_LAST_6);
/**
* QQ号码
*/
public static final Pattern QQ_NUMBER = get(RegexConstants.QQ_NUMBER);
/**
* 邮政编码
*/
public static final Pattern POSTAL_CODE = get(RegexConstants.POSTAL_CODE);
/**
* 注册账号
*/
public static final Pattern ACCOUNT = get(RegexConstants.ACCOUNT);
/**
* 密码包含至少8个字符包括大写字母小写字母数字和特殊字符
*/
public static final Pattern PASSWORD = get(RegexConstants.PASSWORD);
/**
* 通用状态0表示正常1表示停用
*/
public static final Pattern STATUS = get(RegexConstants.STATUS);
}

View File

@ -0,0 +1,31 @@
package org.dromara.common.core.factory;
import org.dromara.common.core.utils.StringUtils;
import org.springframework.beans.factory.config.YamlPropertiesFactoryBean;
import org.springframework.core.env.PropertiesPropertySource;
import org.springframework.core.env.PropertySource;
import org.springframework.core.io.support.DefaultPropertySourceFactory;
import org.springframework.core.io.support.EncodedResource;
import java.io.IOException;
/**
* yml 配置源工厂
*
* @author Lion Li
*/
public class YmlPropertySourceFactory extends DefaultPropertySourceFactory {
@Override
public PropertySource<?> createPropertySource(String name, EncodedResource resource) throws IOException {
String sourceName = resource.getResource().getFilename();
if (StringUtils.isNotBlank(sourceName) && StringUtils.endsWithAny(sourceName, ".yml", ".yaml")) {
YamlPropertiesFactoryBean factory = new YamlPropertiesFactoryBean();
factory.setResources(resource.getResource());
factory.afterPropertiesSet();
return new PropertiesPropertySource(sourceName, factory.getObject());
}
return super.createPropertySource(name, resource);
}
}

View File

@ -0,0 +1,67 @@
package org.dromara.common.core.service;
import java.util.Map;
/**
* 通用 字典服务
*
* @author Lion Li
*/
public interface DictService {
/**
* 分隔符
*/
String SEPARATOR = ",";
/**
* 根据字典类型和字典值获取字典标签
*
* @param dictType 字典类型
* @param dictValue 字典值
* @return 字典标签
*/
default String getDictLabel(String dictType, String dictValue) {
return getDictLabel(dictType, dictValue, SEPARATOR);
}
/**
* 根据字典类型和字典标签获取字典值
*
* @param dictType 字典类型
* @param dictLabel 字典标签
* @return 字典值
*/
default String getDictValue(String dictType, String dictLabel) {
return getDictValue(dictType, dictLabel, SEPARATOR);
}
/**
* 根据字典类型和字典值获取字典标签
*
* @param dictType 字典类型
* @param dictValue 字典值
* @param separator 分隔符
* @return 字典标签
*/
String getDictLabel(String dictType, String dictValue, String separator);
/**
* 根据字典类型和字典标签获取字典值
*
* @param dictType 字典类型
* @param dictLabel 字典标签
* @param separator 分隔符
* @return 字典值
*/
String getDictValue(String dictType, String dictLabel, String separator);
/**
* 获取字典下所有的字典值与标签
*
* @param dictType 字典类型
* @return dictValue为keydictLabel为值组成的Map
*/
Map<String, String> getAllDictByDictType(String dictType);
}

View File

@ -0,0 +1,45 @@
package org.dromara.common.core.service;
import org.dromara.common.core.domain.dto.TaskAssigneeDTO;
import org.dromara.common.core.domain.model.TaskAssigneeBody;
/**
* 工作流设计器获取任务执行人
*
* @author Lion Li
*/
public interface TaskAssigneeService {
/**
* 查询角色并返回任务指派的列表支持分页
*
* @param taskQuery 查询条件
* @return 办理人
*/
TaskAssigneeDTO selectRolesByTaskAssigneeList(TaskAssigneeBody taskQuery);
/**
* 查询岗位并返回任务指派的列表支持分页
*
* @param taskQuery 查询条件
* @return 办理人
*/
TaskAssigneeDTO selectPostsByTaskAssigneeList(TaskAssigneeBody taskQuery);
/**
* 查询部门并返回任务指派的列表支持分页
*
* @param taskQuery 查询条件
* @return 办理人
*/
TaskAssigneeDTO selectDeptsByTaskAssigneeList(TaskAssigneeBody taskQuery);
/**
* 查询用户并返回任务指派的列表支持分页
*
* @param taskQuery 查询条件
* @return 办理人
*/
TaskAssigneeDTO selectUsersByTaskAssigneeList(TaskAssigneeBody taskQuery);
}

View File

@ -0,0 +1,94 @@
package org.dromara.common.core.service;
import org.dromara.common.core.domain.dto.UserDTO;
import java.util.List;
/**
* 通用 用户服务
*
* @author Lion Li
*/
public interface UserService {
/**
* 通过用户ID查询用户账户
*
* @param userId 用户ID
* @return 用户账户
*/
String selectUserNameById(Long userId);
/**
* 通过用户ID查询用户账户
*
* @param userId 用户ID
* @return 用户名称
*/
String selectNicknameById(Long userId);
/**
* 通过用户ID查询用户账户
*
* @param userIds 用户ID 多个用逗号隔开
* @return 用户名称
*/
String selectNicknameByIds(String userIds);
/**
* 通过用户ID查询用户手机号
*
* @param userId 用户id
* @return 用户手机号
*/
String selectPhonenumberById(Long userId);
/**
* 通过用户ID查询用户邮箱
*
* @param userId 用户id
* @return 用户邮箱
*/
String selectEmailById(Long userId);
/**
* 通过用户ID查询用户列表
*
* @param userIds 用户ids
* @return 用户列表
*/
List<UserDTO> selectListByIds(List<Long> userIds);
/**
* 通过角色ID查询用户ID
*
* @param roleIds 角色ids
* @return 用户ids
*/
List<Long> selectUserIdsByRoleIds(List<Long> roleIds);
/**
* 通过角色ID查询用户
*
* @param roleIds 角色ids
* @return 用户
*/
List<UserDTO> selectUsersByRoleIds(List<Long> roleIds);
/**
* 通过部门ID查询用户
*
* @param deptIds 部门ids
* @return 用户
*/
List<UserDTO> selectUsersByDeptIds(List<Long> deptIds);
/**
* 通过岗位ID查询用户
*
* @param postIds 岗位ids
* @return 用户
*/
List<UserDTO> selectUsersByPostIds(List<Long> postIds);
}

View File

@ -0,0 +1,86 @@
package org.dromara.common.core.service;
import org.dromara.common.core.domain.dto.CompleteTaskDTO;
import org.dromara.common.core.domain.dto.StartProcessDTO;
import org.dromara.common.core.domain.dto.StartProcessReturnDTO;
import java.util.List;
import java.util.Map;
/**
* 通用 工作流服务
*
* @author may
*/
public interface WorkflowService {
/**
* 运行中的实例 删除程实例删除历史记录删除业务与流程关联信息
*
* @param businessIds 业务id
* @return 结果
*/
boolean deleteInstance(List<Long> businessIds);
/**
* 获取当前流程状态
*
* @param taskId 任务id
* @return 状态
*/
String getBusinessStatusByTaskId(Long taskId);
/**
* 获取当前流程状态
*
* @param businessId 业务id
* @return 状态
*/
String getBusinessStatus(String businessId);
/**
* 设置流程变量
*
* @param instanceId 流程实例id
* @param variable 流程变量
*/
void setVariable(Long instanceId, Map<String, Object> variable);
/**
* 获取流程变量
*
* @param instanceId 流程实例id
*/
Map<String, Object> instanceVariable(Long instanceId);
/**
* 按照业务id查询流程实例id
*
* @param businessId 业务id
* @return 结果
*/
Long getInstanceIdByBusinessId(String businessId);
/**
* 新增租户流程定义
*
* @param tenantId 租户id
*/
void syncDef(String tenantId);
/**
* 启动流程
*
* @param startProcess 参数
* @return 结果
*/
StartProcessReturnDTO startWorkFlow(StartProcessDTO startProcess);
/**
* 办理任务
*
* @param completeTask 参数
* @return 结果
*/
boolean completeTask(CompleteTaskDTO completeTask);
}

View File

@ -0,0 +1,93 @@
package org.dromara.common.core.utils;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.ObjectUtil;
import io.github.linpeilie.Converter;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import java.util.List;
import java.util.Map;
/**
* Mapstruct 工具类
* <p>参考文档<a href="https://mapstruct.plus/introduction/quick-start.html">mapstruct-plus</a></p>
*
*
* @author Michelle.Chung
*/
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class MapstructUtils {
private final static Converter CONVERTER = SpringUtils.getBean(Converter.class);
/**
* T 类型对象转换为 desc 类型的对象并返回
*
* @param source 数据来源实体
* @param desc 描述对象 转换后的对象
* @return desc
*/
public static <T, V> V convert(T source, Class<V> desc) {
if (ObjectUtil.isNull(source)) {
return null;
}
if (ObjectUtil.isNull(desc)) {
return null;
}
return CONVERTER.convert(source, desc);
}
/**
* T 类型对象按照配置的映射字段规则 desc 类型的对象赋值并返回 desc 对象
*
* @param source 数据来源实体
* @param desc 转换后的对象
* @return desc
*/
public static <T, V> V convert(T source, V desc) {
if (ObjectUtil.isNull(source)) {
return null;
}
if (ObjectUtil.isNull(desc)) {
return null;
}
return CONVERTER.convert(source, desc);
}
/**
* T 类型的集合转换为 desc 类型的集合并返回
*
* @param sourceList 数据来源实体列表
* @param desc 描述对象 转换后的对象
* @return desc
*/
public static <T, V> List<V> convert(List<T> sourceList, Class<V> desc) {
if (ObjectUtil.isNull(sourceList)) {
return null;
}
if (CollUtil.isEmpty(sourceList)) {
return CollUtil.newArrayList();
}
return CONVERTER.convert(sourceList, desc);
}
/**
* Map 转换为 beanClass 类型的集合并返回
*
* @param map 数据来源
* @param beanClass bean类
* @return bean对象
*/
public static <T> T convert(Map<String, Object> map, Class<T> beanClass) {
if (MapUtil.isEmpty(map)) {
return null;
}
if (ObjectUtil.isNull(beanClass)) {
return null;
}
return CONVERTER.convert(map, beanClass);
}
}

View File

@ -0,0 +1,60 @@
package org.dromara.common.core.utils;
import cn.hutool.core.util.ObjectUtil;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import java.util.function.Function;
/**
* 对象工具类
*
* @author 秋辞未寒
*/
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class ObjectUtils extends ObjectUtil {
/**
* 如果对象不为空则获取对象中的某个字段 ObjectUtils.notNullGetter(user, User::getName);
*
* @param obj 对象
* @param func 获取方法
* @return 对象字段
*/
public static <T, E> E notNullGetter(T obj, Function<T, E> func) {
if (isNotNull(obj) && isNotNull(func)) {
return func.apply(obj);
}
return null;
}
/**
* 如果对象不为空则获取对象中的某个字段否则返回默认值
*
* @param obj 对象
* @param func 获取方法
* @param defaultValue 默认值
* @return 对象字段
*/
public static <T, E> E notNullGetter(T obj, Function<T, E> func, E defaultValue) {
if (isNotNull(obj) && isNotNull(func)) {
return func.apply(obj);
}
return defaultValue;
}
/**
* 如果值不为空则返回值否则返回默认值
*
* @param obj 对象
* @param defaultValue 默认值
* @return 对象字段
*/
public static <T> T notNull(T obj, T defaultValue) {
if (isNotNull(obj)) {
return obj;
}
return defaultValue;
}
}

View File

@ -0,0 +1,27 @@
package org.dromara.common.core.utils;
import org.springframework.stereotype.Component;
@Component
public final class PddUtil {
private static String accessToken;
public static String getClientId(){
return "203c5a7ba8bd4b8488d5e26f93052642";
}
public static String getClientSecret(){
return "892ffaa86e12b7a3d8d2942b669d9aa520ad8179";
}
public static String getToken(){
return accessToken;
}
public static String setToken(String token){
accessToken = token;
return accessToken;
}
}

View File

@ -0,0 +1,63 @@
package org.dromara.common.core.utils;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.*;
/**
* 线程相关工具类.
*
* @author ruoyi
*/
@Slf4j
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class Threads {
/**
* 停止线程池
* 先使用shutdown, 停止接收新任务并尝试完成所有已存在任务.
* 如果超时, 则调用shutdownNow, 取消在workQueue中Pending的任务,并中断所有阻塞函数.
* 如果仍然超時則強制退出.
* 另对在shutdown时线程本身被调用中断做了处理.
*/
public static void shutdownAndAwaitTermination(ExecutorService pool) {
if (pool != null && !pool.isShutdown()) {
pool.shutdown();
try {
if (!pool.awaitTermination(120, TimeUnit.SECONDS)) {
pool.shutdownNow();
if (!pool.awaitTermination(120, TimeUnit.SECONDS)) {
log.info("Pool did not terminate");
}
}
} catch (InterruptedException ie) {
pool.shutdownNow();
Thread.currentThread().interrupt();
}
}
}
/**
* 打印线程异常信息
*/
public static void printException(Runnable r, Throwable t) {
if (t == null && r instanceof Future<?>) {
try {
Future<?> future = (Future<?>) r;
if (future.isDone()) {
future.get();
}
} catch (CancellationException ce) {
t = ce;
} catch (ExecutionException ee) {
t = ee.getCause();
} catch (InterruptedException ie) {
Thread.currentThread().interrupt();
}
}
if (t != null) {
log.error(t.getMessage(), t);
}
}
}

View File

@ -0,0 +1,103 @@
package org.dromara.common.core.utils.file;
import cn.hutool.core.io.FileUtil;
import jakarta.servlet.http.HttpServletResponse;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import java.io.*;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
/**
* 文件处理工具类
*
* @author Lion Li
*/
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class FileUtils extends FileUtil {
/**
* 下载文件名重新编码
*
* @param response 响应对象
* @param realFileName 真实文件名
*/
public static void setAttachmentResponseHeader(HttpServletResponse response, String realFileName) {
String percentEncodedFileName = percentEncode(realFileName);
String contentDispositionValue = "attachment; filename=%s;filename*=utf-8''%s".formatted(percentEncodedFileName, percentEncodedFileName);
response.addHeader("Access-Control-Expose-Headers", "Content-Disposition,download-filename");
response.setHeader("Content-disposition", contentDispositionValue);
response.setHeader("download-filename", percentEncodedFileName);
}
/**
* 百分号编码工具方法
*
* @param s 需要百分号编码的字符串
* @return 百分号编码后的字符串
*/
public static String percentEncode(String s) {
String encode = URLEncoder.encode(s, StandardCharsets.UTF_8);
return encode.replaceAll("\\+", "%20");
}
/**
* 从服务器下载文件到临时文件夹
*/
public static String percentDecode(String fileName,String suffix) {
try {
// 1. 定义目标URL
URL url = new URL("http://111.229.25.150:8001/"+fileName+"."+suffix);
// 2. 打开连接并获取输入流
URLConnection connection = url.openConnection();
try (InputStream inputStream = connection.getInputStream()) {
// 3. 创建临时文件或指定本地路径
File tempFile = File.createTempFile("downloaded-", "."+suffix);
tempFile.deleteOnExit(); // 程序退出时删除临时文件
// 4. 将输入流写入临时文件
try (FileOutputStream outputStream = new FileOutputStream(tempFile)) {
byte[] buffer = new byte[4096];
int bytesRead;
while ((bytesRead = inputStream.read(buffer)) != -1) {
outputStream.write(buffer, 0, bytesRead);
}
}
// 5. 使用文件对象
System.out.println("文件已保存至: " + tempFile.getAbsolutePath());
return tempFile.getAbsolutePath();
}
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
public static void writeFile(String filePath, String content) {
try (FileWriter fileWriter = new FileWriter(filePath)) {
fileWriter.write(content);
} catch (IOException e) {
throw new RuntimeException("Failed to write file", e);
}
}
public static boolean isFileExists(String filePath) {
try {
Path path = Paths.get(filePath);
return Files.exists(path);
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
}

View File

@ -0,0 +1,67 @@
package org.dromara.common.core.utils.ip;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.resource.ClassPathResource;
import cn.hutool.core.util.ObjectUtil;
import org.dromara.common.core.exception.ServiceException;
import org.dromara.common.core.utils.file.FileUtils;
import lombok.extern.slf4j.Slf4j;
import org.lionsoul.ip2region.xdb.Searcher;
import java.io.File;
/**
* 根据ip地址定位工具类离线方式
* 参考地址<a href="https://gitee.com/lionsoul/ip2region/tree/master/binding/java">集成 ip2region 实现离线IP地址定位库</a>
*
* @author lishuyan
*/
@Slf4j
public class RegionUtils {
private static final Searcher SEARCHER;
static {
String fileName = "/ip2region.xdb";
File existFile = FileUtils.file(FileUtil.getTmpDir() + FileUtil.FILE_SEPARATOR + fileName);
if (!FileUtils.exist(existFile)) {
ClassPathResource fileStream = new ClassPathResource(fileName);
if (ObjectUtil.isEmpty(fileStream.getStream())) {
throw new ServiceException("RegionUtils初始化失败原因IP地址库数据不存在");
}
FileUtils.writeFromStream(fileStream.getStream(), existFile);
}
String dbPath = existFile.getPath();
// 1 dbPath 加载整个 xdb 到内存
byte[] cBuff;
try {
cBuff = Searcher.loadContentFromFile(dbPath);
} catch (Exception e) {
throw new ServiceException("RegionUtils初始化失败原因从ip2region.xdb文件加载内容失败" + e.getMessage());
}
// 2使用上述的 cBuff 创建一个完全基于内存的查询对象
try {
SEARCHER = Searcher.newWithBuffer(cBuff);
} catch (Exception e) {
throw new ServiceException("RegionUtils初始化失败原因" + e.getMessage());
}
}
/**
* 根据IP地址离线获取城市
*/
public static String getCityInfo(String ip) {
try {
ip = ip.trim();
// 3执行查询
String region = SEARCHER.search(ip);
return region.replace("0|", "").replace("|0", "");
} catch (Exception e) {
log.error("IP地址离线获取城市异常 {}", ip);
return "未知";
}
}
}

View File

@ -0,0 +1,56 @@
package org.dromara.common.core.utils.reflect;
import cn.hutool.core.util.ReflectUtil;
import org.dromara.common.core.utils.StringUtils;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import java.lang.reflect.Method;
/**
* 反射工具类. 提供调用getter/setter方法, 访问私有变量, 调用私有方法, 获取泛型类型Class, 被AOP过的真实类等工具函数.
*
* @author Lion Li
*/
@SuppressWarnings("rawtypes")
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class ReflectUtils extends ReflectUtil {
private static final String SETTER_PREFIX = "set";
private static final String GETTER_PREFIX = "get";
/**
* 调用Getter方法.
* 支持多级对象名.对象名.方法
*/
@SuppressWarnings("unchecked")
public static <E> E invokeGetter(Object obj, String propertyName) {
Object object = obj;
for (String name : StringUtils.split(propertyName, ".")) {
String getterMethodName = GETTER_PREFIX + StringUtils.capitalize(name);
object = invoke(object, getterMethodName);
}
return (E) object;
}
/**
* 调用Setter方法, 仅匹配方法名
* 支持多级对象名.对象名.方法
*/
public static <E> void invokeSetter(Object obj, String propertyName, E value) {
Object object = obj;
String[] names = StringUtils.split(propertyName, ".");
for (int i = 0; i < names.length; i++) {
if (i < names.length - 1) {
String getterMethodName = GETTER_PREFIX + StringUtils.capitalize(names[i]);
object = invoke(object, getterMethodName);
} else {
String setterMethodName = SETTER_PREFIX + StringUtils.capitalize(names[i]);
Method method = getMethodByName(object.getClass(), setterMethodName);
invoke(object, method, value);
}
}
}
}

View File

@ -0,0 +1,56 @@
package org.dromara.common.core.utils.sql;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import org.dromara.common.core.utils.StringUtils;
/**
* sql操作工具类
*
* @author ruoyi
*/
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class SqlUtil {
/**
* 定义常用的 sql关键字
*/
public static String SQL_REGEX = "\u000B|and |extractvalue|updatexml|sleep|exec |insert |select |delete |update |drop |count |chr |mid |master |truncate |char |declare |or |union |like |+|/*|user()";
/**
* 仅支持字母数字下划线空格逗号小数点支持多个字段排序
*/
public static final String SQL_PATTERN = "[a-zA-Z0-9_\\ \\,\\.]+";
/**
* 检查字符防止注入绕过
*/
public static String escapeOrderBySql(String value) {
if (StringUtils.isNotEmpty(value) && !isValidOrderBySql(value)) {
throw new IllegalArgumentException("参数不符合规范,不能进行查询");
}
return value;
}
/**
* 验证 order by 语法是否符合规范
*/
public static boolean isValidOrderBySql(String value) {
return value.matches(SQL_PATTERN);
}
/**
* SQL关键字检查
*/
public static void filterKeyword(String value) {
if (StringUtils.isEmpty(value)) {
return;
}
String[] sqlKeywords = StringUtils.split(SQL_REGEX, "\\|");
for (String sqlKeyword : sqlKeywords) {
if (StringUtils.indexOfIgnoreCase(value, sqlKeyword) > -1) {
throw new IllegalArgumentException("参数存在SQL注入风险");
}
}
}
}

View File

@ -0,0 +1,9 @@
package org.dromara.common.core.validate;
/**
* 校验分组 add
*
* @author Lion Li
*/
public interface AddGroup {
}

View File

@ -0,0 +1,48 @@
package org.dromara.common.core.validate.enumd;
import jakarta.validation.Constraint;
import jakarta.validation.Payload;
import java.lang.annotation.*;
import static java.lang.annotation.ElementType.*;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
/**
* 自定义枚举校验
*
* @author 秋辞未寒
* @date 2024-12-09
*/
@Documented
@Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE})
@Retention(RUNTIME)
@Repeatable(EnumPattern.List.class) // 允许在同一元素上多次使用该注解
@Constraint(validatedBy = {EnumPatternValidator.class})
public @interface EnumPattern {
/**
* 需要校验的枚举类型
*/
Class<? extends Enum<?>> type();
/**
* 枚举类型校验值字段名称
* 需确保该字段实现了 getter 方法
*/
String fieldName();
String message() default "输入值不在枚举范围内";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
@Documented
@Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE})
@Retention(RUNTIME)
@interface List {
EnumPattern[] value();
}
}

View File

@ -0,0 +1,26 @@
package org.dromara.common.core.xss;
import jakarta.validation.Constraint;
import jakarta.validation.Payload;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 自定义xss校验注解
*
* @author Lion Li
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(value = {ElementType.METHOD, ElementType.FIELD, ElementType.CONSTRUCTOR, ElementType.PARAMETER})
@Constraint(validatedBy = {XssValidator.class})
public @interface Xss {
String message() default "不允许任何脚本运行";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}

View File

@ -0,0 +1,41 @@
<?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>
<groupId>org.dromara</groupId>
<artifactId>ruoyi-common</artifactId>
<version>${revision}</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>ruoyi-common-doc</artifactId>
<description>
ruoyi-common-doc 系统接口
</description>
<dependencies>
<dependency>
<groupId>org.dromara</groupId>
<artifactId>ruoyi-common-core</artifactId>
</dependency>
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webmvc-api</artifactId>
</dependency>
<dependency>
<groupId>com.github.therapi</groupId>
<artifactId>therapi-runtime-javadoc</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.module</groupId>
<artifactId>jackson-module-kotlin</artifactId>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,54 @@
<?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>
<groupId>org.dromara</groupId>
<artifactId>ruoyi-common</artifactId>
<version>${revision}</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>ruoyi-common-encrypt</artifactId>
<description>
ruoyi-common-encrypt 数据加解密模块
</description>
<dependencies>
<dependency>
<groupId>org.dromara</groupId>
<artifactId>ruoyi-common-core</artifactId>
</dependency>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk15to18</artifactId>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-crypto</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-spring-boot3-starter</artifactId>
<optional>true</optional>
<exclusions>
<exclusion>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,41 @@
package org.dromara.common.encrypt.core;
import org.dromara.common.encrypt.enumd.AlgorithmType;
import org.dromara.common.encrypt.enumd.EncodeType;
import lombok.Data;
/**
* 加密上下文 用于encryptor传递必要的参数
*
* @author 老马
* @version 4.6.0
*/
@Data
public class EncryptContext {
/**
* 默认算法
*/
private AlgorithmType algorithm;
/**
* 安全秘钥
*/
private String password;
/**
* 公钥
*/
private String publicKey;
/**
* 私钥
*/
private String privateKey;
/**
* 编码方式base64/hex
*/
private EncodeType encode;
}

View File

@ -0,0 +1,159 @@
package org.dromara.common.encrypt.core;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ReflectUtil;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.io.Resources;
import org.dromara.common.core.utils.ObjectUtils;
import org.dromara.common.core.utils.StringUtils;
import org.dromara.common.encrypt.annotation.EncryptField;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.core.type.ClassMetadata;
import org.springframework.core.type.classreading.CachingMetadataReaderFactory;
import org.springframework.util.ClassUtils;
import java.lang.reflect.Field;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
/**
* 加密管理类
*
* @author 老马
* @version 4.6.0
*/
@Slf4j
@NoArgsConstructor
public class EncryptorManager {
/**
* 缓存加密器
*/
Map<Integer, IEncryptor> encryptorMap = new ConcurrentHashMap<>();
/**
* 类加密字段缓存
*/
Map<Class<?>, Set<Field>> fieldCache = new ConcurrentHashMap<>();
/**
* 构造方法传入类加密字段缓存
*
* @param typeAliasesPackage 实体类包
*/
public EncryptorManager(String typeAliasesPackage) {
scanEncryptClasses(typeAliasesPackage);
}
/**
* 获取类加密字段缓存
*/
public Set<Field> getFieldCache(Class<?> sourceClazz) {
return ObjectUtils.notNullGetter(fieldCache, f -> f.get(sourceClazz));
}
/**
* 注册加密执行者到缓存
*
* @param encryptContext 加密执行者需要的相关配置参数
*/
public IEncryptor registAndGetEncryptor(EncryptContext encryptContext) {
int key = encryptContext.hashCode();
if (encryptorMap.containsKey(key)) {
return encryptorMap.get(key);
}
IEncryptor encryptor = ReflectUtil.newInstance(encryptContext.getAlgorithm().getClazz(), encryptContext);
encryptorMap.put(key, encryptor);
return encryptor;
}
/**
* 移除缓存中的加密执行者
*
* @param encryptContext 加密执行者需要的相关配置参数
*/
public void removeEncryptor(EncryptContext encryptContext) {
this.encryptorMap.remove(encryptContext.hashCode());
}
/**
* 根据配置进行加密会进行本地缓存对应的算法和对应的秘钥信息
*
* @param value 待加密的值
* @param encryptContext 加密相关的配置信息
*/
public String encrypt(String value, EncryptContext encryptContext) {
IEncryptor encryptor = this.registAndGetEncryptor(encryptContext);
return encryptor.encrypt(value, encryptContext.getEncode());
}
/**
* 根据配置进行解密
*
* @param value 待解密的值
* @param encryptContext 加密相关的配置信息
*/
public String decrypt(String value, EncryptContext encryptContext) {
IEncryptor encryptor = this.registAndGetEncryptor(encryptContext);
return encryptor.decrypt(value);
}
/**
* 通过 typeAliasesPackage 设置的扫描包 扫描缓存实体
*/
private void scanEncryptClasses(String typeAliasesPackage) {
PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
CachingMetadataReaderFactory factory = new CachingMetadataReaderFactory();
String[] packagePatternArray = StringUtils.splitPreserveAllTokens(typeAliasesPackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
String classpath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX;
try {
for (String packagePattern : packagePatternArray) {
String path = ClassUtils.convertClassNameToResourcePath(packagePattern);
Resource[] resources = resolver.getResources(classpath + path + "/*.class");
for (Resource resource : resources) {
ClassMetadata classMetadata = factory.getMetadataReader(resource).getClassMetadata();
Class<?> clazz = Resources.classForName(classMetadata.getClassName());
Set<Field> encryptFieldSet = getEncryptFieldSetFromClazz(clazz);
if (CollUtil.isNotEmpty(encryptFieldSet)) {
fieldCache.put(clazz, encryptFieldSet);
}
}
}
} catch (Exception e) {
log.error("初始化数据安全缓存时出错:{}", e.getMessage());
}
}
/**
* 获得一个类的加密字段集合
*/
private Set<Field> getEncryptFieldSetFromClazz(Class<?> clazz) {
Set<Field> fieldSet = new HashSet<>();
// 判断clazz如果是接口,内部类,匿名类就直接返回
if (clazz.isInterface() || clazz.isMemberClass() || clazz.isAnonymousClass()) {
return fieldSet;
}
while (clazz != null) {
Field[] fields = clazz.getDeclaredFields();
fieldSet.addAll(Arrays.asList(fields));
clazz = clazz.getSuperclass();
}
fieldSet = fieldSet.stream().filter(field ->
field.isAnnotationPresent(EncryptField.class) && field.getType() == String.class)
.collect(Collectors.toSet());
for (Field field : fieldSet) {
field.setAccessible(true);
}
return fieldSet;
}
}

View File

@ -0,0 +1,55 @@
package org.dromara.common.encrypt.core.encryptor;
import org.dromara.common.encrypt.core.EncryptContext;
import org.dromara.common.encrypt.enumd.AlgorithmType;
import org.dromara.common.encrypt.enumd.EncodeType;
import org.dromara.common.encrypt.utils.EncryptUtils;
/**
* AES算法实现
*
* @author 老马
* @version 4.6.0
*/
public class AesEncryptor extends AbstractEncryptor {
private final EncryptContext context;
public AesEncryptor(EncryptContext context) {
super(context);
this.context = context;
}
/**
* 获得当前算法
*/
@Override
public AlgorithmType algorithm() {
return AlgorithmType.AES;
}
/**
* 加密
*
* @param value 待加密字符串
* @param encodeType 加密后的编码格式
*/
@Override
public String encrypt(String value, EncodeType encodeType) {
if (encodeType == EncodeType.HEX) {
return EncryptUtils.encryptByAesHex(value, context.getPassword());
} else {
return EncryptUtils.encryptByAes(value, context.getPassword());
}
}
/**
* 解密
*
* @param value 待加密字符串
*/
@Override
public String decrypt(String value) {
return EncryptUtils.decryptByAes(value, context.getPassword());
}
}

View File

@ -0,0 +1,48 @@
package org.dromara.common.encrypt.core.encryptor;
import org.dromara.common.encrypt.core.EncryptContext;
import org.dromara.common.encrypt.enumd.AlgorithmType;
import org.dromara.common.encrypt.enumd.EncodeType;
import org.dromara.common.encrypt.utils.EncryptUtils;
/**
* Base64算法实现
*
* @author 老马
* @version 4.6.0
*/
public class Base64Encryptor extends AbstractEncryptor {
public Base64Encryptor(EncryptContext context) {
super(context);
}
/**
* 获得当前算法
*/
@Override
public AlgorithmType algorithm() {
return AlgorithmType.BASE64;
}
/**
* 加密
*
* @param value 待加密字符串
* @param encodeType 加密后的编码格式
*/
@Override
public String encrypt(String value, EncodeType encodeType) {
return EncryptUtils.encryptByBase64(value);
}
/**
* 解密
*
* @param value 待加密字符串
*/
@Override
public String decrypt(String value) {
return EncryptUtils.decryptByBase64(value);
}
}

View File

@ -0,0 +1,55 @@
package org.dromara.common.encrypt.core.encryptor;
import org.dromara.common.encrypt.core.EncryptContext;
import org.dromara.common.encrypt.enumd.AlgorithmType;
import org.dromara.common.encrypt.enumd.EncodeType;
import org.dromara.common.encrypt.utils.EncryptUtils;
/**
* sm4算法实现
*
* @author 老马
* @version 4.6.0
*/
public class Sm4Encryptor extends AbstractEncryptor {
private final EncryptContext context;
public Sm4Encryptor(EncryptContext context) {
super(context);
this.context = context;
}
/**
* 获得当前算法
*/
@Override
public AlgorithmType algorithm() {
return AlgorithmType.SM4;
}
/**
* 加密
*
* @param value 待加密字符串
* @param encodeType 加密后的编码格式
*/
@Override
public String encrypt(String value, EncodeType encodeType) {
if (encodeType == EncodeType.HEX) {
return EncryptUtils.encryptBySm4Hex(value, context.getPassword());
} else {
return EncryptUtils.encryptBySm4(value, context.getPassword());
}
}
/**
* 解密
*
* @param value 待加密字符串
*/
@Override
public String decrypt(String value) {
return EncryptUtils.decryptBySm4(value, context.getPassword());
}
}

View File

@ -0,0 +1,110 @@
package org.dromara.common.encrypt.filter;
import cn.hutool.core.util.ObjectUtil;
import jakarta.servlet.*;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.dromara.common.core.constant.HttpStatus;
import org.dromara.common.core.exception.ServiceException;
import org.dromara.common.core.utils.SpringUtils;
import org.dromara.common.core.utils.StringUtils;
import org.dromara.common.encrypt.annotation.ApiEncrypt;
import org.dromara.common.encrypt.properties.ApiDecryptProperties;
import org.springframework.http.HttpMethod;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerExceptionResolver;
import org.springframework.web.servlet.HandlerExecutionChain;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
import java.io.IOException;
/**
* Crypto 过滤器
*
* @author wdhcr
*/
public class CryptoFilter implements Filter {
private final ApiDecryptProperties properties;
public CryptoFilter(ApiDecryptProperties properties) {
this.properties = properties;
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
HttpServletRequest servletRequest = (HttpServletRequest) request;
HttpServletResponse servletResponse = (HttpServletResponse) response;
// 获取加密注解
ApiEncrypt apiEncrypt = this.getApiEncryptAnnotation(servletRequest);
boolean responseFlag = apiEncrypt != null && apiEncrypt.response();
ServletRequest requestWrapper = null;
ServletResponse responseWrapper = null;
EncryptResponseBodyWrapper responseBodyWrapper = null;
// 是否为 put 或者 post 请求
if (HttpMethod.PUT.matches(servletRequest.getMethod()) || HttpMethod.POST.matches(servletRequest.getMethod())) {
// 是否存在加密标头
String headerValue = servletRequest.getHeader(properties.getHeaderFlag());
if (StringUtils.isNotBlank(headerValue)) {
// 请求解密
requestWrapper = new DecryptRequestBodyWrapper(servletRequest, properties.getPrivateKey(), properties.getHeaderFlag());
} else {
// 是否有注解有就报错没有放行
if (ObjectUtil.isNotNull(apiEncrypt)) {
HandlerExceptionResolver exceptionResolver = SpringUtils.getBean("handlerExceptionResolver", HandlerExceptionResolver.class);
exceptionResolver.resolveException(
servletRequest, servletResponse, null,
new ServiceException("没有访问权限,请联系管理员授权", HttpStatus.FORBIDDEN));
return;
}
}
}
// 判断是否响应加密
if (responseFlag) {
responseBodyWrapper = new EncryptResponseBodyWrapper(servletResponse);
responseWrapper = responseBodyWrapper;
}
chain.doFilter(
ObjectUtil.defaultIfNull(requestWrapper, request),
ObjectUtil.defaultIfNull(responseWrapper, response));
if (responseFlag) {
servletResponse.reset();
// 对原始内容加密
String encryptContent = responseBodyWrapper.getEncryptContent(
servletResponse, properties.getPublicKey(), properties.getHeaderFlag());
// 对加密后的内容写出
servletResponse.getWriter().write(encryptContent);
}
}
/**
* 获取 ApiEncrypt 注解
*/
private ApiEncrypt getApiEncryptAnnotation(HttpServletRequest servletRequest) {
RequestMappingHandlerMapping handlerMapping = SpringUtils.getBean("requestMappingHandlerMapping", RequestMappingHandlerMapping.class);
// 获取注解
try {
HandlerExecutionChain mappingHandler = handlerMapping.getHandler(servletRequest);
if (ObjectUtil.isNotNull(mappingHandler)) {
Object handler = mappingHandler.getHandler();
if (ObjectUtil.isNotNull(handler)) {
// 从handler获取注解
if (handler instanceof HandlerMethod handlerMethod) {
return handlerMethod.getMethodAnnotation(ApiEncrypt.class);
}
}
}
} catch (Exception e) {
return null;
}
return null;
}
@Override
public void destroy() {
}
}

View File

@ -0,0 +1,3 @@
org.dromara.common.encrypt.config.EncryptorAutoConfiguration
org.dromara.common.encrypt.config.ApiDecryptAutoConfiguration

View File

@ -0,0 +1,52 @@
package org.dromara.common.excel.convert;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.util.ObjectUtil;
import com.alibaba.excel.converters.Converter;
import com.alibaba.excel.enums.CellDataTypeEnum;
import com.alibaba.excel.metadata.GlobalConfiguration;
import com.alibaba.excel.metadata.data.ReadCellData;
import com.alibaba.excel.metadata.data.WriteCellData;
import com.alibaba.excel.metadata.property.ExcelContentProperty;
import lombok.extern.slf4j.Slf4j;
import java.math.BigDecimal;
/**
* 大数值转换
* Excel 数值长度位15位 大于15位的数值转换位字符串
*
* @author Lion Li
*/
@Slf4j
public class ExcelBigNumberConvert implements Converter<Long> {
@Override
public Class<Long> supportJavaTypeKey() {
return Long.class;
}
@Override
public CellDataTypeEnum supportExcelTypeKey() {
return CellDataTypeEnum.STRING;
}
@Override
public Long convertToJavaData(ReadCellData<?> cellData, ExcelContentProperty contentProperty, GlobalConfiguration globalConfiguration) {
return Convert.toLong(cellData.getData());
}
@Override
public WriteCellData<Object> convertToExcelData(Long object, ExcelContentProperty contentProperty, GlobalConfiguration globalConfiguration) {
if (ObjectUtil.isNotNull(object)) {
String str = Convert.toStr(object);
if (str.length() > 15) {
return new WriteCellData<>(str);
}
}
WriteCellData<Object> cellData = new WriteCellData<>(new BigDecimal(object));
cellData.setType(CellDataTypeEnum.NUMBER);
return cellData;
}
}

View File

@ -0,0 +1,73 @@
package org.dromara.common.excel.core;
import cn.hutool.core.util.StrUtil;
import lombok.Setter;
import java.util.ArrayList;
import java.util.List;
/**
* 默认excel返回对象
*
* @author Yjoioooo
* @author Lion Li
*/
public class DefaultExcelResult<T> implements ExcelResult<T> {
/**
* 数据对象list
*/
@Setter
private List<T> list;
/**
* 错误信息列表
*/
@Setter
private List<String> errorList;
public DefaultExcelResult() {
this.list = new ArrayList<>();
this.errorList = new ArrayList<>();
}
public DefaultExcelResult(List<T> list, List<String> errorList) {
this.list = list;
this.errorList = errorList;
}
public DefaultExcelResult(ExcelResult<T> excelResult) {
this.list = excelResult.getList();
this.errorList = excelResult.getErrorList();
}
@Override
public List<T> getList() {
return list;
}
@Override
public List<String> getErrorList() {
return errorList;
}
/**
* 获取导入回执
*
* @return 导入回执
*/
@Override
public String getAnalysis() {
int successCount = list.size();
int errorCount = errorList.size();
if (successCount == 0) {
return "读取失败,未解析到数据";
} else {
if (errorCount == 0) {
return StrUtil.format("恭喜您,全部读取成功!共{}条", successCount);
} else {
return "";
}
}
}
}

View File

@ -0,0 +1,149 @@
package org.dromara.common.excel.core;
import cn.hutool.core.util.StrUtil;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.dromara.common.core.exception.ServiceException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;
/**
* <h1>Excel下拉可选项</h1>
* 注意为确保下拉框解析正确传值务必使用createOptionValue()做为值的拼接
*
* @author Emil.Zhang
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
@SuppressWarnings("unused")
public class DropDownOptions {
/**
* 一级下拉所在列index从0开始算
*/
private int index = 0;
/**
* 二级下拉所在的index从0开始算不能与一级相同
*/
private int nextIndex = 0;
/**
* 一级下拉所包含的数据
*/
private List<String> options = new ArrayList<>();
/**
* 二级下拉所包含的数据Map
* <p>以每一个一级选项值为Key每个一级选项对应的二级数据为Value</p>
*/
private Map<String, List<String>> nextOptions = new HashMap<>();
/**
* 分隔符
*/
private static final String DELIMITER = "_";
/**
* 创建只有一级的下拉选
*/
public DropDownOptions(int index, List<String> options) {
this.index = index;
this.options = options;
}
/**
* <h2>创建每个选项可选值</h2>
* <p>注意不能以数字特殊符号开头选项中不可以包含任何运算符号</p>
*
* @param vars 可选值内包含的参数
* @return 合规的可选值
*/
public static String createOptionValue(Object... vars) {
StringBuilder stringBuffer = new StringBuilder();
String regex = "^[\\S\\d\\u4e00-\\u9fa5]+$";
for (int i = 0; i < vars.length; i++) {
String var = StrUtil.trimToEmpty(String.valueOf(vars[i]));
if (!var.matches(regex)) {
throw new ServiceException("选项数据不符合规则,仅允许使用中英文字符以及数字");
}
stringBuffer.append(var);
if (i < vars.length - 1) {
// 直至最后一个前都以_作为切割线
stringBuffer.append(DELIMITER);
}
}
if (stringBuffer.toString().matches("^\\d_*$")) {
throw new ServiceException("禁止以数字开头");
}
return stringBuffer.toString();
}
/**
* 将处理后合理的可选值解析为原始的参数
*
* @param option 经过处理后的合理的可选项
* @return 原始的参数
*/
public static List<String> analyzeOptionValue(String option) {
return StrUtil.split(option, DELIMITER, true, true);
}
/**
* 创建级联下拉选项
*
* @param parentList 父实体可选项原始数据
* @param parentIndex 父下拉选位置
* @param sonList 子实体可选项原始数据
* @param sonIndex 子下拉选位置
* @param parentHowToGetIdFunction 父类如何获取唯一标识
* @param sonHowToGetParentIdFunction 子类如何获取父类的唯一标识
* @param howToBuildEveryOption 如何生成下拉选内容
* @return 级联下拉选项
*/
public static <T> DropDownOptions buildLinkedOptions(List<T> parentList,
int parentIndex,
List<T> sonList,
int sonIndex,
Function<T, Number> parentHowToGetIdFunction,
Function<T, Number> sonHowToGetParentIdFunction,
Function<T, String> howToBuildEveryOption) {
DropDownOptions parentLinkSonOptions = new DropDownOptions();
// 先创建父类的下拉
parentLinkSonOptions.setIndex(parentIndex);
parentLinkSonOptions.setOptions(
parentList.stream()
.map(howToBuildEveryOption)
.collect(Collectors.toList())
);
// 提取父-子级联下拉
Map<String, List<String>> sonOptions = new HashMap<>();
// 父级依据自己的ID分组
Map<Number, List<T>> parentGroupByIdMap =
parentList.stream().collect(Collectors.groupingBy(parentHowToGetIdFunction));
// 遍历每个子集提取到Map中
sonList.forEach(everySon -> {
if (parentGroupByIdMap.containsKey(sonHowToGetParentIdFunction.apply(everySon))) {
// 找到对应的上级
T parentObj = parentGroupByIdMap.get(sonHowToGetParentIdFunction.apply(everySon)).get(0);
// 提取名称和ID作为Key
String key = howToBuildEveryOption.apply(parentObj);
// Key对应的Value
List<String> thisParentSonOptionList;
if (sonOptions.containsKey(key)) {
thisParentSonOptionList = sonOptions.get(key);
} else {
thisParentSonOptionList = new ArrayList<>();
sonOptions.put(key, thisParentSonOptionList);
}
// 往Value中添加当前子集选项
thisParentSonOptionList.add(howToBuildEveryOption.apply(everySon));
}
});
parentLinkSonOptions.setNextIndex(sonIndex);
parentLinkSonOptions.setNextOptions(sonOptions);
return parentLinkSonOptions;
}
}

View File

@ -0,0 +1,134 @@
package org.dromara.zhishu.handler;
import com.alibaba.excel.write.handler.CellWriteHandler;
import com.alibaba.excel.write.handler.context.CellWriteHandlerContext;
import lombok.extern.slf4j.Slf4j;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.usermodel.XSSFClientAnchor;
import org.apache.poi.xssf.usermodel.XSSFRichTextString;
import java.util.HashMap;
import java.util.Map;
@Slf4j
public class HeadCommentWriteHandler implements CellWriteHandler {
private final Map<Integer, CommentConfig> commentConfigMap = new HashMap<>();
// 缓存样式避免重复创建
private CellStyle cachedHeadStyle;
public HeadCommentWriteHandler() {
commentConfigMap.put(0, new CommentConfig("\n必填项\n\n可填写13位ISBN\n\n系统会根据ISBN进行图书匹配和上传", 2, 7));
commentConfigMap.put(1, new CommentConfig("\n商品ID\n\n更新价格、库存、上下架状态时选填,若空白则根据ISBN查询商品进行修改", 2, 6));
commentConfigMap.put(2, new CommentConfig("\n商品编码\n\n更新价格、库存、上下架状态时选填若空白则优先根据商品ID查询在根据ISBN查询", 2, 6));
commentConfigMap.put(3, new CommentConfig("\n可空白。\n\n商品标题中的书名优先使用表格中的书名。如果表格书名空白则商品标题中的书名采用系统数据库中的书名", 3, 7));
commentConfigMap.put(4, new CommentConfig("\n库存数量\n\n修改库存必填,填写的库存数量就是平台上的实际库存数量", 2, 6));
commentConfigMap.put(5, new CommentConfig("\n创建时间\n\n导出项平台的商品创建时间", 2, 5));
commentConfigMap.put(6, new CommentConfig("\n价格\n\n注意这个价格是拼单价的基础价在这个价格的基础上根据店铺的价格模板进行计算拼单价。\n\n单买价为计算后的价格加1元", 3, 7));
commentConfigMap.put(7, new CommentConfig("\n规格编码\n\n拼多多店铺中的规格编码\n孔夫子店铺中的货号\n若上传时为空优先选择商品编码若已经为空则默认ISBN", 2, 9));
commentConfigMap.put(8, new CommentConfig("\n可空白\n\n设置平台上商品的上下架状态\n\n拼多多发布商品不能选下架状态", 2, 5));
commentConfigMap.put(9, new CommentConfig("\n可空白\n\n填写图片http地址\n\n发布时轮播图使用的图片若为空或无法访问则系统会根据ISBN进行图书匹配获取图片\n多张图片链接用逗号进行分割", 3, 5));
}
@Override
public void afterCellDispose(CellWriteHandlerContext context) {
if (context.getRowIndex() != 0) {
return;
}
Cell cell = context.getCell();
if (cell == null) {
return;
}
// 获取或创建表头单元格样式
CellStyle style = getOrCreateHeadStyle(cell.getSheet().getWorkbook());
cell.setCellStyle(style);
// 添加带格式的批注
int columnIndex = cell.getColumnIndex();
CommentConfig config = commentConfigMap.get(columnIndex);
if (config != null) {
addStyledComment(cell, config);
}
}
/**
* 获取或创建表头样式单例模式
*/
private CellStyle getOrCreateHeadStyle(Workbook workbook) {
if (cachedHeadStyle == null) {
Font font = workbook.createFont();
font.setBold(true);
font.setFontHeightInPoints((short) 16);
cachedHeadStyle = workbook.createCellStyle();
cachedHeadStyle.setFont(font);
cachedHeadStyle.setWrapText(true);
cachedHeadStyle.setAlignment(HorizontalAlignment.CENTER);
cachedHeadStyle.setVerticalAlignment(VerticalAlignment.CENTER);
cachedHeadStyle.setBorderTop(BorderStyle.THIN);
cachedHeadStyle.setBorderBottom(BorderStyle.THIN);
cachedHeadStyle.setBorderLeft(BorderStyle.THIN);
cachedHeadStyle.setBorderRight(BorderStyle.THIN);
}
return cachedHeadStyle;
}
/**
* 添加带格式的批注加粗字体大小
*/
private void addStyledComment(Cell cell, CommentConfig config) {
try {
Sheet sheet = cell.getSheet();
Drawing<?> drawing = sheet.createDrawingPatriarch();
int columnIndex = cell.getColumnIndex();
Comment comment = drawing.createCellComment(
new XSSFClientAnchor(0, 0, 0, 0,
(short) columnIndex, 0,
(short) (columnIndex + config.col2), config.row2)
);
// 创建带格式的批注文本
XSSFRichTextString richText = new XSSFRichTextString(config.commentText);
// 获取批注字体加粗大小14号
Font commentFont = cell.getSheet().getWorkbook().createFont();
commentFont.setBold(true);
commentFont.setFontHeightInPoints((short) 14);
// 将整个批注文本应用字体样式
richText.applyFont(commentFont);
// 可选只对第一行标题加粗并放大字体
// int firstLineEnd = config.commentText.indexOf("\n");
// if (firstLineEnd > 0) {
// Font titleFont = cell.getSheet().getWorkbook().createFont();
// titleFont.setBold(true);
// titleFont.setFontHeightInPoints((short) 16);
// richText.applyFont(0, firstLineEnd, titleFont);
// }
comment.setString(richText);
comment.setAuthor("系统");
cell.setCellComment(comment);
log.debug("为第 {} 列添加带格式批注", columnIndex);
} catch (Exception e) {
log.error("添加批注失败", e);
}
}
private static class CommentConfig {
String commentText;
int col2;
int row2;
public CommentConfig(String commentText, int col2, int row2) {
this.commentText = commentText;
this.col2 = col2;
this.row2 = row2;
}
}
}

View File

@ -0,0 +1,146 @@
package org.dromara.common.idempotent.aspectj;
import cn.dev33.satoken.SaManager;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.crypto.SecureUtil;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.dromara.common.core.constant.GlobalConstants;
import org.dromara.common.core.domain.R;
import org.dromara.common.core.exception.ServiceException;
import org.dromara.common.core.utils.MessageUtils;
import org.dromara.common.core.utils.ServletUtils;
import org.dromara.common.core.utils.StringUtils;
import org.dromara.common.idempotent.annotation.RepeatSubmit;
import org.dromara.common.json.utils.JsonUtils;
import org.dromara.common.redis.utils.RedisUtils;
import org.springframework.validation.BindingResult;
import org.springframework.web.multipart.MultipartFile;
import java.time.Duration;
import java.util.Collection;
import java.util.Map;
import java.util.StringJoiner;
/**
* 防止重复提交(参考美团GTIS防重系统)
*
* @author Lion Li
*/
@Aspect
public class RepeatSubmitAspect {
private static final ThreadLocal<String> KEY_CACHE = new ThreadLocal<>();
@Before("@annotation(repeatSubmit)")
public void doBefore(JoinPoint point, RepeatSubmit repeatSubmit) throws Throwable {
// 如果注解不为0 则使用注解数值
long interval = repeatSubmit.timeUnit().toMillis(repeatSubmit.interval());
if (interval < 1000) {
throw new ServiceException("重复提交间隔时间不能小于'1'秒");
}
HttpServletRequest request = ServletUtils.getRequest();
String nowParams = argsArrayToString(point.getArgs());
// 请求地址作为存放cache的key值
String url = request.getRequestURI();
// 唯一值没有消息头则使用请求地址
String submitKey = StringUtils.trimToEmpty(request.getHeader(SaManager.getConfig().getTokenName()));
submitKey = SecureUtil.md5(submitKey + ":" + nowParams);
// 唯一标识指定key + url + 消息头
String cacheRepeatKey = GlobalConstants.REPEAT_SUBMIT_KEY + url + submitKey;
if (RedisUtils.setObjectIfAbsent(cacheRepeatKey, "", Duration.ofMillis(interval))) {
KEY_CACHE.set(cacheRepeatKey);
} else {
String message = repeatSubmit.message();
if (StringUtils.startsWith(message, "{") && StringUtils.endsWith(message, "}")) {
message = MessageUtils.message(StringUtils.substring(message, 1, message.length() - 1));
}
throw new ServiceException(message);
}
}
/**
* 处理完请求后执行
*
* @param joinPoint 切点
*/
@AfterReturning(pointcut = "@annotation(repeatSubmit)", returning = "jsonResult")
public void doAfterReturning(JoinPoint joinPoint, RepeatSubmit repeatSubmit, Object jsonResult) {
if (jsonResult instanceof R<?> r) {
try {
// 成功则不删除redis数据 保证在有效时间内无法重复提交
if (r.getCode() == R.SUCCESS) {
return;
}
RedisUtils.deleteObject(KEY_CACHE.get());
} finally {
KEY_CACHE.remove();
}
}
}
/**
* 拦截异常操作
*
* @param joinPoint 切点
* @param e 异常
*/
@AfterThrowing(value = "@annotation(repeatSubmit)", throwing = "e")
public void doAfterThrowing(JoinPoint joinPoint, RepeatSubmit repeatSubmit, Exception e) {
RedisUtils.deleteObject(KEY_CACHE.get());
KEY_CACHE.remove();
}
/**
* 参数拼装
*/
private String argsArrayToString(Object[] paramsArray) {
StringJoiner params = new StringJoiner(" ");
if (ArrayUtil.isEmpty(paramsArray)) {
return params.toString();
}
for (Object o : paramsArray) {
if (ObjectUtil.isNotNull(o) && !isFilterObject(o)) {
params.add(JsonUtils.toJsonString(o));
}
}
return params.toString();
}
/**
* 判断是否需要过滤的对象
*
* @param o 对象信息
* @return 如果是需要过滤的对象则返回true否则返回false
*/
@SuppressWarnings("rawtypes")
public boolean isFilterObject(final Object o) {
Class<?> clazz = o.getClass();
if (clazz.isArray()) {
return MultipartFile.class.isAssignableFrom(clazz.getComponentType());
} else if (Collection.class.isAssignableFrom(clazz)) {
Collection collection = (Collection) o;
for (Object value : collection) {
return value instanceof MultipartFile;
}
} else if (Map.class.isAssignableFrom(clazz)) {
Map map = (Map) o;
for (Object value : map.values()) {
return value instanceof MultipartFile;
}
}
return o instanceof MultipartFile || o instanceof HttpServletRequest || o instanceof HttpServletResponse
|| o instanceof BindingResult;
}
}

View File

@ -0,0 +1,21 @@
package org.dromara.common.idempotent.config;
import org.dromara.common.idempotent.aspectj.RepeatSubmitAspect;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.data.redis.connection.RedisConfiguration;
/**
* 幂等功能配置
*
* @author Lion Li
*/
@AutoConfiguration(after = RedisConfiguration.class)
public class IdempotentConfig {
@Bean
public RepeatSubmitAspect repeatSubmitAspect() {
return new RepeatSubmitAspect();
}
}

View File

@ -0,0 +1 @@
org.dromara.common.idempotent.config.IdempotentConfig

View File

@ -0,0 +1,37 @@
package org.dromara.common.job.config;
import ch.qos.logback.classic.Logger;
import ch.qos.logback.classic.LoggerContext;
import ch.qos.logback.classic.spi.ILoggingEvent;
import com.aizuda.snailjob.client.common.appender.SnailLogbackAppender;
import com.aizuda.snailjob.client.common.event.SnailClientStartingEvent;
import com.aizuda.snailjob.client.starter.EnableSnailJob;
import org.slf4j.LoggerFactory;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.event.EventListener;
import org.springframework.scheduling.annotation.EnableScheduling;
/**
* 启动定时任务
*
* @author opensnail
* @date 2024-05-17
*/
@AutoConfiguration
@ConditionalOnProperty(prefix = "snail-job", name = "enabled", havingValue = "true")
@EnableScheduling
@EnableSnailJob
public class SnailJobConfig {
@EventListener(SnailClientStartingEvent.class)
public void onStarting(SnailClientStartingEvent event) {
LoggerContext lc = (LoggerContext) LoggerFactory.getILoggerFactory();
SnailLogbackAppender<ILoggingEvent> ca = new SnailLogbackAppender<>();
ca.setName("snail_log_appender");
ca.start();
Logger rootLogger = lc.getLogger(Logger.ROOT_LOGGER_NAME);
rootLogger.addAppender(ca);
}
}

View File

@ -0,0 +1,37 @@
<?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>
<groupId>org.dromara</groupId>
<artifactId>ruoyi-common</artifactId>
<version>${revision}</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>ruoyi-common-json</artifactId>
<description>
ruoyi-common-json 序列化模块
</description>
<dependencies>
<dependency>
<groupId>org.dromara</groupId>
<artifactId>ruoyi-common-core</artifactId>
</dependency>
<!-- JSON工具类 -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,47 @@
package org.dromara.common.json.config;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
import org.dromara.common.json.handler.BigNumberSerializer;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer;
import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration;
import org.springframework.context.annotation.Bean;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.TimeZone;
/**
* jackson 配置
*
* @author Lion Li
*/
@Slf4j
@AutoConfiguration(before = JacksonAutoConfiguration.class)
public class JacksonConfig {
@Bean
public Jackson2ObjectMapperBuilderCustomizer customizer() {
return builder -> {
// 全局配置序列化返回 JSON 处理
JavaTimeModule javaTimeModule = new JavaTimeModule();
javaTimeModule.addSerializer(Long.class, BigNumberSerializer.INSTANCE);
javaTimeModule.addSerializer(Long.TYPE, BigNumberSerializer.INSTANCE);
javaTimeModule.addSerializer(BigInteger.class, BigNumberSerializer.INSTANCE);
javaTimeModule.addSerializer(BigDecimal.class, ToStringSerializer.instance);
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
javaTimeModule.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(formatter));
javaTimeModule.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(formatter));
builder.modules(javaTimeModule);
builder.timeZone(TimeZone.getDefault());
log.info("初始化 jackson 配置");
};
}
}

View File

@ -0,0 +1,18 @@
package org.dromara.common.log.enums;
/**
* 操作状态
*
* @author ruoyi
*/
public enum BusinessStatus {
/**
* 成功
*/
SUCCESS,
/**
* 失败
*/
FAIL,
}

View File

@ -0,0 +1,58 @@
package org.dromara.common.log.enums;
/**
* 业务操作类型
*
* @author ruoyi
*/
public enum BusinessType {
/**
* 其它
*/
OTHER,
/**
* 新增
*/
INSERT,
/**
* 修改
*/
UPDATE,
/**
* 删除
*/
DELETE,
/**
* 授权
*/
GRANT,
/**
* 导出
*/
EXPORT,
/**
* 导入
*/
IMPORT,
/**
* 强退
*/
FORCE,
/**
* 生成代码
*/
GENCODE,
/**
* 清空数据
*/
CLEAN,
}

View File

@ -0,0 +1,34 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<groupId>org.dromara</groupId>
<artifactId>ruoyi-common</artifactId>
<version>${revision}</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>ruoyi-common-mail</artifactId>
<description>
ruoyi-common-mail 邮件模块
</description>
<dependencies>
<dependency>
<groupId>org.dromara</groupId>
<artifactId>ruoyi-common-core</artifactId>
</dependency>
<dependency>
<groupId>jakarta.mail</groupId>
<artifactId>jakarta.mail-api</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.angus</groupId>
<artifactId>jakarta.mail</artifactId>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,75 @@
package org.dromara.common.mail.config.properties;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
/**
* JavaMail 配置属性
*
* @author Michelle.Chung
*/
@Data
@ConfigurationProperties(prefix = "mail")
public class MailProperties {
/**
* 过滤开关
*/
private Boolean enabled;
/**
* SMTP服务器域名
*/
private String host;
/**
* SMTP服务端口
*/
private Integer port;
/**
* 是否需要用户名密码验证
*/
private Boolean auth;
/**
* 用户名
*/
private String user;
/**
* 密码
*/
private String pass;
/**
* 发送方遵循RFC-822标准<br>
* 发件人可以是以下形式
*
* <pre>
* 1. user@xxx.xx
* 2. name &lt;user@xxx.xx&gt;
* </pre>
*/
private String from;
/**
* 使用 STARTTLS安全连接STARTTLS是对纯文本通信协议的扩展它将纯文本连接升级为加密连接TLS或SSL 而不是使用一个单独的加密通信端口
*/
private Boolean starttlsEnable;
/**
* 使用 SSL安全连接
*/
private Boolean sslEnable;
/**
* SMTP超时时长单位毫秒缺省值不超时
*/
private Long timeout;
/**
* Socket连接超时值单位毫秒缺省值不超时
*/
private Long connectionTimeout;
}

View File

@ -0,0 +1,469 @@
package org.dromara.common.mail.utils;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.CharUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.extra.mail.JakartaMail;
import cn.hutool.extra.mail.JakartaUserPassAuthenticator;
import cn.hutool.extra.mail.MailAccount;
import jakarta.mail.Authenticator;
import jakarta.mail.Session;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import org.dromara.common.core.utils.SpringUtils;
import org.dromara.common.core.utils.StringUtils;
import java.io.File;
import java.io.InputStream;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
/**
* 邮件工具类
*/
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class MailUtils {
private static final MailAccount ACCOUNT = SpringUtils.getBean(MailAccount.class);
/**
* 获取邮件发送实例
*/
public static MailAccount getMailAccount() {
return ACCOUNT;
}
/**
* 获取邮件发送实例 (自定义发送人以及授权码)
*
* @param user 发送人
* @param pass 授权码
*/
public static MailAccount getMailAccount(String from, String user, String pass) {
ACCOUNT.setFrom(StringUtils.blankToDefault(from, ACCOUNT.getFrom()));
ACCOUNT.setUser(StringUtils.blankToDefault(user, ACCOUNT.getUser()));
ACCOUNT.setPass(StringUtils.blankToDefault(pass, ACCOUNT.getPass()));
return ACCOUNT;
}
/**
* 使用配置文件中设置的账户发送文本邮件发送给单个或多个收件人<br>
* 多个收件人可以使用逗号,分隔也可以通过分号;分隔
*
* @param to 收件人
* @param subject 标题
* @param content 正文
* @param files 附件列表
* @return message-id
* @since 3.2.0
*/
public static String sendText(String to, String subject, String content, File... files) {
return send(to, subject, content, false, files);
}
/**
* 使用配置文件中设置的账户发送HTML邮件发送给单个或多个收件人<br>
* 多个收件人可以使用逗号,分隔也可以通过分号;分隔
*
* @param to 收件人
* @param subject 标题
* @param content 正文
* @param files 附件列表
* @return message-id
* @since 3.2.0
*/
public static String sendHtml(String to, String subject, String content, File... files) {
return send(to, subject, content, true, files);
}
/**
* 使用配置文件中设置的账户发送邮件发送单个或多个收件人<br>
* 多个收件人可以使用逗号,分隔也可以通过分号;分隔
*
* @param to 收件人
* @param subject 标题
* @param content 正文
* @param isHtml 是否为HTML
* @param files 附件列表
* @return message-id
*/
public static String send(String to, String subject, String content, boolean isHtml, File... files) {
return send(splitAddress(to), subject, content, isHtml, files);
}
/**
* 使用配置文件中设置的账户发送邮件发送单个或多个收件人<br>
* 多个收件人抄送人密送人可以使用逗号,分隔也可以通过分号;分隔
*
* @param to 收件人可以使用逗号,分隔也可以通过分号;分隔
* @param cc 抄送人可以使用逗号,分隔也可以通过分号;分隔
* @param bcc 密送人可以使用逗号,分隔也可以通过分号;分隔
* @param subject 标题
* @param content 正文
* @param isHtml 是否为HTML
* @param files 附件列表
* @return message-id
* @since 4.0.3
*/
public static String send(String to, String cc, String bcc, String subject, String content, boolean isHtml, File... files) {
return send(splitAddress(to), splitAddress(cc), splitAddress(bcc), subject, content, isHtml, files);
}
/**
* 使用配置文件中设置的账户发送文本邮件发送给多人
*
* @param tos 收件人列表
* @param subject 标题
* @param content 正文
* @param files 附件列表
* @return message-id
*/
public static String sendText(Collection<String> tos, String subject, String content, File... files) {
return send(tos, subject, content, false, files);
}
/**
* 使用配置文件中设置的账户发送HTML邮件发送给多人
*
* @param tos 收件人列表
* @param subject 标题
* @param content 正文
* @param files 附件列表
* @return message-id
* @since 3.2.0
*/
public static String sendHtml(Collection<String> tos, String subject, String content, File... files) {
return send(tos, subject, content, true, files);
}
/**
* 使用配置文件中设置的账户发送邮件发送给多人
*
* @param tos 收件人列表
* @param subject 标题
* @param content 正文
* @param isHtml 是否为HTML
* @param files 附件列表
* @return message-id
*/
public static String send(Collection<String> tos, String subject, String content, boolean isHtml, File... files) {
return send(tos, null, null, subject, content, isHtml, files);
}
/**
* 使用配置文件中设置的账户发送邮件发送给多人
*
* @param tos 收件人列表
* @param ccs 抄送人列表可以为null或空
* @param bccs 密送人列表可以为null或空
* @param subject 标题
* @param content 正文
* @param isHtml 是否为HTML
* @param files 附件列表
* @return message-id
* @since 4.0.3
*/
public static String send(Collection<String> tos, Collection<String> ccs, Collection<String> bccs, String subject, String content, boolean isHtml, File... files) {
return send(getMailAccount(), true, tos, ccs, bccs, subject, content, null, isHtml, files);
}
// ------------------------------------------------------------------------------------------------------------------------------- Custom MailAccount
/**
* 发送邮件给多人
*
* @param mailAccount 邮件认证对象
* @param to 收件人多个收件人逗号或者分号隔开
* @param subject 标题
* @param content 正文
* @param isHtml 是否为HTML格式
* @param files 附件列表
* @return message-id
* @since 3.2.0
*/
public static String send(MailAccount mailAccount, String to, String subject, String content, boolean isHtml, File... files) {
return send(mailAccount, splitAddress(to), subject, content, isHtml, files);
}
/**
* 发送邮件给多人
*
* @param mailAccount 邮件帐户信息
* @param tos 收件人列表
* @param subject 标题
* @param content 正文
* @param isHtml 是否为HTML格式
* @param files 附件列表
* @return message-id
*/
public static String send(MailAccount mailAccount, Collection<String> tos, String subject, String content, boolean isHtml, File... files) {
return send(mailAccount, tos, null, null, subject, content, isHtml, files);
}
/**
* 发送邮件给多人
*
* @param mailAccount 邮件帐户信息
* @param tos 收件人列表
* @param ccs 抄送人列表可以为null或空
* @param bccs 密送人列表可以为null或空
* @param subject 标题
* @param content 正文
* @param isHtml 是否为HTML格式
* @param files 附件列表
* @return message-id
* @since 4.0.3
*/
public static String send(MailAccount mailAccount, Collection<String> tos, Collection<String> ccs, Collection<String> bccs, String subject, String content, boolean isHtml, File... files) {
return send(mailAccount, false, tos, ccs, bccs, subject, content, null, isHtml, files);
}
/**
* 使用配置文件中设置的账户发送HTML邮件发送给单个或多个收件人<br>
* 多个收件人可以使用逗号,分隔也可以通过分号;分隔
*
* @param to 收件人
* @param subject 标题
* @param content 正文
* @param imageMap 图片与占位符占位符格式为cid:$IMAGE_PLACEHOLDER
* @param files 附件列表
* @return message-id
* @since 3.2.0
*/
public static String sendHtml(String to, String subject, String content, Map<String, InputStream> imageMap, File... files) {
return send(to, subject, content, imageMap, true, files);
}
/**
* 使用配置文件中设置的账户发送邮件发送单个或多个收件人<br>
* 多个收件人可以使用逗号,分隔也可以通过分号;分隔
*
* @param to 收件人
* @param subject 标题
* @param content 正文
* @param imageMap 图片与占位符占位符格式为cid:$IMAGE_PLACEHOLDER
* @param isHtml 是否为HTML
* @param files 附件列表
* @return message-id
*/
public static String send(String to, String subject, String content, Map<String, InputStream> imageMap, boolean isHtml, File... files) {
return send(splitAddress(to), subject, content, imageMap, isHtml, files);
}
/**
* 使用配置文件中设置的账户发送邮件发送单个或多个收件人<br>
* 多个收件人抄送人密送人可以使用逗号,分隔也可以通过分号;分隔
*
* @param to 收件人可以使用逗号,分隔也可以通过分号;分隔
* @param cc 抄送人可以使用逗号,分隔也可以通过分号;分隔
* @param bcc 密送人可以使用逗号,分隔也可以通过分号;分隔
* @param subject 标题
* @param content 正文
* @param imageMap 图片与占位符占位符格式为cid:$IMAGE_PLACEHOLDER
* @param isHtml 是否为HTML
* @param files 附件列表
* @return message-id
* @since 4.0.3
*/
public static String send(String to, String cc, String bcc, String subject, String content, Map<String, InputStream> imageMap, boolean isHtml, File... files) {
return send(splitAddress(to), splitAddress(cc), splitAddress(bcc), subject, content, imageMap, isHtml, files);
}
/**
* 使用配置文件中设置的账户发送HTML邮件发送给多人
*
* @param tos 收件人列表
* @param subject 标题
* @param content 正文
* @param imageMap 图片与占位符占位符格式为cid:$IMAGE_PLACEHOLDER
* @param files 附件列表
* @return message-id
* @since 3.2.0
*/
public static String sendHtml(Collection<String> tos, String subject, String content, Map<String, InputStream> imageMap, File... files) {
return send(tos, subject, content, imageMap, true, files);
}
/**
* 使用配置文件中设置的账户发送邮件发送给多人
*
* @param tos 收件人列表
* @param subject 标题
* @param content 正文
* @param imageMap 图片与占位符占位符格式为cid:$IMAGE_PLACEHOLDER
* @param isHtml 是否为HTML
* @param files 附件列表
* @return message-id
*/
public static String send(Collection<String> tos, String subject, String content, Map<String, InputStream> imageMap, boolean isHtml, File... files) {
return send(tos, null, null, subject, content, imageMap, isHtml, files);
}
/**
* 使用配置文件中设置的账户发送邮件发送给多人
*
* @param tos 收件人列表
* @param ccs 抄送人列表可以为null或空
* @param bccs 密送人列表可以为null或空
* @param subject 标题
* @param content 正文
* @param imageMap 图片与占位符占位符格式为cid:$IMAGE_PLACEHOLDER
* @param isHtml 是否为HTML
* @param files 附件列表
* @return message-id
* @since 4.0.3
*/
public static String send(Collection<String> tos, Collection<String> ccs, Collection<String> bccs, String subject, String content, Map<String, InputStream> imageMap, boolean isHtml, File... files) {
return send(getMailAccount(), true, tos, ccs, bccs, subject, content, imageMap, isHtml, files);
}
// ------------------------------------------------------------------------------------------------------------------------------- Custom MailAccount
/**
* 发送邮件给多人
*
* @param mailAccount 邮件认证对象
* @param to 收件人多个收件人逗号或者分号隔开
* @param subject 标题
* @param content 正文
* @param imageMap 图片与占位符占位符格式为cid:$IMAGE_PLACEHOLDER
* @param isHtml 是否为HTML格式
* @param files 附件列表
* @return message-id
* @since 3.2.0
*/
public static String send(MailAccount mailAccount, String to, String subject, String content, Map<String, InputStream> imageMap, boolean isHtml, File... files) {
return send(mailAccount, splitAddress(to), subject, content, imageMap, isHtml, files);
}
/**
* 发送邮件给多人
*
* @param mailAccount 邮件帐户信息
* @param tos 收件人列表
* @param subject 标题
* @param content 正文
* @param imageMap 图片与占位符占位符格式为cid:$IMAGE_PLACEHOLDER
* @param isHtml 是否为HTML格式
* @param files 附件列表
* @return message-id
* @since 4.6.3
*/
public static String send(MailAccount mailAccount, Collection<String> tos, String subject, String content, Map<String, InputStream> imageMap, boolean isHtml, File... files) {
return send(mailAccount, tos, null, null, subject, content, imageMap, isHtml, files);
}
/**
* 发送邮件给多人
*
* @param mailAccount 邮件帐户信息
* @param tos 收件人列表
* @param ccs 抄送人列表可以为null或空
* @param bccs 密送人列表可以为null或空
* @param subject 标题
* @param content 正文
* @param imageMap 图片与占位符占位符格式为cid:$IMAGE_PLACEHOLDER
* @param isHtml 是否为HTML格式
* @param files 附件列表
* @return message-id
* @since 4.6.3
*/
public static String send(MailAccount mailAccount, Collection<String> tos, Collection<String> ccs, Collection<String> bccs, String subject, String content, Map<String, InputStream> imageMap,
boolean isHtml, File... files) {
return send(mailAccount, false, tos, ccs, bccs, subject, content, imageMap, isHtml, files);
}
/**
* 根据配置文件获取邮件客户端会话
*
* @param mailAccount 邮件账户配置
* @param isSingleton 是否单例全局共享会话
* @return {@link Session}
* @since 5.5.7
*/
public static Session getSession(MailAccount mailAccount, boolean isSingleton) {
Authenticator authenticator = null;
if (mailAccount.isAuth()) {
authenticator = new JakartaUserPassAuthenticator(mailAccount.getUser(), mailAccount.getPass());
}
return isSingleton ? Session.getDefaultInstance(mailAccount.getSmtpProps(), authenticator) //
: Session.getInstance(mailAccount.getSmtpProps(), authenticator);
}
// ------------------------------------------------------------------------------------------------------------------------ Private method start
/**
* 发送邮件给多人
*
* @param mailAccount 邮件帐户信息
* @param useGlobalSession 是否全局共享Session
* @param tos 收件人列表
* @param ccs 抄送人列表可以为null或空
* @param bccs 密送人列表可以为null或空
* @param subject 标题
* @param content 正文
* @param imageMap 图片与占位符占位符格式为cid:${cid}
* @param isHtml 是否为HTML格式
* @param files 附件列表
* @return message-id
* @since 4.6.3
*/
private static String send(MailAccount mailAccount, boolean useGlobalSession, Collection<String> tos, Collection<String> ccs, Collection<String> bccs, String subject, String content,
Map<String, InputStream> imageMap, boolean isHtml, File... files) {
final JakartaMail mail = JakartaMail.create(mailAccount).setUseGlobalSession(useGlobalSession);
// 可选抄送人
if (CollUtil.isNotEmpty(ccs)) {
mail.setCcs(ccs.toArray(new String[0]));
}
// 可选密送人
if (CollUtil.isNotEmpty(bccs)) {
mail.setBccs(bccs.toArray(new String[0]));
}
mail.setTos(tos.toArray(new String[0]));
mail.setTitle(subject);
mail.setContent(content);
mail.setHtml(isHtml);
mail.setFiles(files);
// 图片
if (MapUtil.isNotEmpty(imageMap)) {
for (Entry<String, InputStream> entry : imageMap.entrySet()) {
mail.addImage(entry.getKey(), entry.getValue());
// 关闭流
IoUtil.close(entry.getValue());
}
}
return mail.send();
}
/**
* 将多个联系人转为列表分隔符为逗号或者分号
*
* @param addresses 多个联系人如果为空返回null
* @return 联系人列表
*/
private static List<String> splitAddress(String addresses) {
if (StrUtil.isBlank(addresses)) {
return null;
}
List<String> result;
if (StrUtil.contains(addresses, CharUtil.COMMA)) {
result = StrUtil.splitTrim(addresses, CharUtil.COMMA);
} else if (StrUtil.contains(addresses, ';')) {
result = StrUtil.splitTrim(addresses, ';');
} else {
result = CollUtil.newArrayList(addresses);
}
return result;
}
// ------------------------------------------------------------------------------------------------------------------------ Private method end
}

View File

@ -0,0 +1,169 @@
package org.dromara.common.mybatis.core.page;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjectUtil;
import com.baomidou.mybatisplus.core.metadata.OrderItem;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.fasterxml.jackson.annotation.JsonIgnore;
import lombok.Data;
import org.dromara.common.core.exception.ServiceException;
import org.dromara.common.core.utils.StringUtils;
import org.dromara.common.core.utils.sql.SqlUtil;
import java.io.Serial;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
/**
* 分页查询实体类
*
* @author Lion Li
*/
@Data
public class PageQuery implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 分页大小
*/
private Integer pageSize;
/**
* 当前页数
*/
private Integer pageNum;
/**
* 排序列
*/
private String orderByColumn;
/**
* 排序的方向desc或者asc
*/
private String isAsc;
/**
* 当前记录起始索引 默认值
*/
public static final int DEFAULT_PAGE_NUM = 1;
/**
* 每页显示记录数 默认值 默认查全部
*/
// public static final int DEFAULT_PAGE_SIZE = Integer.MAX_VALUE;
// 修复设置合理的默认分页大小
public static final int DEFAULT_PAGE_SIZE = 20;
// 添加最大分页大小限制
public static final int MAX_PAGE_SIZE = 1000;
public static final int min_size=0;
/**
* 构建分页对象
*/
// public <T> Page<T> build() {
// Integer pageNum = ObjectUtil.defaultIfNull(getPageNum(), DEFAULT_PAGE_NUM);
// Integer pageSize = ObjectUtil.defaultIfNull(getPageSize(), DEFAULT_PAGE_SIZE);
//
// if (pageNum <= 0) {
// pageNum = DEFAULT_PAGE_NUM;
// }
// Page<T> page = new Page<>(pageNum, pageSize);
// List<OrderItem> orderItems = buildOrderItem();
// if (CollUtil.isNotEmpty(orderItems)) {
// page.addOrder(orderItems);
// }
// return page;
// }
/**
* 构建分页对象
*/
public <T> Page<T> build() {
Integer pageNum = ObjectUtil.defaultIfNull(getPageNum(), DEFAULT_PAGE_NUM);
Integer pageSize = ObjectUtil.defaultIfNull(getPageSize(), DEFAULT_PAGE_SIZE);
// 修复验证分页参数
if (pageNum <= 0) {
pageNum = DEFAULT_PAGE_NUM;
}
if (pageSize <= 0) {
pageSize = DEFAULT_PAGE_SIZE;
}
// 修复限制最大分页大小
if (pageSize > MAX_PAGE_SIZE) {
pageSize = min_size;
throw new ServiceException("分页大小不能超过 " + MAX_PAGE_SIZE);
// 可以记录日志或抛出异常
// log.warn("分页大小超过限制,已自动调整为最大值: {}", MAX_PAGE_SIZE);
}
Page<T> page = new Page<>(pageNum, pageSize);
List<OrderItem> orderItems = buildOrderItem();
if (CollUtil.isNotEmpty(orderItems)) {
page.addOrder(orderItems);
}
return page;
}
/**
* 构建排序
*
* 支持的用法如下:
* {isAsc:"asc",orderByColumn:"id"} order by id asc
* {isAsc:"asc",orderByColumn:"id,createTime"} order by id asc,create_time asc
* {isAsc:"desc",orderByColumn:"id,createTime"} order by id desc,create_time desc
* {isAsc:"asc,desc",orderByColumn:"id,createTime"} order by id asc,create_time desc
*/
private List<OrderItem> buildOrderItem() {
if (StringUtils.isBlank(orderByColumn) || StringUtils.isBlank(isAsc)) {
return null;
}
String orderBy = SqlUtil.escapeOrderBySql(orderByColumn);
orderBy = StringUtils.toUnderScoreCase(orderBy);
// 兼容前端排序类型
isAsc = StringUtils.replaceEach(isAsc, new String[]{"ascending", "descending"}, new String[]{"asc", "desc"});
String[] orderByArr = orderBy.split(StringUtils.SEPARATOR);
String[] isAscArr = isAsc.split(StringUtils.SEPARATOR);
if (isAscArr.length != 1 && isAscArr.length != orderByArr.length) {
throw new ServiceException("排序参数有误");
}
List<OrderItem> list = new ArrayList<>();
// 每个字段各自排序
for (int i = 0; i < orderByArr.length; i++) {
String orderByStr = orderByArr[i];
String isAscStr = isAscArr.length == 1 ? isAscArr[0] : isAscArr[i];
if ("asc".equals(isAscStr)) {
list.add(OrderItem.asc(orderByStr));
} else if ("desc".equals(isAscStr)) {
list.add(OrderItem.desc(orderByStr));
} else {
throw new ServiceException("排序参数有误");
}
}
return list;
}
@JsonIgnore
public Integer getFirstNum() {
return (pageNum - 1) * pageSize;
}
public PageQuery(Integer pageSize, Integer pageNum) {
this.pageSize = pageSize;
this.pageNum = pageNum;
}
}

View File

@ -0,0 +1,87 @@
package org.dromara.common.mybatis.enums;
import lombok.AllArgsConstructor;
import lombok.Getter;
import org.dromara.common.core.domain.model.LoginUser;
import org.dromara.common.core.utils.StringUtils;
import org.dromara.common.mybatis.helper.DataPermissionHelper;
/**
* 数据权限类型枚举
* <p>
* 支持使用 SpEL 模板表达式定义 SQL 查询条件
* 内置数据
* - {@code user}: 当前登录用户信息参考 {@link LoginUser}
* 内置服务
* - {@code sdss}: 系统数据权限服务参考 ISysDataScopeService
* 如需扩展数据可以通过 {@link DataPermissionHelper} 进行操作
* 如需扩展服务可以通过 ISysDataScopeService 自行编写
* </p>
*
* @author Lion Li
* @version 3.5.0
*/
@Getter
@AllArgsConstructor
public enum DataScopeType {
/**
* 全部数据权限
*/
ALL("1", "", ""),
/**
* 自定数据权限
*/
CUSTOM("2", " #{#deptName} IN ( #{@sdss.getRoleCustom( #user.roleId )} ) ", " 1 = 0 "),
/**
* 部门数据权限
*/
DEPT("3", " #{#deptName} = #{#user.deptId} ", " 1 = 0 "),
/**
* 部门及以下数据权限
*/
DEPT_AND_CHILD("4", " #{#deptName} IN ( #{@sdss.getDeptAndChild( #user.deptId )} )", " 1 = 0 "),
/**
* 仅本人数据权限
*/
SELF("5", " #{#userName} = #{#user.userId} ", " 1 = 0 "),
/**
* 部门及以下或本人数据权限
*/
DEPT_AND_CHILD_OR_SELF("6", " #{#deptName} IN ( #{@sdss.getDeptAndChild( #user.deptId )} ) OR #{#userName} = #{#user.userId} ", " 1 = 0 ");
private final String code;
/**
* SpEL 模板表达式用于构建 SQL 查询条件
*/
private final String sqlTemplate;
/**
* 如果不满足 {@code sqlTemplate} 的条件则使用此默认 SQL 表达式
*/
private final String elseSql;
/**
* 根据枚举代码查找对应的枚举值
*
* @param code 枚举代码
* @return 对应的枚举值如果未找到则返回 null
*/
public static DataScopeType findCode(String code) {
if (StringUtils.isBlank(code)) {
return null;
}
for (DataScopeType type : values()) {
if (type.getCode().equals(code)) {
return type;
}
}
return null;
}
}

View File

@ -0,0 +1,33 @@
# 内置配置 不允许修改 如需修改请在 nacos 上写相同配置覆盖
# MyBatisPlus配置
# https://baomidou.com/config/
mybatis-plus:
# 启动时是否检查 MyBatis XML 文件的存在,默认不检查
checkConfigLocation: false
configuration:
# 自动驼峰命名规则camel case映射
mapUnderscoreToCamelCase: true
# MyBatis 自动映射策略
# NONE不启用 PARTIAL只对非嵌套 resultMap 自动映射 FULL对所有 resultMap 自动映射
autoMappingBehavior: FULL
# 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.nologging.NoLoggingImpl
global-config:
# 是否打印 Logo banner
banner: true
dbConfig:
# 主键类型
# AUTO 自增 NONE 空 INPUT 用户输入 ASSIGN_ID 雪花 ASSIGN_UUID 唯一 UUID
idType: ASSIGN_ID
# 逻辑已删除值(可按需求随意修改)
logicDeleteValue: 1
# 逻辑未删除值
logicNotDeleteValue: 0
insertStrategy: NOT_NULL
updateStrategy: NOT_NULL
whereStrategy: NOT_NULL

View File

@ -0,0 +1,40 @@
package org.dromara.common.oss.constant;
import org.dromara.common.core.constant.GlobalConstants;
import java.util.Arrays;
import java.util.List;
/**
* 对象存储常量
*
* @author Lion Li
*/
public interface OssConstant {
/**
* 默认配置KEY
*/
String DEFAULT_CONFIG_KEY = GlobalConstants.GLOBAL_REDIS_KEY + "sys_oss:default_config";
/**
* 预览列表资源开关Key
*/
String PEREVIEW_LIST_RESOURCE_KEY = "sys.oss.previewListResource";
/**
* 系统数据ids
*/
List<Long> SYSTEM_DATA_IDS = Arrays.asList(1L, 2L, 3L, 4L);
/**
* 云服务商
*/
String[] CLOUD_SERVICE = new String[] {"aliyun", "qcloud", "qiniu", "obs"};
/**
* https 状态
*/
String IS_HTTPS = "Y";
}

View File

@ -0,0 +1,30 @@
package org.dromara.common.oss.entity;
import lombok.Builder;
import lombok.Data;
/**
* 上传返回体
*
* @author Lion Li
*/
@Data
@Builder
public class UploadResult {
/**
* 文件路径
*/
private String url;
/**
* 文件名
*/
private String filename;
/**
* 已上传对象的实体标记用来校验文件
*/
private String eTag;
}

View File

@ -0,0 +1,56 @@
package org.dromara.common.oss.enumd;
import lombok.AllArgsConstructor;
import lombok.Getter;
import software.amazon.awssdk.services.s3.model.BucketCannedACL;
import software.amazon.awssdk.services.s3.model.ObjectCannedACL;
/**
* 桶访问策略配置
*
* @author 陈賝
*/
@Getter
@AllArgsConstructor
public enum AccessPolicyType {
/**
* private
*/
PRIVATE("0", BucketCannedACL.PRIVATE, ObjectCannedACL.PRIVATE),
/**
* public
*/
PUBLIC("1", BucketCannedACL.PUBLIC_READ_WRITE, ObjectCannedACL.PUBLIC_READ_WRITE),
/**
* custom
*/
CUSTOM("2", BucketCannedACL.PUBLIC_READ, ObjectCannedACL.PUBLIC_READ);
/**
* 权限类型数据库值
*/
private final String type;
/**
* 权限类型
*/
private final BucketCannedACL bucketCannedACL;
/**
* 文件对象 权限类型
*/
private final ObjectCannedACL objectCannedACL;
public static AccessPolicyType getByType(String type) {
for (AccessPolicyType value : values()) {
if (value.getType().equals(type)) {
return value;
}
}
throw new RuntimeException("'type' not found By " + type);
}
}

View File

@ -0,0 +1,63 @@
package org.dromara.common.oss.properties;
import lombok.Data;
/**
* OSS对象存储 配置属性
*
* @author Lion Li
*/
@Data
public class OssProperties {
/**
* 租户id
*/
private String tenantId;
/**
* 访问站点
*/
private String endpoint;
/**
* 自定义域名
*/
private String domain;
/**
* 前缀
*/
private String prefix;
/**
* ACCESS_KEY
*/
private String accessKey;
/**
* SECRET_KEY
*/
private String secretKey;
/**
* 存储空间名
*/
private String bucketName;
/**
* 存储区域
*/
private String region;
/**
* 是否httpsY=,N=
*/
private String isHttps;
/**
* 桶权限类型(0private 1public 2custom)
*/
private String accessPolicy;
}

View File

@ -0,0 +1,30 @@
<?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>
<groupId>org.dromara</groupId>
<artifactId>ruoyi-common</artifactId>
<version>${revision}</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>ruoyi-common-ratelimiter</artifactId>
<description>
ruoyi-common-ratelimiter 限流功能
</description>
<dependencies>
<dependency>
<groupId>org.dromara</groupId>
<artifactId>ruoyi-common-core</artifactId>
</dependency>
<dependency>
<groupId>org.dromara</groupId>
<artifactId>ruoyi-common-redis</artifactId>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,112 @@
package org.dromara.common.ratelimiter.aspectj;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.reflect.MethodSignature;
import org.dromara.common.core.constant.GlobalConstants;
import org.dromara.common.core.exception.ServiceException;
import org.dromara.common.core.utils.MessageUtils;
import org.dromara.common.core.utils.ServletUtils;
import org.dromara.common.core.utils.SpringUtils;
import org.dromara.common.core.utils.StringUtils;
import org.dromara.common.ratelimiter.annotation.RateLimiter;
import org.dromara.common.ratelimiter.enums.LimitType;
import org.dromara.common.redis.utils.RedisUtils;
import org.redisson.api.RateType;
import org.springframework.context.expression.BeanFactoryResolver;
import org.springframework.context.expression.MethodBasedEvaluationContext;
import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.core.ParameterNameDiscoverer;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.ParserContext;
import org.springframework.expression.common.TemplateParserContext;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import java.lang.reflect.Method;
/**
* 限流处理
*
* @author Lion Li
*/
@Slf4j
@Aspect
public class RateLimiterAspect {
/**
* 定义spel表达式解析器
*/
private final ExpressionParser parser = new SpelExpressionParser();
/**
* 定义spel解析模版
*/
private final ParserContext parserContext = new TemplateParserContext();
/**
* 方法参数解析器
*/
private final ParameterNameDiscoverer pnd = new DefaultParameterNameDiscoverer();
@Before("@annotation(rateLimiter)")
public void doBefore(JoinPoint point, RateLimiter rateLimiter) {
int time = rateLimiter.time();
int count = rateLimiter.count();
int timeout = rateLimiter.timeout();
try {
String combineKey = getCombineKey(rateLimiter, point);
RateType rateType = RateType.OVERALL;
if (rateLimiter.limitType() == LimitType.CLUSTER) {
rateType = RateType.PER_CLIENT;
}
long number = RedisUtils.rateLimiter(combineKey, rateType, count, time, timeout);
if (number == -1) {
String message = rateLimiter.message();
if (StringUtils.startsWith(message, "{") && StringUtils.endsWith(message, "}")) {
message = MessageUtils.message(StringUtils.substring(message, 1, message.length() - 1));
}
throw new ServiceException(message);
}
log.info("限制令牌 => {}, 剩余令牌 => {}, 缓存key => '{}'", count, number, combineKey);
} catch (Exception e) {
if (e instanceof ServiceException) {
throw e;
} else {
throw new RuntimeException("服务器限流异常,请稍候再试", e);
}
}
}
private String getCombineKey(RateLimiter rateLimiter, JoinPoint point) {
String key = rateLimiter.key();
// 判断 key 不为空 不是表达式
if (StringUtils.isNotBlank(key) && StringUtils.containsAny(key, "#")) {
MethodSignature signature = (MethodSignature) point.getSignature();
Method targetMethod = signature.getMethod();
Object[] args = point.getArgs();
MethodBasedEvaluationContext context =
new MethodBasedEvaluationContext(null, targetMethod, args, pnd);
context.setBeanResolver(new BeanFactoryResolver(SpringUtils.getBeanFactory()));
Expression expression;
if (StringUtils.startsWith(key, parserContext.getExpressionPrefix())
&& StringUtils.endsWith(key, parserContext.getExpressionSuffix())) {
expression = parser.parseExpression(key, parserContext);
} else {
expression = parser.parseExpression(key);
}
key = expression.getValue(context, String.class);
}
StringBuilder stringBuffer = new StringBuilder(GlobalConstants.RATE_LIMIT_KEY);
stringBuffer.append(ServletUtils.getRequest().getRequestURI()).append(":");
if (rateLimiter.limitType() == LimitType.IP) {
// 获取请求ip
stringBuffer.append(ServletUtils.getClientIP()).append(":");
} else if (rateLimiter.limitType() == LimitType.CLUSTER) {
// 获取客户端实例id
stringBuffer.append(RedisUtils.getClient().getId()).append(":");
}
return stringBuffer.append(key).toString();
}
}

View File

@ -0,0 +1,159 @@
package org.dromara.common.redis.config;
import cn.hutool.core.util.ObjectUtil;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
import lombok.extern.slf4j.Slf4j;
import org.dromara.common.core.utils.SpringUtils;
import org.dromara.common.redis.config.properties.RedissonProperties;
import org.dromara.common.redis.handler.KeyPrefixHandler;
import org.dromara.common.redis.handler.RedisExceptionHandler;
import org.redisson.client.codec.StringCodec;
import org.redisson.codec.CompositeCodec;
import org.redisson.codec.TypedJsonJacksonCodec;
import org.redisson.spring.starter.RedissonAutoConfigurationCustomizer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.core.task.VirtualThreadTaskExecutor;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.TimeZone;
/**
* redis配置
*
* @author Lion Li
*/
@Slf4j
@AutoConfiguration
@EnableConfigurationProperties(RedissonProperties.class)
public class RedisConfig {
@Autowired
private RedissonProperties redissonProperties;
@Bean
public RedissonAutoConfigurationCustomizer redissonCustomizer() {
return config -> {
JavaTimeModule javaTimeModule = new JavaTimeModule();
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
javaTimeModule.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(formatter));
javaTimeModule.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(formatter));
ObjectMapper om = new ObjectMapper();
om.registerModule(javaTimeModule);
om.setTimeZone(TimeZone.getDefault());
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
// 指定序列化输入的类型类必须是非final修饰的序列化时将对象全类名一起保存下来
om.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL);
// LoggerFactory.useSlf4jLogging(true);
// FuryCodec furyCodec = new FuryCodec();
// CompositeCodec codec = new CompositeCodec(StringCodec.INSTANCE, furyCodec, furyCodec);
TypedJsonJacksonCodec jsonCodec = new TypedJsonJacksonCodec(Object.class, om);
// 组合序列化 key 使用 String 内容使用通用 json 格式
CompositeCodec codec = new CompositeCodec(StringCodec.INSTANCE, jsonCodec, jsonCodec);
config.setThreads(redissonProperties.getThreads())
.setNettyThreads(redissonProperties.getNettyThreads())
// 缓存 Lua 脚本 减少网络传输(redisson 大部分的功能都是基于 Lua 脚本实现)
.setUseScriptCache(true)
.setCodec(codec);
if (SpringUtils.isVirtual()) {
config.setNettyExecutor(new VirtualThreadTaskExecutor("redisson-"));
}
RedissonProperties.SingleServerConfig singleServerConfig = redissonProperties.getSingleServerConfig();
if (ObjectUtil.isNotNull(singleServerConfig)) {
// 使用单机模式
config.useSingleServer()
//设置redis key前缀
.setNameMapper(new KeyPrefixHandler(redissonProperties.getKeyPrefix()))
.setTimeout(singleServerConfig.getTimeout())
.setClientName(singleServerConfig.getClientName())
.setIdleConnectionTimeout(singleServerConfig.getIdleConnectionTimeout())
.setSubscriptionConnectionPoolSize(singleServerConfig.getSubscriptionConnectionPoolSize())
.setConnectionMinimumIdleSize(singleServerConfig.getConnectionMinimumIdleSize())
.setConnectionPoolSize(singleServerConfig.getConnectionPoolSize());
}
// 集群配置方式 参考下方注释
RedissonProperties.ClusterServersConfig clusterServersConfig = redissonProperties.getClusterServersConfig();
if (ObjectUtil.isNotNull(clusterServersConfig)) {
config.useClusterServers()
//设置redis key前缀
.setNameMapper(new KeyPrefixHandler(redissonProperties.getKeyPrefix()))
.setTimeout(clusterServersConfig.getTimeout())
.setClientName(clusterServersConfig.getClientName())
.setIdleConnectionTimeout(clusterServersConfig.getIdleConnectionTimeout())
.setSubscriptionConnectionPoolSize(clusterServersConfig.getSubscriptionConnectionPoolSize())
.setMasterConnectionMinimumIdleSize(clusterServersConfig.getMasterConnectionMinimumIdleSize())
.setMasterConnectionPoolSize(clusterServersConfig.getMasterConnectionPoolSize())
.setSlaveConnectionMinimumIdleSize(clusterServersConfig.getSlaveConnectionMinimumIdleSize())
.setSlaveConnectionPoolSize(clusterServersConfig.getSlaveConnectionPoolSize())
.setReadMode(clusterServersConfig.getReadMode())
.setSubscriptionMode(clusterServersConfig.getSubscriptionMode());
}
log.info("初始化 redis 配置");
};
}
/**
* 异常处理器
*/
@Bean
public RedisExceptionHandler redisExceptionHandler() {
return new RedisExceptionHandler();
}
/**
* redis集群配置 yml
*
* --- # redis 集群配置(单机与集群只能开启一个另一个需要注释掉)
* spring.data:
* redis:
* cluster:
* nodes:
* - 192.168.0.100:6379
* - 192.168.0.101:6379
* - 192.168.0.102:6379
* # 密码
* password:
* # 连接超时时间
* timeout: 10s
* # 是否开启ssl
* ssl.enabled: false
*
* redisson:
* # 线程池数量
* threads: 16
* # Netty线程池数量
* nettyThreads: 32
* # 集群配置
* clusterServersConfig:
* # 客户端名称
* clientName: ${ruoyi.name}
* # master最小空闲连接数
* masterConnectionMinimumIdleSize: 32
* # master连接池大小
* masterConnectionPoolSize: 64
* # slave最小空闲连接数
* slaveConnectionMinimumIdleSize: 32
* # slave连接池大小
* slaveConnectionPoolSize: 64
* # 连接空闲超时单位毫秒
* idleConnectionTimeout: 10000
* # 命令等待超时单位毫秒
* timeout: 3000
* # 发布和订阅连接池大小
* subscriptionConnectionPoolSize: 50
* # 读取模式
* readMode: "SLAVE"
* # 订阅模式
* subscriptionMode: "MASTER"
*/
}

View File

@ -0,0 +1,135 @@
package org.dromara.common.redis.config.properties;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.redisson.config.ReadMode;
import org.redisson.config.SubscriptionMode;
import org.springframework.boot.context.properties.ConfigurationProperties;
/**
* Redisson 配置属性
*
* @author Lion Li
*/
@Data
@ConfigurationProperties(prefix = "redisson")
public class RedissonProperties {
/**
* redis缓存key前缀
*/
private String keyPrefix;
/**
* 线程池数量,默认值 = 当前处理核数量 * 2
*/
private int threads;
/**
* Netty线程池数量,默认值 = 当前处理核数量 * 2
*/
private int nettyThreads;
/**
* 单机服务配置
*/
private SingleServerConfig singleServerConfig;
/**
* 集群服务配置
*/
private ClusterServersConfig clusterServersConfig;
@Data
@NoArgsConstructor
public static class SingleServerConfig {
/**
* 客户端名称
*/
private String clientName;
/**
* 最小空闲连接数
*/
private int connectionMinimumIdleSize;
/**
* 连接池大小
*/
private int connectionPoolSize;
/**
* 连接空闲超时单位毫秒
*/
private int idleConnectionTimeout;
/**
* 命令等待超时单位毫秒
*/
private int timeout;
/**
* 发布和订阅连接池大小
*/
private int subscriptionConnectionPoolSize;
}
@Data
@NoArgsConstructor
public static class ClusterServersConfig {
/**
* 客户端名称
*/
private String clientName;
/**
* master最小空闲连接数
*/
private int masterConnectionMinimumIdleSize;
/**
* master连接池大小
*/
private int masterConnectionPoolSize;
/**
* slave最小空闲连接数
*/
private int slaveConnectionMinimumIdleSize;
/**
* slave连接池大小
*/
private int slaveConnectionPoolSize;
/**
* 连接空闲超时单位毫秒
*/
private int idleConnectionTimeout;
/**
* 命令等待超时单位毫秒
*/
private int timeout;
/**
* 发布和订阅连接池大小
*/
private int subscriptionConnectionPoolSize;
/**
* 读取模式
*/
private ReadMode readMode;
/**
* 订阅模式
*/
private SubscriptionMode subscriptionMode;
}
}

View File

@ -0,0 +1,50 @@
package org.dromara.common.redis.handler;
import org.dromara.common.core.utils.StringUtils;
import org.redisson.api.NameMapper;
/**
* redis缓存key前缀处理
*
* @author ye
* @date 2022/7/14 17:44
* @since 4.3.0
*/
public class KeyPrefixHandler implements NameMapper {
private final String keyPrefix;
public KeyPrefixHandler(String keyPrefix) {
//前缀为空 则返回空前缀
this.keyPrefix = StringUtils.isBlank(keyPrefix) ? "" : keyPrefix + ":";
}
/**
* 增加前缀
*/
@Override
public String map(String name) {
if (StringUtils.isBlank(name)) {
return null;
}
if (StringUtils.isNotBlank(keyPrefix) && !name.startsWith(keyPrefix)) {
return keyPrefix + name;
}
return name;
}
/**
* 去除前缀
*/
@Override
public String unmap(String name) {
if (StringUtils.isBlank(name)) {
return null;
}
if (StringUtils.isNotBlank(keyPrefix) && name.startsWith(keyPrefix)) {
return name.substring(keyPrefix.length());
}
return name;
}
}

View File

@ -0,0 +1,30 @@
package org.dromara.common.redis.handler;
import cn.hutool.http.HttpStatus;
import com.baomidou.lock.exception.LockFailureException;
import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.dromara.common.core.domain.R;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
/**
* Redis异常处理器
*
* @author AprilWind
*/
@Slf4j
@RestControllerAdvice
public class RedisExceptionHandler {
/**
* 分布式锁Lock4j异常
*/
@ExceptionHandler(LockFailureException.class)
public R<Void> handleLockFailureException(LockFailureException e, HttpServletRequest request) {
String requestURI = request.getRequestURI();
log.error("获取锁失败了'{}',发生Lock4j异常.", requestURI, e);
return R.fail(HttpStatus.HTTP_UNAVAILABLE, "业务处理中,请稍后再试...");
}
}

View File

@ -0,0 +1,192 @@
/**
* Copyright (c) 2013-2021 Nikita Koksharov
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dromara.common.redis.manager;
import org.dromara.common.redis.utils.RedisUtils;
import org.redisson.api.RMap;
import org.redisson.api.RMapCache;
import org.redisson.spring.cache.CacheConfig;
import org.redisson.spring.cache.RedissonCache;
import org.springframework.boot.convert.DurationStyle;
import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;
import org.springframework.cache.transaction.TransactionAwareCacheDecorator;
import org.springframework.util.StringUtils;
import java.util.Collection;
import java.util.Collections;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
/**
* A {@link org.springframework.cache.CacheManager} implementation
* backed by Redisson instance.
* <p>
* 修改 RedissonSpringCacheManager 源码
* 重写 cacheName 处理方法 支持多参数
*
* @author Nikita Koksharov
*
*/
@SuppressWarnings("unchecked")
public class PlusSpringCacheManager implements CacheManager {
private boolean dynamic = true;
private boolean allowNullValues = true;
private boolean transactionAware = true;
Map<String, CacheConfig> configMap = new ConcurrentHashMap<>();
ConcurrentMap<String, Cache> instanceMap = new ConcurrentHashMap<>();
/**
* Creates CacheManager supplied by Redisson instance
*/
public PlusSpringCacheManager() {
}
/**
* Defines possibility of storing {@code null} values.
* <p>
* Default is <code>true</code>
*
* @param allowNullValues stores if <code>true</code>
*/
public void setAllowNullValues(boolean allowNullValues) {
this.allowNullValues = allowNullValues;
}
/**
* Defines if cache aware of Spring-managed transactions.
* If {@code true} put/evict operations are executed only for successful transaction in after-commit phase.
* <p>
* Default is <code>false</code>
*
* @param transactionAware cache is transaction aware if <code>true</code>
*/
public void setTransactionAware(boolean transactionAware) {
this.transactionAware = transactionAware;
}
/**
* Defines 'fixed' cache names.
* A new cache instance will not be created in dynamic for non-defined names.
* <p>
* `null` parameter setups dynamic mode
*
* @param names of caches
*/
public void setCacheNames(Collection<String> names) {
if (names != null) {
for (String name : names) {
getCache(name);
}
dynamic = false;
} else {
dynamic = true;
}
}
/**
* Set cache config mapped by cache name
*
* @param config object
*/
public void setConfig(Map<String, ? extends CacheConfig> config) {
this.configMap = (Map<String, CacheConfig>) config;
}
protected CacheConfig createDefaultConfig() {
return new CacheConfig();
}
@Override
public Cache getCache(String name) {
// 重写 cacheName 支持多参数
String[] array = StringUtils.delimitedListToStringArray(name, "#");
name = array[0];
Cache cache = instanceMap.get(name);
if (cache != null) {
return cache;
}
if (!dynamic) {
return cache;
}
CacheConfig config = configMap.get(name);
if (config == null) {
config = createDefaultConfig();
configMap.put(name, config);
}
if (array.length > 1) {
config.setTTL(DurationStyle.detectAndParse(array[1]).toMillis());
}
if (array.length > 2) {
config.setMaxIdleTime(DurationStyle.detectAndParse(array[2]).toMillis());
}
if (array.length > 3) {
config.setMaxSize(Integer.parseInt(array[3]));
}
if (config.getMaxIdleTime() == 0 && config.getTTL() == 0 && config.getMaxSize() == 0) {
return createMap(name, config);
}
return createMapCache(name, config);
}
private Cache createMap(String name, CacheConfig config) {
RMap<Object, Object> map = RedisUtils.getClient().getMap(name);
Cache cache = new CaffeineCacheDecorator(name, new RedissonCache(map, allowNullValues));
if (transactionAware) {
cache = new TransactionAwareCacheDecorator(cache);
}
Cache oldCache = instanceMap.putIfAbsent(name, cache);
if (oldCache != null) {
cache = oldCache;
}
return cache;
}
private Cache createMapCache(String name, CacheConfig config) {
RMapCache<Object, Object> map = RedisUtils.getClient().getMapCache(name);
Cache cache = new CaffeineCacheDecorator(name, new RedissonCache(map, config, allowNullValues));
if (transactionAware) {
cache = new TransactionAwareCacheDecorator(cache);
}
Cache oldCache = instanceMap.putIfAbsent(name, cache);
if (oldCache != null) {
cache = oldCache;
} else {
map.setMaxSize(config.getMaxSize());
}
return cache;
}
@Override
public Collection<String> getCacheNames() {
return Collections.unmodifiableSet(configMap.keySet());
}
}

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