commit a2d1fcde12e64664376f743d9491ea7f85da5dec Author: xuelijun <977662702@qq.com> Date: Sat Sep 20 18:41:07 2025 +0800 初始化 diff --git a/.cursor/rules/1.mdc b/.cursor/rules/1.mdc new file mode 100644 index 0000000..19111e9 --- /dev/null +++ b/.cursor/rules/1.mdc @@ -0,0 +1,48 @@ +--- +description: +globs: +alwaysApply: false +--- +--- +description: +globs: +alwaysApply: false +--- + +# Your rule content +#角色 +你是一名精通开发的高级工程师,拥有10年以上的应用开发经验,熟悉*等开发工具和技术栈。 +你的任务是帮助用户设计和开发易用且易于推护的 *** 应用。始终遵循最佳实践,并坚持干净代码和健壮架构的原则。 + +#目标 +你的目标是以用户容易理解的方式帮助他们完成“应用的设计和开发工作,确保应用功能完善、性能优异、用户体验良好。 + +#要求 +在理解用户需求、设计UI、编写代码、解决问题和项目选代优化时,你应该始终遵循以下原则: + + +##需求理解 +-充分理解用户需求,站在用户角度思考,分析需求是否存在缺漏,并与用户讨论完善需求; +-选择最简单的解决方案来满足用户需求,避免过度设计。 +##UI和样式设计 +-使用现代UI框架进行样式设计(例如***,这里可以根据不同开发项目仔纽展开,比如使用哪些视觉规范或者UI框架,没有的话也可以不用过多展开); +-在不同平台上实现一致的设计和响应式模式 +##代码编写 +技术选型:根据项目需求选择合适的技术栈(例如***,这里需要仔细展开,比如介招某个技术栈用在什么地方,以及要遵循什么最佳实践) +代码结构:强调代码的清晰性、模块化、可维护性,遵循最佳实践(如DRY原则、最小权限原则、响应式设计等) +-代码安全性:在编写代码时,始终考虑安全性,避免引入漏洞,确保用户输入的安全处理 +-性能优化:优化代码的性能,减少资源占用,提升加载速度,确保项目的高效运行 +-测试与文档:编写单元测试,确保代码的健壮性,并提供清晰的中文注释和文档。方便后续阅读和维护 +##问题解决 +-全面阅读相关代码,理解***应用的工作原理 +-根据用户的反馈分析问题的原因,提出解决问题的思路 +-确保每次代码变更不会破坏现有功能,且尽可能保持最小的改动 +##迭代优化 +与用户保持密切沟通,根据反读调整功能和设计,确保应用符合用户需求 +在不确定需求时,主动询问用户以澄清需求或技术细节 +##方法论 +-系统2思维:以分析严谨的方式解决问题。将需求分解为更小、可管理的部分,并在实施前仔细考虑每一步 +思维树:评估多种可能的解决方案及其后果。使用结构化的方法探索不同的路径。并选择最优的解决方案 +-选代改进:在最终确定代码之前,考虑改进、边缘情况和优化。通过潜在增强的迭代,确保最终解决方案是健壮的 + + diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..fc5b4bf --- /dev/null +++ b/.gitignore @@ -0,0 +1,76 @@ + +# 查看更多 .gitignore 配置 -> https://help.github.com/articles/ignoring-files/ + +target/ +!.mvn/wrapper/maven-wrapper.jar + +.flattened-pom.xml + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr +*.class +target/* + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +/build/ +.idea + + + +### admin-web ### + +# dependencies +**/node_modules + +# roadhog-api-doc ignore +/src/utils/request-temp.js +_roadhog-api-doc + +# production +/dist +/.vscode + +# misc +.DS_Store +npm-debug.log* +yarn-error.log + +/coverage +.idea +yarn.lock +package-lock.json +*bak +.vscode + +# visual studio code +.history +*.log + +functions/mock +.temp/** + +# umi +.umi +.umi-production + +# screenshot +screenshot +.firebase +sessionStore diff --git a/.lingma/rules/project_rule.md b/.lingma/rules/project_rule.md new file mode 100644 index 0000000..aea8af4 --- /dev/null +++ b/.lingma/rules/project_rule.md @@ -0,0 +1,2 @@ +**添加规则文件可帮助模型精准理解你的编码偏好,如框架、代码风格等** +**规则文件只对当前工程生效,单文件限制10000字符。如果无需将该文件提交到远程 Git 仓库,请将其添加到 .gitignore** \ No newline at end of file diff --git a/LOG_FILE_IS_UNDEFINED b/LOG_FILE_IS_UNDEFINED new file mode 100644 index 0000000..1d129c1 --- /dev/null +++ b/LOG_FILE_IS_UNDEFINED @@ -0,0 +1,116 @@ +2025-09-15 10:17:42.991 |  INFO 4200 | main [TID: N/A] c.t.c.gateway.GatewayServerApplication  | Starting GatewayServerApplication using Java 17.0.14 with PID 4200 (D:\work\houtai\new_work\new-tashow-platform\tashow-platform\tashow-gateway\target\classes started by Administrator in D:\work\houtai\new_work\new-tashow-platform\tashow-platform) +2025-09-15 10:17:42.992 |  INFO 4200 | main [TID: N/A] c.t.c.gateway.GatewayServerApplication  | The following 1 profile is active: "local" +2025-09-15 10:17:43.019 | ERROR 4200 | main [TID: N/A] c.a.c.n.c.NacosConfigDataLoader  | Error getting properties from nacos: NacosConfigDataResource{properties=NacosConfigProperties{serverAddr='43.139.42.137:8848', encode='null', group='DEFAULT_GROUP', prefix='null', fileExtension='properties', timeout=3000, maxRetry='null', configLongPollTimeout='null', configRetryTime='null', enableRemoteSyncConfig=false, endpoint='null', namespace='76667956-2ac2-4e05-906b-4bca4ebcc5f0', accessKey='null', secretKey='null', ramRoleName='null', contextPath='null', clusterName='null', name='null'', shares=null, extensions=null, refreshEnabled=true}, optional=true, profiles=[Profiles@77128dab active = '[local]', default = '[default]', accepted = '[local]'], config=NacosItemConfig{group='DEFAULT_GROUP', dataId='gateway-server-local.yaml', suffix='yaml', refreshEnabled=true, preference=null}} + +com.alibaba.nacos.api.exception.NacosException: http error, code=403,msg=user not found!,dataId=gateway-server-local.yaml,group=DEFAULT_GROUP,tenant=76667956-2ac2-4e05-906b-4bca4ebcc5f0 + at com.alibaba.nacos.client.config.impl.ClientWorker$ConfigRpcTransportClient.queryConfigInner(ClientWorker.java:1194) ~[nacos-client-2.4.2.jar:na] + at com.alibaba.nacos.client.config.impl.ClientWorker$ConfigRpcTransportClient.queryConfig(ClientWorker.java:1153) ~[nacos-client-2.4.2.jar:na] + at com.alibaba.nacos.client.config.impl.ClientWorker.getServerConfig(ClientWorker.java:474) ~[nacos-client-2.4.2.jar:na] + at com.alibaba.nacos.client.config.NacosConfigService.getConfigInner(NacosConfigService.java:188) ~[nacos-client-2.4.2.jar:na] + at com.alibaba.nacos.client.config.NacosConfigService.getConfig(NacosConfigService.java:99) ~[nacos-client-2.4.2.jar:na] + at com.alibaba.cloud.nacos.configdata.NacosConfigDataLoader.pullConfig(NacosConfigDataLoader.java:142) ~[spring-alibaba-nacos-config-2023.0.3.2.jar:2023.0.3.2] + at com.alibaba.cloud.nacos.configdata.NacosConfigDataLoader.doLoad(NacosConfigDataLoader.java:80) ~[spring-alibaba-nacos-config-2023.0.3.2.jar:2023.0.3.2] + at com.alibaba.cloud.nacos.configdata.NacosConfigDataLoader.load(NacosConfigDataLoader.java:67) ~[spring-alibaba-nacos-config-2023.0.3.2.jar:2023.0.3.2] + at com.alibaba.cloud.nacos.configdata.NacosConfigDataLoader.load(NacosConfigDataLoader.java:56) ~[spring-alibaba-nacos-config-2023.0.3.2.jar:2023.0.3.2] + at org.springframework.boot.context.config.ConfigDataLoaders.load(ConfigDataLoaders.java:96) ~[spring-boot-3.4.1.jar:3.4.1] + at org.springframework.boot.context.config.ConfigDataImporter.load(ConfigDataImporter.java:132) ~[spring-boot-3.4.1.jar:3.4.1] + at org.springframework.boot.context.config.ConfigDataImporter.resolveAndLoad(ConfigDataImporter.java:87) ~[spring-boot-3.4.1.jar:3.4.1] + at org.springframework.boot.context.config.ConfigDataEnvironmentContributors.withProcessedImports(ConfigDataEnvironmentContributors.java:121) ~[spring-boot-3.4.1.jar:3.4.1] + at org.springframework.boot.context.config.ConfigDataEnvironment.processWithProfiles(ConfigDataEnvironment.java:316) ~[spring-boot-3.4.1.jar:3.4.1] + at org.springframework.boot.context.config.ConfigDataEnvironment.processAndApply(ConfigDataEnvironment.java:237) ~[spring-boot-3.4.1.jar:3.4.1] + at org.springframework.boot.context.config.ConfigDataEnvironmentPostProcessor.postProcessEnvironment(ConfigDataEnvironmentPostProcessor.java:96) ~[spring-boot-3.4.1.jar:3.4.1] + at org.springframework.boot.context.config.ConfigDataEnvironmentPostProcessor.postProcessEnvironment(ConfigDataEnvironmentPostProcessor.java:89) ~[spring-boot-3.4.1.jar:3.4.1] + at org.springframework.boot.env.EnvironmentPostProcessorApplicationListener.onApplicationEnvironmentPreparedEvent(EnvironmentPostProcessorApplicationListener.java:132) ~[spring-boot-3.4.1.jar:3.4.1] + at org.springframework.boot.env.EnvironmentPostProcessorApplicationListener.onApplicationEvent(EnvironmentPostProcessorApplicationListener.java:115) ~[spring-boot-3.4.1.jar:3.4.1] + at org.springframework.context.event.SimpleApplicationEventMulticaster.doInvokeListener(SimpleApplicationEventMulticaster.java:185) ~[spring-context-6.2.1.jar:6.2.1] + at org.springframework.context.event.SimpleApplicationEventMulticaster.invokeListener(SimpleApplicationEventMulticaster.java:178) ~[spring-context-6.2.1.jar:6.2.1] + at org.springframework.context.event.SimpleApplicationEventMulticaster.multicastEvent(SimpleApplicationEventMulticaster.java:156) ~[spring-context-6.2.1.jar:6.2.1] + at org.springframework.context.event.SimpleApplicationEventMulticaster.multicastEvent(SimpleApplicationEventMulticaster.java:138) ~[spring-context-6.2.1.jar:6.2.1] + at org.springframework.boot.context.event.EventPublishingRunListener.multicastInitialEvent(EventPublishingRunListener.java:136) ~[spring-boot-3.4.1.jar:3.4.1] + at org.springframework.boot.context.event.EventPublishingRunListener.environmentPrepared(EventPublishingRunListener.java:81) ~[spring-boot-3.4.1.jar:3.4.1] + at org.springframework.boot.SpringApplicationRunListeners.lambda$environmentPrepared$2(SpringApplicationRunListeners.java:64) ~[spring-boot-3.4.1.jar:3.4.1] + at java.base/java.lang.Iterable.forEach(Iterable.java:75) ~[na:na] + at org.springframework.boot.SpringApplicationRunListeners.doWithListeners(SpringApplicationRunListeners.java:118) ~[spring-boot-3.4.1.jar:3.4.1] + at org.springframework.boot.SpringApplicationRunListeners.doWithListeners(SpringApplicationRunListeners.java:112) ~[spring-boot-3.4.1.jar:3.4.1] + at org.springframework.boot.SpringApplicationRunListeners.environmentPrepared(SpringApplicationRunListeners.java:63) ~[spring-boot-3.4.1.jar:3.4.1] + at org.springframework.boot.SpringApplication.prepareEnvironment(SpringApplication.java:353) ~[spring-boot-3.4.1.jar:3.4.1] + at org.springframework.boot.SpringApplication.run(SpringApplication.java:313) ~[spring-boot-3.4.1.jar:3.4.1] + at org.springframework.boot.SpringApplication.run(SpringApplication.java:1361) ~[spring-boot-3.4.1.jar:3.4.1] + at org.springframework.boot.SpringApplication.run(SpringApplication.java:1350) ~[spring-boot-3.4.1.jar:3.4.1] + at com.tashow.cloud.gateway.GatewayServerApplication.main(GatewayServerApplication.java:10) ~[classes/:na] + +2025-09-15 10:17:43.021 | ERROR 4200 | main [TID: N/A] c.a.c.n.c.NacosConfigDataLoader  | Error getting properties from nacos: NacosConfigDataResource{properties=NacosConfigProperties{serverAddr='43.139.42.137:8848', encode='null', group='DEFAULT_GROUP', prefix='null', fileExtension='properties', timeout=3000, maxRetry='null', configLongPollTimeout='null', configRetryTime='null', enableRemoteSyncConfig=false, endpoint='null', namespace='76667956-2ac2-4e05-906b-4bca4ebcc5f0', accessKey='null', secretKey='null', ramRoleName='null', contextPath='null', clusterName='null', name='null'', shares=null, extensions=null, refreshEnabled=true}, optional=true, profiles=[Profiles@77128dab active = '[local]', default = '[default]', accepted = '[local]'], config=NacosItemConfig{group='DEFAULT_GROUP', dataId='application.yaml', suffix='yaml', refreshEnabled=true, preference=null}} + +com.alibaba.nacos.api.exception.NacosException: http error, code=403,msg=user not found!,dataId=application.yaml,group=DEFAULT_GROUP,tenant=76667956-2ac2-4e05-906b-4bca4ebcc5f0 + at com.alibaba.nacos.client.config.impl.ClientWorker$ConfigRpcTransportClient.queryConfigInner(ClientWorker.java:1194) ~[nacos-client-2.4.2.jar:na] + at com.alibaba.nacos.client.config.impl.ClientWorker$ConfigRpcTransportClient.queryConfig(ClientWorker.java:1153) ~[nacos-client-2.4.2.jar:na] + at com.alibaba.nacos.client.config.impl.ClientWorker.getServerConfig(ClientWorker.java:474) ~[nacos-client-2.4.2.jar:na] + at com.alibaba.nacos.client.config.NacosConfigService.getConfigInner(NacosConfigService.java:188) ~[nacos-client-2.4.2.jar:na] + at com.alibaba.nacos.client.config.NacosConfigService.getConfig(NacosConfigService.java:99) ~[nacos-client-2.4.2.jar:na] + at com.alibaba.cloud.nacos.configdata.NacosConfigDataLoader.pullConfig(NacosConfigDataLoader.java:142) ~[spring-alibaba-nacos-config-2023.0.3.2.jar:2023.0.3.2] + at com.alibaba.cloud.nacos.configdata.NacosConfigDataLoader.doLoad(NacosConfigDataLoader.java:80) ~[spring-alibaba-nacos-config-2023.0.3.2.jar:2023.0.3.2] + at com.alibaba.cloud.nacos.configdata.NacosConfigDataLoader.load(NacosConfigDataLoader.java:67) ~[spring-alibaba-nacos-config-2023.0.3.2.jar:2023.0.3.2] + at com.alibaba.cloud.nacos.configdata.NacosConfigDataLoader.load(NacosConfigDataLoader.java:56) ~[spring-alibaba-nacos-config-2023.0.3.2.jar:2023.0.3.2] + at org.springframework.boot.context.config.ConfigDataLoaders.load(ConfigDataLoaders.java:96) ~[spring-boot-3.4.1.jar:3.4.1] + at org.springframework.boot.context.config.ConfigDataImporter.load(ConfigDataImporter.java:132) ~[spring-boot-3.4.1.jar:3.4.1] + at org.springframework.boot.context.config.ConfigDataImporter.resolveAndLoad(ConfigDataImporter.java:87) ~[spring-boot-3.4.1.jar:3.4.1] + at org.springframework.boot.context.config.ConfigDataEnvironmentContributors.withProcessedImports(ConfigDataEnvironmentContributors.java:121) ~[spring-boot-3.4.1.jar:3.4.1] + at org.springframework.boot.context.config.ConfigDataEnvironment.processWithProfiles(ConfigDataEnvironment.java:316) ~[spring-boot-3.4.1.jar:3.4.1] + at org.springframework.boot.context.config.ConfigDataEnvironment.processAndApply(ConfigDataEnvironment.java:237) ~[spring-boot-3.4.1.jar:3.4.1] + at org.springframework.boot.context.config.ConfigDataEnvironmentPostProcessor.postProcessEnvironment(ConfigDataEnvironmentPostProcessor.java:96) ~[spring-boot-3.4.1.jar:3.4.1] + at org.springframework.boot.context.config.ConfigDataEnvironmentPostProcessor.postProcessEnvironment(ConfigDataEnvironmentPostProcessor.java:89) ~[spring-boot-3.4.1.jar:3.4.1] + at org.springframework.boot.env.EnvironmentPostProcessorApplicationListener.onApplicationEnvironmentPreparedEvent(EnvironmentPostProcessorApplicationListener.java:132) ~[spring-boot-3.4.1.jar:3.4.1] + at org.springframework.boot.env.EnvironmentPostProcessorApplicationListener.onApplicationEvent(EnvironmentPostProcessorApplicationListener.java:115) ~[spring-boot-3.4.1.jar:3.4.1] + at org.springframework.context.event.SimpleApplicationEventMulticaster.doInvokeListener(SimpleApplicationEventMulticaster.java:185) ~[spring-context-6.2.1.jar:6.2.1] + at org.springframework.context.event.SimpleApplicationEventMulticaster.invokeListener(SimpleApplicationEventMulticaster.java:178) ~[spring-context-6.2.1.jar:6.2.1] + at org.springframework.context.event.SimpleApplicationEventMulticaster.multicastEvent(SimpleApplicationEventMulticaster.java:156) ~[spring-context-6.2.1.jar:6.2.1] + at org.springframework.context.event.SimpleApplicationEventMulticaster.multicastEvent(SimpleApplicationEventMulticaster.java:138) ~[spring-context-6.2.1.jar:6.2.1] + at org.springframework.boot.context.event.EventPublishingRunListener.multicastInitialEvent(EventPublishingRunListener.java:136) ~[spring-boot-3.4.1.jar:3.4.1] + at org.springframework.boot.context.event.EventPublishingRunListener.environmentPrepared(EventPublishingRunListener.java:81) ~[spring-boot-3.4.1.jar:3.4.1] + at org.springframework.boot.SpringApplicationRunListeners.lambda$environmentPrepared$2(SpringApplicationRunListeners.java:64) ~[spring-boot-3.4.1.jar:3.4.1] + at java.base/java.lang.Iterable.forEach(Iterable.java:75) ~[na:na] + at org.springframework.boot.SpringApplicationRunListeners.doWithListeners(SpringApplicationRunListeners.java:118) ~[spring-boot-3.4.1.jar:3.4.1] + at org.springframework.boot.SpringApplicationRunListeners.doWithListeners(SpringApplicationRunListeners.java:112) ~[spring-boot-3.4.1.jar:3.4.1] + at org.springframework.boot.SpringApplicationRunListeners.environmentPrepared(SpringApplicationRunListeners.java:63) ~[spring-boot-3.4.1.jar:3.4.1] + at org.springframework.boot.SpringApplication.prepareEnvironment(SpringApplication.java:353) ~[spring-boot-3.4.1.jar:3.4.1] + at org.springframework.boot.SpringApplication.run(SpringApplication.java:313) ~[spring-boot-3.4.1.jar:3.4.1] + at org.springframework.boot.SpringApplication.run(SpringApplication.java:1361) ~[spring-boot-3.4.1.jar:3.4.1] + at org.springframework.boot.SpringApplication.run(SpringApplication.java:1350) ~[spring-boot-3.4.1.jar:3.4.1] + at com.tashow.cloud.gateway.GatewayServerApplication.main(GatewayServerApplication.java:10) ~[classes/:na] + +2025-09-15 10:17:43.819 |  INFO 4200 | main [TID: N/A] o.s.cloud.context.scope.GenericScope  | BeanFactory id=855d66cd-16da-30e9-a12a-5b2f1bd783f0 +2025-09-15 10:17:44.369 |  INFO 4200 | main [TID: N/A] c.t.c.g.j.JacksonAutoConfiguration  | [init][初始化 JsonUtils 成功] +2025-09-15 10:17:44.743 |  INFO 4200 | main [TID: N/A] o.s.c.g.r.RouteDefinitionRouteLocator  | Loaded RoutePredicateFactory [After] +2025-09-15 10:17:44.743 |  INFO 4200 | main [TID: N/A] o.s.c.g.r.RouteDefinitionRouteLocator  | Loaded RoutePredicateFactory [Before] +2025-09-15 10:17:44.743 |  INFO 4200 | main [TID: N/A] o.s.c.g.r.RouteDefinitionRouteLocator  | Loaded RoutePredicateFactory [Between] +2025-09-15 10:17:44.743 |  INFO 4200 | main [TID: N/A] o.s.c.g.r.RouteDefinitionRouteLocator  | Loaded RoutePredicateFactory [Cookie] +2025-09-15 10:17:44.743 |  INFO 4200 | main [TID: N/A] o.s.c.g.r.RouteDefinitionRouteLocator  | Loaded RoutePredicateFactory [Header] +2025-09-15 10:17:44.743 |  INFO 4200 | main [TID: N/A] o.s.c.g.r.RouteDefinitionRouteLocator  | Loaded RoutePredicateFactory [Host] +2025-09-15 10:17:44.743 |  INFO 4200 | main [TID: N/A] o.s.c.g.r.RouteDefinitionRouteLocator  | Loaded RoutePredicateFactory [Method] +2025-09-15 10:17:44.743 |  INFO 4200 | main [TID: N/A] o.s.c.g.r.RouteDefinitionRouteLocator  | Loaded RoutePredicateFactory [Path] +2025-09-15 10:17:44.743 |  INFO 4200 | main [TID: N/A] o.s.c.g.r.RouteDefinitionRouteLocator  | Loaded RoutePredicateFactory [Query] +2025-09-15 10:17:44.743 |  INFO 4200 | main [TID: N/A] o.s.c.g.r.RouteDefinitionRouteLocator  | Loaded RoutePredicateFactory [ReadBody] +2025-09-15 10:17:44.743 |  INFO 4200 | main [TID: N/A] o.s.c.g.r.RouteDefinitionRouteLocator  | Loaded RoutePredicateFactory [RemoteAddr] +2025-09-15 10:17:44.743 |  INFO 4200 | main [TID: N/A] o.s.c.g.r.RouteDefinitionRouteLocator  | Loaded RoutePredicateFactory [XForwardedRemoteAddr] +2025-09-15 10:17:44.743 |  INFO 4200 | main [TID: N/A] o.s.c.g.r.RouteDefinitionRouteLocator  | Loaded RoutePredicateFactory [Weight] +2025-09-15 10:17:44.743 |  INFO 4200 | main [TID: N/A] o.s.c.g.r.RouteDefinitionRouteLocator  | Loaded RoutePredicateFactory [CloudFoundryRouteService] +2025-09-15 10:17:45.241 |  INFO 4200 | main [TID: N/A] o.s.b.a.e.web.EndpointLinksResolver  | Exposing 1 endpoint beneath base path '/actuator' +2025-09-15 10:17:46.618 |  WARN 4200 | main [TID: N/A] iguration$LoadBalancerCaffeineWarnLogger | Spring Cloud LoadBalancer is currently working with the default cache. While this cache implementation is useful for development and tests, it's recommended to use Caffeine cache in production.You can switch to using Caffeine cache, by adding it and org.springframework.cache.caffeine.CaffeineCacheManager to the classpath. +2025-09-15 10:17:46.993 |  INFO 4200 | main [TID: N/A] o.s.b.web.embedded.netty.NettyWebServer  | Netty started on port 48080 (http) +2025-09-15 10:17:47.072 |  INFO 4200 | main [TID: N/A] c.a.n.p.a.s.c.ClientAuthPluginManager  | [ClientAuthPluginManager] Load ClientAuthService com.alibaba.nacos.client.auth.impl.NacosClientAuthServiceImpl success. +2025-09-15 10:17:47.072 |  INFO 4200 | main [TID: N/A] c.a.n.p.a.s.c.ClientAuthPluginManager  | [ClientAuthPluginManager] Load ClientAuthService com.alibaba.nacos.client.auth.ram.RamClientAuthServiceImpl success. +2025-09-15 10:17:47.403 |  INFO 4200 | main [TID: N/A] c.a.c.n.registry.NacosServiceRegistry  | nacos registry, DEFAULT_GROUP gateway-server 192.168.1.114:48080 register finished +2025-09-15 10:17:47.433 |  INFO 4200 | main [TID: N/A] c.t.c.gateway.GatewayServerApplication  | Started GatewayServerApplication in 6.966 seconds (process running for 7.886) +2025-09-15 10:17:48.441 |  INFO 4200 | pool-8-thread-1 [TID: N/A] c.t.c.g.util.BannerApplicationRunner  | +---------------------------------------------------------- + 项目启动成功! + 接口文档: https://cloud.iocoder.cn/api-doc/ + ---------------------------------------------------------- +2025-09-15 10:22:50.103 |  WARN 4200 | Thread-1 [TID: N/A] c.a.n.common.executor.ThreadPoolManager  | [ThreadPoolManager] Start destroying ThreadPool +2025-09-15 10:22:50.104 |  WARN 4200 | Thread-6 [TID: N/A] c.a.n.common.http.HttpClientBeanHolder  | [HttpClientBeanHolder] Start destroying common HttpClient +2025-09-15 10:22:50.104 |  WARN 4200 | Thread-1 [TID: N/A] c.a.n.common.executor.ThreadPoolManager  | [ThreadPoolManager] Destruction of the end +2025-09-15 10:22:50.104 |  WARN 4200 | Thread-8 [TID: N/A] c.a.nacos.common.notify.NotifyCenter  | [NotifyCenter] Start destroying Publisher +2025-09-15 10:22:50.104 |  WARN 4200 | Thread-8 [TID: N/A] c.a.nacos.common.notify.NotifyCenter  | [NotifyCenter] Destruction of the end +2025-09-15 10:22:50.105 |  WARN 4200 | Thread-6 [TID: N/A] c.a.n.common.http.HttpClientBeanHolder  | [HttpClientBeanHolder] Destruction of the end +2025-09-15 10:22:50.108 |  INFO 4200 | SpringApplicationShutdownHook [TID: N/A] o.s.b.w.embedded.netty.GracefulShutdown  | Commencing graceful shutdown. Waiting for active requests to complete +2025-09-15 10:22:50.110 |  INFO 4200 | netty-shutdown [TID: N/A] o.s.b.w.embedded.netty.GracefulShutdown  | Graceful shutdown complete diff --git a/logs/gateway-server.log.2025-05-22.0.gz b/logs/gateway-server.log.2025-05-22.0.gz new file mode 100644 index 0000000..4e92a6e Binary files /dev/null and b/logs/gateway-server.log.2025-05-22.0.gz differ diff --git a/logs/gateway-server.log.2025-05-23.0.gz b/logs/gateway-server.log.2025-05-23.0.gz new file mode 100644 index 0000000..da74149 Binary files /dev/null and b/logs/gateway-server.log.2025-05-23.0.gz differ diff --git a/logs/gateway-server.log.2025-08-21.0.gz b/logs/gateway-server.log.2025-08-21.0.gz new file mode 100644 index 0000000..a9790e5 Binary files /dev/null and b/logs/gateway-server.log.2025-08-21.0.gz differ diff --git a/logs/gateway-server.log.2025-08-21.1.gz b/logs/gateway-server.log.2025-08-21.1.gz new file mode 100644 index 0000000..f7f1ae5 Binary files /dev/null and b/logs/gateway-server.log.2025-08-21.1.gz differ diff --git a/logs/gateway-server.log.2025-08-22.0.gz b/logs/gateway-server.log.2025-08-22.0.gz new file mode 100644 index 0000000..c5edb75 Binary files /dev/null and b/logs/gateway-server.log.2025-08-22.0.gz differ diff --git a/logs/gateway-server.log.2025-08-25.0.gz b/logs/gateway-server.log.2025-08-25.0.gz new file mode 100644 index 0000000..4482e85 Binary files /dev/null and b/logs/gateway-server.log.2025-08-25.0.gz differ diff --git a/logs/gateway-server.log.2025-08-25.1.gz b/logs/gateway-server.log.2025-08-25.1.gz new file mode 100644 index 0000000..c2cac41 Binary files /dev/null and b/logs/gateway-server.log.2025-08-25.1.gz differ diff --git a/logs/gateway-server.log.2025-08-26.0.gz b/logs/gateway-server.log.2025-08-26.0.gz new file mode 100644 index 0000000..1829332 Binary files /dev/null and b/logs/gateway-server.log.2025-08-26.0.gz differ diff --git a/logs/gateway-server.log.2025-08-26.1.gz b/logs/gateway-server.log.2025-08-26.1.gz new file mode 100644 index 0000000..31796ec Binary files /dev/null and b/logs/gateway-server.log.2025-08-26.1.gz differ diff --git a/logs/gateway-server.log.2025-08-26.2.gz b/logs/gateway-server.log.2025-08-26.2.gz new file mode 100644 index 0000000..09b55ea Binary files /dev/null and b/logs/gateway-server.log.2025-08-26.2.gz differ diff --git a/logs/gateway-server.log.2025-08-26.3.gz b/logs/gateway-server.log.2025-08-26.3.gz new file mode 100644 index 0000000..ac748e1 Binary files /dev/null and b/logs/gateway-server.log.2025-08-26.3.gz differ diff --git a/logs/gateway-server.log.2025-08-27.0.gz b/logs/gateway-server.log.2025-08-27.0.gz new file mode 100644 index 0000000..fd2aa89 Binary files /dev/null and b/logs/gateway-server.log.2025-08-27.0.gz differ diff --git a/logs/gateway-server.log.2025-08-28.0.gz b/logs/gateway-server.log.2025-08-28.0.gz new file mode 100644 index 0000000..5770977 Binary files /dev/null and b/logs/gateway-server.log.2025-08-28.0.gz differ diff --git a/logs/gateway-server.log.2025-08-29.0.gz b/logs/gateway-server.log.2025-08-29.0.gz new file mode 100644 index 0000000..62d15a0 Binary files /dev/null and b/logs/gateway-server.log.2025-08-29.0.gz differ diff --git a/logs/gateway-server.log.2025-09-01.0.gz b/logs/gateway-server.log.2025-09-01.0.gz new file mode 100644 index 0000000..2a2d5a0 Binary files /dev/null and b/logs/gateway-server.log.2025-09-01.0.gz differ diff --git a/logs/gateway-server.log.2025-09-02.0.gz b/logs/gateway-server.log.2025-09-02.0.gz new file mode 100644 index 0000000..c6c00c8 Binary files /dev/null and b/logs/gateway-server.log.2025-09-02.0.gz differ diff --git a/logs/gateway-server.log.2025-09-03.0.gz b/logs/gateway-server.log.2025-09-03.0.gz new file mode 100644 index 0000000..0bc0cc3 Binary files /dev/null and b/logs/gateway-server.log.2025-09-03.0.gz differ diff --git a/logs/gateway-server.log.2025-09-04.0.gz b/logs/gateway-server.log.2025-09-04.0.gz new file mode 100644 index 0000000..f2a077e Binary files /dev/null and b/logs/gateway-server.log.2025-09-04.0.gz differ diff --git a/logs/gateway-server.log.2025-09-05.0.gz b/logs/gateway-server.log.2025-09-05.0.gz new file mode 100644 index 0000000..998c541 Binary files /dev/null and b/logs/gateway-server.log.2025-09-05.0.gz differ diff --git a/logs/gateway-server.log.2025-09-09.0.gz b/logs/gateway-server.log.2025-09-09.0.gz new file mode 100644 index 0000000..89d297f Binary files /dev/null and b/logs/gateway-server.log.2025-09-09.0.gz differ diff --git a/logs/gateway-server.log.2025-09-10.0.gz b/logs/gateway-server.log.2025-09-10.0.gz new file mode 100644 index 0000000..66c4a15 Binary files /dev/null and b/logs/gateway-server.log.2025-09-10.0.gz differ diff --git a/logs/gateway-server.log.2025-09-11.0.gz b/logs/gateway-server.log.2025-09-11.0.gz new file mode 100644 index 0000000..fed0263 Binary files /dev/null and b/logs/gateway-server.log.2025-09-11.0.gz differ diff --git a/logs/gateway-server.log.2025-09-12.0.gz b/logs/gateway-server.log.2025-09-12.0.gz new file mode 100644 index 0000000..85e97b3 Binary files /dev/null and b/logs/gateway-server.log.2025-09-12.0.gz differ diff --git a/logs/gateway-server.log.2025-09-13.0.gz b/logs/gateway-server.log.2025-09-13.0.gz new file mode 100644 index 0000000..af0e577 Binary files /dev/null and b/logs/gateway-server.log.2025-09-13.0.gz differ diff --git a/logs/gateway-server.log.2025-09-15.0.gz b/logs/gateway-server.log.2025-09-15.0.gz new file mode 100644 index 0000000..d885383 Binary files /dev/null and b/logs/gateway-server.log.2025-09-15.0.gz differ diff --git a/logs/gateway-server.log.2025-09-16.0.gz b/logs/gateway-server.log.2025-09-16.0.gz new file mode 100644 index 0000000..5742ffd Binary files /dev/null and b/logs/gateway-server.log.2025-09-16.0.gz differ diff --git a/logs/gateway-server.log.2025-09-17.0.gz b/logs/gateway-server.log.2025-09-17.0.gz new file mode 100644 index 0000000..f8b1f91 Binary files /dev/null and b/logs/gateway-server.log.2025-09-17.0.gz differ diff --git a/logs/gateway-server.log.2025-09-18.0.gz b/logs/gateway-server.log.2025-09-18.0.gz new file mode 100644 index 0000000..7f83e70 Binary files /dev/null and b/logs/gateway-server.log.2025-09-18.0.gz differ diff --git a/logs/gateway-server.log.2025-09-19.0.gz b/logs/gateway-server.log.2025-09-19.0.gz new file mode 100644 index 0000000..36663a3 Binary files /dev/null and b/logs/gateway-server.log.2025-09-19.0.gz differ diff --git a/logs/gateway-server.log.2025-09-19.1.gz b/logs/gateway-server.log.2025-09-19.1.gz new file mode 100644 index 0000000..e8c6924 Binary files /dev/null and b/logs/gateway-server.log.2025-09-19.1.gz differ diff --git a/logs/infra-server.log.2025-07-25.0.gz b/logs/infra-server.log.2025-07-25.0.gz new file mode 100644 index 0000000..16fd04f Binary files /dev/null and b/logs/infra-server.log.2025-07-25.0.gz differ diff --git a/logs/infra-server.log.2025-07-28.0.gz b/logs/infra-server.log.2025-07-28.0.gz new file mode 100644 index 0000000..1aa7573 Binary files /dev/null and b/logs/infra-server.log.2025-07-28.0.gz differ diff --git a/logs/infra-server.log.2025-07-29.0.gz b/logs/infra-server.log.2025-07-29.0.gz new file mode 100644 index 0000000..93eba2e Binary files /dev/null and b/logs/infra-server.log.2025-07-29.0.gz differ diff --git a/logs/infra-server.log.2025-08-01.0.gz b/logs/infra-server.log.2025-08-01.0.gz new file mode 100644 index 0000000..7052451 Binary files /dev/null and b/logs/infra-server.log.2025-08-01.0.gz differ diff --git a/logs/infra-server.log.2025-08-02.0.gz b/logs/infra-server.log.2025-08-02.0.gz new file mode 100644 index 0000000..fecb4c0 Binary files /dev/null and b/logs/infra-server.log.2025-08-02.0.gz differ diff --git a/logs/infra-server.log.2025-08-06.0.gz b/logs/infra-server.log.2025-08-06.0.gz new file mode 100644 index 0000000..e787a34 Binary files /dev/null and b/logs/infra-server.log.2025-08-06.0.gz differ diff --git a/logs/infra-server.log.2025-08-07.0.gz b/logs/infra-server.log.2025-08-07.0.gz new file mode 100644 index 0000000..193f328 Binary files /dev/null and b/logs/infra-server.log.2025-08-07.0.gz differ diff --git a/logs/infra-server.log.2025-08-11.0.gz b/logs/infra-server.log.2025-08-11.0.gz new file mode 100644 index 0000000..c4ca1f4 Binary files /dev/null and b/logs/infra-server.log.2025-08-11.0.gz differ diff --git a/logs/system-server.log.2025-05-22.0.gz b/logs/system-server.log.2025-05-22.0.gz new file mode 100644 index 0000000..63e381e Binary files /dev/null and b/logs/system-server.log.2025-05-22.0.gz differ diff --git a/logs/system-server.log.2025-05-23.0.gz b/logs/system-server.log.2025-05-23.0.gz new file mode 100644 index 0000000..7015e55 Binary files /dev/null and b/logs/system-server.log.2025-05-23.0.gz differ diff --git a/logs/system-server.log.2025-08-21.0.gz b/logs/system-server.log.2025-08-21.0.gz new file mode 100644 index 0000000..392bc09 Binary files /dev/null and b/logs/system-server.log.2025-08-21.0.gz differ diff --git a/logs/system-server.log.2025-08-22.0.gz b/logs/system-server.log.2025-08-22.0.gz new file mode 100644 index 0000000..8801757 Binary files /dev/null and b/logs/system-server.log.2025-08-22.0.gz differ diff --git a/logs/system-server.log.2025-08-25.0.gz b/logs/system-server.log.2025-08-25.0.gz new file mode 100644 index 0000000..e819a8d Binary files /dev/null and b/logs/system-server.log.2025-08-25.0.gz differ diff --git a/logs/system-server.log.2025-08-26.0.gz b/logs/system-server.log.2025-08-26.0.gz new file mode 100644 index 0000000..6e29a3f Binary files /dev/null and b/logs/system-server.log.2025-08-26.0.gz differ diff --git a/logs/system-server.log.2025-08-27.0.gz b/logs/system-server.log.2025-08-27.0.gz new file mode 100644 index 0000000..992a573 Binary files /dev/null and b/logs/system-server.log.2025-08-27.0.gz differ diff --git a/logs/system-server.log.2025-08-28.0.gz b/logs/system-server.log.2025-08-28.0.gz new file mode 100644 index 0000000..a632c57 Binary files /dev/null and b/logs/system-server.log.2025-08-28.0.gz differ diff --git a/logs/system-server.log.2025-08-29.0.gz b/logs/system-server.log.2025-08-29.0.gz new file mode 100644 index 0000000..220ea2e Binary files /dev/null and b/logs/system-server.log.2025-08-29.0.gz differ diff --git a/logs/system-server.log.2025-09-01.0.gz b/logs/system-server.log.2025-09-01.0.gz new file mode 100644 index 0000000..755ebff Binary files /dev/null and b/logs/system-server.log.2025-09-01.0.gz differ diff --git a/logs/system-server.log.2025-09-02.0.gz b/logs/system-server.log.2025-09-02.0.gz new file mode 100644 index 0000000..b3119c2 Binary files /dev/null and b/logs/system-server.log.2025-09-02.0.gz differ diff --git a/logs/system-server.log.2025-09-03.0.gz b/logs/system-server.log.2025-09-03.0.gz new file mode 100644 index 0000000..caaa6ad Binary files /dev/null and b/logs/system-server.log.2025-09-03.0.gz differ diff --git a/logs/system-server.log.2025-09-04.0.gz b/logs/system-server.log.2025-09-04.0.gz new file mode 100644 index 0000000..52e9ce8 Binary files /dev/null and b/logs/system-server.log.2025-09-04.0.gz differ diff --git a/logs/system-server.log.2025-09-05.0.gz b/logs/system-server.log.2025-09-05.0.gz new file mode 100644 index 0000000..79bf6e9 Binary files /dev/null and b/logs/system-server.log.2025-09-05.0.gz differ diff --git a/logs/system-server.log.2025-09-09.0.gz b/logs/system-server.log.2025-09-09.0.gz new file mode 100644 index 0000000..e4c8af5 Binary files /dev/null and b/logs/system-server.log.2025-09-09.0.gz differ diff --git a/logs/system-server.log.2025-09-10.0.gz b/logs/system-server.log.2025-09-10.0.gz new file mode 100644 index 0000000..271c45e Binary files /dev/null and b/logs/system-server.log.2025-09-10.0.gz differ diff --git a/logs/system-server.log.2025-09-11.0.gz b/logs/system-server.log.2025-09-11.0.gz new file mode 100644 index 0000000..9a37465 Binary files /dev/null and b/logs/system-server.log.2025-09-11.0.gz differ diff --git a/logs/system-server.log.2025-09-12.0.gz b/logs/system-server.log.2025-09-12.0.gz new file mode 100644 index 0000000..b516eb4 Binary files /dev/null and b/logs/system-server.log.2025-09-12.0.gz differ diff --git a/logs/system-server.log.2025-09-13.0.gz b/logs/system-server.log.2025-09-13.0.gz new file mode 100644 index 0000000..aca9680 Binary files /dev/null and b/logs/system-server.log.2025-09-13.0.gz differ diff --git a/logs/system-server.log.2025-09-15.0.gz b/logs/system-server.log.2025-09-15.0.gz new file mode 100644 index 0000000..59cfcfd Binary files /dev/null and b/logs/system-server.log.2025-09-15.0.gz differ diff --git a/logs/system-server.log.2025-09-16.0.gz b/logs/system-server.log.2025-09-16.0.gz new file mode 100644 index 0000000..7fc9dfc Binary files /dev/null and b/logs/system-server.log.2025-09-16.0.gz differ diff --git a/logs/system-server.log.2025-09-17.0.gz b/logs/system-server.log.2025-09-17.0.gz new file mode 100644 index 0000000..5e00c39 Binary files /dev/null and b/logs/system-server.log.2025-09-17.0.gz differ diff --git a/logs/system-server.log.2025-09-18.0.gz b/logs/system-server.log.2025-09-18.0.gz new file mode 100644 index 0000000..03ef293 Binary files /dev/null and b/logs/system-server.log.2025-09-18.0.gz differ diff --git a/logs/system-server.log.2025-09-19.0.gz b/logs/system-server.log.2025-09-19.0.gz new file mode 100644 index 0000000..3bc5e25 Binary files /dev/null and b/logs/system-server.log.2025-09-19.0.gz differ diff --git a/logs/system-server.log.2025-09-20.0.gz b/logs/system-server.log.2025-09-20.0.gz new file mode 100644 index 0000000..26498ba Binary files /dev/null and b/logs/system-server.log.2025-09-20.0.gz differ diff --git a/lombok.config b/lombok.config new file mode 100644 index 0000000..a8e8ce6 --- /dev/null +++ b/lombok.config @@ -0,0 +1,4 @@ +config.stopBubbling = true +lombok.tostring.callsuper=CALL +lombok.equalsandhashcode.callsuper=CALL +lombok.accessors.chain=true diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..14c7e35 --- /dev/null +++ b/pom.xml @@ -0,0 +1,155 @@ + + + 4.0.0 + com.tashow.cloud + tashow-platform + ${revision} + pom + + tashow-dependencies + tashow-framework + tashow-module + tashow-gateway + tashow-feign + tashow-sdk + + + ${project.artifactId} + bydj项目基础脚手架 + + + 1.0.0 + + 17 + ${java.version} + ${java.version} + 3.2.2 + 3.13.0 + 1.6.0 + + 1.18.36 + 3.4.1 + 1.6.3 + UTF-8 + + + + + + com.tashow.cloud + tashow-dependencies + ${revision} + pom + import + + + + + + + + + + + org.apache.maven.plugins + maven-surefire-plugin + ${maven-surefire-plugin.version} + + + + + org.apache.maven.plugins + maven-compiler-plugin + ${maven-compiler-plugin.version} + + + + org.springframework.boot + spring-boot-configuration-processor + ${spring.boot.version} + + + org.projectlombok + lombok + ${lombok.version} + + + org.mapstruct + mapstruct-processor + ${mapstruct.version} + + + + false + + -parameters + + + + + + + + + + org.codehaus.mojo + flatten-maven-plugin + ${flatten-maven-plugin.version} + + oss + true + + + + + flatten + + flatten + process-resources + + + + clean + + flatten.clean + clean + + + + + + + + + + huaweicloud + huawei + https://mirrors.huaweicloud.com/repository/maven/ + + + aliyunmaven + aliyun + https://maven.aliyun.com/repository/public + + + + spring-milestones + Spring Milestones + https://repo.spring.io/milestone + + false + + + + spring-snapshots + Spring Snapshots + https://repo.spring.io/snapshot + + false + + + + + diff --git a/sql/db2/README.md b/sql/db2/README.md new file mode 100644 index 0000000..d9a95a8 --- /dev/null +++ b/sql/db2/README.md @@ -0,0 +1,3 @@ +暂未适配 IBM DB2 数据库,如果你有需要,可以微信联系 wangwenbin-server 一起建设。 + +你需要把表结构与数据导入到 DM 数据库,我a来测试与适配代码。 diff --git a/sql/mysql/quartz.sql b/sql/mysql/quartz.sql new file mode 100644 index 0000000..d158189 --- /dev/null +++ b/sql/mysql/quartz.sql @@ -0,0 +1,284 @@ +/* + Navicat Premium Data Transfer + + Source Server : 127.0.0.1 MySQL + Source Server Type : MySQL + Source Server Version : 80200 (8.2.0) + Source Host : 127.0.0.1:3306 + Source Schema : ruoyi-vue-pro + + Target Server Type : MySQL + Target Server Version : 80200 (8.2.0) + File Encoding : 65001 + + Date: 24/07/2024 08:52:41 +*/ + +SET NAMES utf8mb4; +SET FOREIGN_KEY_CHECKS = 0; + +-- ---------------------------- +-- Table structure for QRTZ_BLOB_TRIGGERS +-- ---------------------------- +DROP TABLE IF EXISTS `QRTZ_BLOB_TRIGGERS`; +CREATE TABLE `QRTZ_BLOB_TRIGGERS` ( + `SCHED_NAME` varchar(120) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `TRIGGER_NAME` varchar(190) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `TRIGGER_GROUP` varchar(190) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `BLOB_DATA` blob NULL, + PRIMARY KEY (`SCHED_NAME`, `TRIGGER_NAME`, `TRIGGER_GROUP`) USING BTREE, + INDEX `SCHED_NAME`(`SCHED_NAME` ASC, `TRIGGER_NAME` ASC, `TRIGGER_GROUP` ASC) USING BTREE, + CONSTRAINT `qrtz_blob_triggers_ibfk_1` FOREIGN KEY (`SCHED_NAME`, `TRIGGER_NAME`, `TRIGGER_GROUP`) REFERENCES `QRTZ_TRIGGERS` (`SCHED_NAME`, `TRIGGER_NAME`, `TRIGGER_GROUP`) ON DELETE RESTRICT ON UPDATE RESTRICT +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci; + +-- ---------------------------- +-- Records of QRTZ_BLOB_TRIGGERS +-- ---------------------------- +BEGIN; +COMMIT; + +-- ---------------------------- +-- Table structure for QRTZ_CALENDARS +-- ---------------------------- +DROP TABLE IF EXISTS `QRTZ_CALENDARS`; +CREATE TABLE `QRTZ_CALENDARS` ( + `SCHED_NAME` varchar(120) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `CALENDAR_NAME` varchar(190) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `CALENDAR` blob NOT NULL, + PRIMARY KEY (`SCHED_NAME`, `CALENDAR_NAME`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci; + +-- ---------------------------- +-- Records of QRTZ_CALENDARS +-- ---------------------------- +BEGIN; +COMMIT; + +-- ---------------------------- +-- Table structure for QRTZ_CRON_TRIGGERS +-- ---------------------------- +DROP TABLE IF EXISTS `QRTZ_CRON_TRIGGERS`; +CREATE TABLE `QRTZ_CRON_TRIGGERS` ( + `SCHED_NAME` varchar(120) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `TRIGGER_NAME` varchar(190) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `TRIGGER_GROUP` varchar(190) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `CRON_EXPRESSION` varchar(120) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `TIME_ZONE_ID` varchar(80) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL, + PRIMARY KEY (`SCHED_NAME`, `TRIGGER_NAME`, `TRIGGER_GROUP`) USING BTREE, + CONSTRAINT `qrtz_cron_triggers_ibfk_1` FOREIGN KEY (`SCHED_NAME`, `TRIGGER_NAME`, `TRIGGER_GROUP`) REFERENCES `QRTZ_TRIGGERS` (`SCHED_NAME`, `TRIGGER_NAME`, `TRIGGER_GROUP`) ON DELETE RESTRICT ON UPDATE RESTRICT +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci; + +-- ---------------------------- +-- Records of QRTZ_CRON_TRIGGERS +-- ---------------------------- +BEGIN; +INSERT INTO `QRTZ_CRON_TRIGGERS` (`SCHED_NAME`, `TRIGGER_NAME`, `TRIGGER_GROUP`, `CRON_EXPRESSION`, `TIME_ZONE_ID`) VALUES ('schedulerName', 'accessLogCleanJob', 'DEFAULT', '0 0 0 * * ?', 'Asia/Shanghai'), ('schedulerName', 'brokerageRecordUnfreezeJob', 'DEFAULT', '0 * * * * ?', 'Asia/Shanghai'), ('schedulerName', 'errorLogCleanJob', 'DEFAULT', '0 0 0 * * ?', 'Asia/Shanghai'), ('schedulerName', 'jobLogCleanJob', 'DEFAULT', '0 0 0 * * ?', 'Asia/Shanghai'), ('schedulerName', 'payNotifyJob', 'DEFAULT', '* * * * * ?', 'Asia/Shanghai'), ('schedulerName', 'payOrderExpireJob', 'DEFAULT', '0 0/1 * * * ?', 'Asia/Shanghai'), ('schedulerName', 'payOrderSyncJob', 'DEFAULT', '0 0/1 * * * ?', 'Asia/Shanghai'), ('schedulerName', 'payRefundSyncJob', 'DEFAULT', '0 0/1 * * * ?', 'Asia/Shanghai'), ('schedulerName', 'tradeOrderAutoCancelJob', 'DEFAULT', '0 * * * * ?', 'Asia/Shanghai'), ('schedulerName', 'tradeOrderAutoCommentJob', 'DEFAULT', '0 * * * * ?', 'Asia/Shanghai'), ('schedulerName', 'tradeOrderAutoReceiveJob', 'DEFAULT', '0 * * * * ?', 'Asia/Shanghai'); +COMMIT; + +-- ---------------------------- +-- Table structure for QRTZ_FIRED_TRIGGERS +-- ---------------------------- +DROP TABLE IF EXISTS `QRTZ_FIRED_TRIGGERS`; +CREATE TABLE `QRTZ_FIRED_TRIGGERS` ( + `SCHED_NAME` varchar(120) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `ENTRY_ID` varchar(95) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `TRIGGER_NAME` varchar(190) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `TRIGGER_GROUP` varchar(190) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `INSTANCE_NAME` varchar(190) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `FIRED_TIME` bigint NOT NULL, + `SCHED_TIME` bigint NOT NULL, + `PRIORITY` int NOT NULL, + `STATE` varchar(16) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `JOB_NAME` varchar(190) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL, + `JOB_GROUP` varchar(190) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL, + `IS_NONCONCURRENT` varchar(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL, + `REQUESTS_RECOVERY` varchar(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL, + PRIMARY KEY (`SCHED_NAME`, `ENTRY_ID`) USING BTREE, + INDEX `IDX_QRTZ_FT_TRIG_INST_NAME`(`SCHED_NAME` ASC, `INSTANCE_NAME` ASC) USING BTREE, + INDEX `IDX_QRTZ_FT_INST_JOB_REQ_RCVRY`(`SCHED_NAME` ASC, `INSTANCE_NAME` ASC, `REQUESTS_RECOVERY` ASC) USING BTREE, + INDEX `IDX_QRTZ_FT_J_G`(`SCHED_NAME` ASC, `JOB_NAME` ASC, `JOB_GROUP` ASC) USING BTREE, + INDEX `IDX_QRTZ_FT_JG`(`SCHED_NAME` ASC, `JOB_GROUP` ASC) USING BTREE, + INDEX `IDX_QRTZ_FT_T_G`(`SCHED_NAME` ASC, `TRIGGER_NAME` ASC, `TRIGGER_GROUP` ASC) USING BTREE, + INDEX `IDX_QRTZ_FT_TG`(`SCHED_NAME` ASC, `TRIGGER_GROUP` ASC) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci; + +-- ---------------------------- +-- Records of QRTZ_FIRED_TRIGGERS +-- ---------------------------- +BEGIN; +COMMIT; + +-- ---------------------------- +-- Table structure for QRTZ_JOB_DETAILS +-- ---------------------------- +DROP TABLE IF EXISTS `QRTZ_JOB_DETAILS`; +CREATE TABLE `QRTZ_JOB_DETAILS` ( + `SCHED_NAME` varchar(120) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `JOB_NAME` varchar(190) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `JOB_GROUP` varchar(190) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `DESCRIPTION` varchar(250) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL, + `JOB_CLASS_NAME` varchar(250) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `IS_DURABLE` varchar(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `IS_NONCONCURRENT` varchar(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `IS_UPDATE_DATA` varchar(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `REQUESTS_RECOVERY` varchar(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `JOB_DATA` blob NULL, + PRIMARY KEY (`SCHED_NAME`, `JOB_NAME`, `JOB_GROUP`) USING BTREE, + INDEX `IDX_QRTZ_J_REQ_RECOVERY`(`SCHED_NAME` ASC, `REQUESTS_RECOVERY` ASC) USING BTREE, + INDEX `IDX_QRTZ_J_GRP`(`SCHED_NAME` ASC, `JOB_GROUP` ASC) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci; + +-- ---------------------------- +-- Records of QRTZ_JOB_DETAILS +-- ---------------------------- +BEGIN; +INSERT INTO `QRTZ_JOB_DETAILS` (`SCHED_NAME`, `JOB_NAME`, `JOB_GROUP`, `DESCRIPTION`, `JOB_CLASS_NAME`, `IS_DURABLE`, `IS_NONCONCURRENT`, `IS_UPDATE_DATA`, `REQUESTS_RECOVERY`, `JOB_DATA`) VALUES ('schedulerName', 'accessLogCleanJob', 'DEFAULT', NULL, 'com.tashow.cloud.quartz.core.handler.JobHandlerInvoker', '0', '1', '1', '0', 0xACED0005737200156F72672E71756172747A2E4A6F62446174614D61709FB083E8BFA9B0CB020000787200266F72672E71756172747A2E7574696C732E537472696E674B65794469727479466C61674D61708208E8C3FBC55D280200015A0013616C6C6F77735472616E7369656E74446174617872001D6F72672E71756172747A2E7574696C732E4469727479466C61674D617013E62EAD28760ACE0200025A000564697274794C00036D617074000F4C6A6176612F7574696C2F4D61703B787001737200116A6176612E7574696C2E486173684D61700507DAC1C31660D103000246000A6C6F6164466163746F724900097468726573686F6C6478703F4000000000000C770800000010000000027400064A4F425F49447372000E6A6176612E6C616E672E4C6F6E673B8BE490CC8F23DF0200014A000576616C7565787200106A6176612E6C616E672E4E756D62657286AC951D0B94E08B020000787000000000000000197400104A4F425F48414E444C45525F4E414D457400116163636573734C6F67436C65616E4A6F627800), ('schedulerName', 'brokerageRecordUnfreezeJob', 'DEFAULT', NULL, 'cn.iocoder.yudao.framework.quartz.core.handler.JobHandlerInvoker', '0', '1', '1', '0', 0xACED0005737200156F72672E71756172747A2E4A6F62446174614D61709FB083E8BFA9B0CB020000787200266F72672E71756172747A2E7574696C732E537472696E674B65794469727479466C61674D61708208E8C3FBC55D280200015A0013616C6C6F77735472616E7369656E74446174617872001D6F72672E71756172747A2E7574696C732E4469727479466C61674D617013E62EAD28760ACE0200025A000564697274794C00036D617074000F4C6A6176612F7574696C2F4D61703B787001737200116A6176612E7574696C2E486173684D61700507DAC1C31660D103000246000A6C6F6164466163746F724900097468726573686F6C6478703F4000000000000C770800000010000000027400064A4F425F49447372000E6A6176612E6C616E672E4C6F6E673B8BE490CC8F23DF0200014A000576616C7565787200106A6176612E6C616E672E4E756D62657286AC951D0B94E08B020000787000000000000000187400104A4F425F48414E444C45525F4E414D4574001A62726F6B65726167655265636F7264556E667265657A654A6F627800), ('schedulerName', 'errorLogCleanJob', 'DEFAULT', NULL, 'cn.iocoder.yudao.framework.quartz.core.handler.JobHandlerInvoker', '0', '1', '1', '0', 0xACED0005737200156F72672E71756172747A2E4A6F62446174614D61709FB083E8BFA9B0CB020000787200266F72672E71756172747A2E7574696C732E537472696E674B65794469727479466C61674D61708208E8C3FBC55D280200015A0013616C6C6F77735472616E7369656E74446174617872001D6F72672E71756172747A2E7574696C732E4469727479466C61674D617013E62EAD28760ACE0200025A000564697274794C00036D617074000F4C6A6176612F7574696C2F4D61703B787001737200116A6176612E7574696C2E486173684D61700507DAC1C31660D103000246000A6C6F6164466163746F724900097468726573686F6C6478703F4000000000000C770800000010000000027400064A4F425F49447372000E6A6176612E6C616E672E4C6F6E673B8BE490CC8F23DF0200014A000576616C7565787200106A6176612E6C616E672E4E756D62657286AC951D0B94E08B0200007870000000000000001A7400104A4F425F48414E444C45525F4E414D457400106572726F724C6F67436C65616E4A6F627800), ('schedulerName', 'jobLogCleanJob', 'DEFAULT', NULL, 'cn.iocoder.yudao.framework.quartz.core.handler.JobHandlerInvoker', '0', '1', '1', '0', 0xACED0005737200156F72672E71756172747A2E4A6F62446174614D61709FB083E8BFA9B0CB020000787200266F72672E71756172747A2E7574696C732E537472696E674B65794469727479466C61674D61708208E8C3FBC55D280200015A0013616C6C6F77735472616E7369656E74446174617872001D6F72672E71756172747A2E7574696C732E4469727479466C61674D617013E62EAD28760ACE0200025A000564697274794C00036D617074000F4C6A6176612F7574696C2F4D61703B787001737200116A6176612E7574696C2E486173684D61700507DAC1C31660D103000246000A6C6F6164466163746F724900097468726573686F6C6478703F4000000000000C770800000010000000027400064A4F425F49447372000E6A6176612E6C616E672E4C6F6E673B8BE490CC8F23DF0200014A000576616C7565787200106A6176612E6C616E672E4E756D62657286AC951D0B94E08B0200007870000000000000001B7400104A4F425F48414E444C45525F4E414D4574000E6A6F624C6F67436C65616E4A6F627800), ('schedulerName', 'payNotifyJob', 'DEFAULT', NULL, 'cn.iocoder.yudao.framework.quartz.core.handler.JobHandlerInvoker', '0', '1', '1', '0', 0xACED0005737200156F72672E71756172747A2E4A6F62446174614D61709FB083E8BFA9B0CB020000787200266F72672E71756172747A2E7574696C732E537472696E674B65794469727479466C61674D61708208E8C3FBC55D280200015A0013616C6C6F77735472616E7369656E74446174617872001D6F72672E71756172747A2E7574696C732E4469727479466C61674D617013E62EAD28760ACE0200025A000564697274794C00036D617074000F4C6A6176612F7574696C2F4D61703B787001737200116A6176612E7574696C2E486173684D61700507DAC1C31660D103000246000A6C6F6164466163746F724900097468726573686F6C6478703F4000000000000C770800000010000000027400064A4F425F49447372000E6A6176612E6C616E672E4C6F6E673B8BE490CC8F23DF0200014A000576616C7565787200106A6176612E6C616E672E4E756D62657286AC951D0B94E08B020000787000000000000000057400104A4F425F48414E444C45525F4E414D4574000C7061794E6F746966794A6F627800), ('schedulerName', 'payOrderExpireJob', 'DEFAULT', NULL, 'cn.iocoder.yudao.framework.quartz.core.handler.JobHandlerInvoker', '0', '1', '1', '0', 0xACED0005737200156F72672E71756172747A2E4A6F62446174614D61709FB083E8BFA9B0CB020000787200266F72672E71756172747A2E7574696C732E537472696E674B65794469727479466C61674D61708208E8C3FBC55D280200015A0013616C6C6F77735472616E7369656E74446174617872001D6F72672E71756172747A2E7574696C732E4469727479466C61674D617013E62EAD28760ACE0200025A000564697274794C00036D617074000F4C6A6176612F7574696C2F4D61703B787001737200116A6176612E7574696C2E486173684D61700507DAC1C31660D103000246000A6C6F6164466163746F724900097468726573686F6C6478703F4000000000000C770800000010000000027400064A4F425F49447372000E6A6176612E6C616E672E4C6F6E673B8BE490CC8F23DF0200014A000576616C7565787200106A6176612E6C616E672E4E756D62657286AC951D0B94E08B020000787000000000000000127400104A4F425F48414E444C45525F4E414D457400117061794F726465724578706972654A6F627800), ('schedulerName', 'payOrderSyncJob', 'DEFAULT', NULL, 'cn.iocoder.yudao.framework.quartz.core.handler.JobHandlerInvoker', '0', '1', '1', '0', 0xACED0005737200156F72672E71756172747A2E4A6F62446174614D61709FB083E8BFA9B0CB020000787200266F72672E71756172747A2E7574696C732E537472696E674B65794469727479466C61674D61708208E8C3FBC55D280200015A0013616C6C6F77735472616E7369656E74446174617872001D6F72672E71756172747A2E7574696C732E4469727479466C61674D617013E62EAD28760ACE0200025A000564697274794C00036D617074000F4C6A6176612F7574696C2F4D61703B787001737200116A6176612E7574696C2E486173684D61700507DAC1C31660D103000246000A6C6F6164466163746F724900097468726573686F6C6478703F4000000000000C770800000010000000027400064A4F425F49447372000E6A6176612E6C616E672E4C6F6E673B8BE490CC8F23DF0200014A000576616C7565787200106A6176612E6C616E672E4E756D62657286AC951D0B94E08B020000787000000000000000117400104A4F425F48414E444C45525F4E414D4574000F7061794F7264657253796E634A6F627800), ('schedulerName', 'payRefundSyncJob', 'DEFAULT', NULL, 'cn.iocoder.yudao.framework.quartz.core.handler.JobHandlerInvoker', '0', '1', '1', '0', 0xACED0005737200156F72672E71756172747A2E4A6F62446174614D61709FB083E8BFA9B0CB020000787200266F72672E71756172747A2E7574696C732E537472696E674B65794469727479466C61674D61708208E8C3FBC55D280200015A0013616C6C6F77735472616E7369656E74446174617872001D6F72672E71756172747A2E7574696C732E4469727479466C61674D617013E62EAD28760ACE0200025A000564697274794C00036D617074000F4C6A6176612F7574696C2F4D61703B787001737200116A6176612E7574696C2E486173684D61700507DAC1C31660D103000246000A6C6F6164466163746F724900097468726573686F6C6478703F4000000000000C770800000010000000027400064A4F425F49447372000E6A6176612E6C616E672E4C6F6E673B8BE490CC8F23DF0200014A000576616C7565787200106A6176612E6C616E672E4E756D62657286AC951D0B94E08B020000787000000000000000137400104A4F425F48414E444C45525F4E414D45740010706179526566756E6453796E634A6F627800), ('schedulerName', 'tradeOrderAutoCancelJob', 'DEFAULT', NULL, 'cn.iocoder.yudao.framework.quartz.core.handler.JobHandlerInvoker', '0', '1', '1', '0', 0xACED0005737200156F72672E71756172747A2E4A6F62446174614D61709FB083E8BFA9B0CB020000787200266F72672E71756172747A2E7574696C732E537472696E674B65794469727479466C61674D61708208E8C3FBC55D280200015A0013616C6C6F77735472616E7369656E74446174617872001D6F72672E71756172747A2E7574696C732E4469727479466C61674D617013E62EAD28760ACE0200025A000564697274794C00036D617074000F4C6A6176612F7574696C2F4D61703B787001737200116A6176612E7574696C2E486173684D61700507DAC1C31660D103000246000A6C6F6164466163746F724900097468726573686F6C6478703F4000000000000C770800000010000000027400064A4F425F49447372000E6A6176612E6C616E672E4C6F6E673B8BE490CC8F23DF0200014A000576616C7565787200106A6176612E6C616E672E4E756D62657286AC951D0B94E08B020000787000000000000000157400104A4F425F48414E444C45525F4E414D4574001774726164654F726465724175746F43616E63656C4A6F627800), ('schedulerName', 'tradeOrderAutoCommentJob', 'DEFAULT', NULL, 'cn.iocoder.yudao.framework.quartz.core.handler.JobHandlerInvoker', '0', '1', '1', '0', 0xACED0005737200156F72672E71756172747A2E4A6F62446174614D61709FB083E8BFA9B0CB020000787200266F72672E71756172747A2E7574696C732E537472696E674B65794469727479466C61674D61708208E8C3FBC55D280200015A0013616C6C6F77735472616E7369656E74446174617872001D6F72672E71756172747A2E7574696C732E4469727479466C61674D617013E62EAD28760ACE0200025A000564697274794C00036D617074000F4C6A6176612F7574696C2F4D61703B787001737200116A6176612E7574696C2E486173684D61700507DAC1C31660D103000246000A6C6F6164466163746F724900097468726573686F6C6478703F4000000000000C770800000010000000027400064A4F425F49447372000E6A6176612E6C616E672E4C6F6E673B8BE490CC8F23DF0200014A000576616C7565787200106A6176612E6C616E672E4E756D62657286AC951D0B94E08B020000787000000000000000177400104A4F425F48414E444C45525F4E414D4574001874726164654F726465724175746F436F6D6D656E744A6F627800), ('schedulerName', 'tradeOrderAutoReceiveJob', 'DEFAULT', NULL, 'cn.iocoder.yudao.framework.quartz.core.handler.JobHandlerInvoker', '0', '1', '1', '0', 0xACED0005737200156F72672E71756172747A2E4A6F62446174614D61709FB083E8BFA9B0CB020000787200266F72672E71756172747A2E7574696C732E537472696E674B65794469727479466C61674D61708208E8C3FBC55D280200015A0013616C6C6F77735472616E7369656E74446174617872001D6F72672E71756172747A2E7574696C732E4469727479466C61674D617013E62EAD28760ACE0200025A000564697274794C00036D617074000F4C6A6176612F7574696C2F4D61703B787001737200116A6176612E7574696C2E486173684D61700507DAC1C31660D103000246000A6C6F6164466163746F724900097468726573686F6C6478703F4000000000000C770800000010000000027400064A4F425F49447372000E6A6176612E6C616E672E4C6F6E673B8BE490CC8F23DF0200014A000576616C7565787200106A6176612E6C616E672E4E756D62657286AC951D0B94E08B020000787000000000000000167400104A4F425F48414E444C45525F4E414D4574001874726164654F726465724175746F526563656976654A6F627800); +COMMIT; + +-- ---------------------------- +-- Table structure for QRTZ_LOCKS +-- ---------------------------- +DROP TABLE IF EXISTS `QRTZ_LOCKS`; +CREATE TABLE `QRTZ_LOCKS` ( + `SCHED_NAME` varchar(120) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `LOCK_NAME` varchar(40) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + PRIMARY KEY (`SCHED_NAME`, `LOCK_NAME`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci; + +-- ---------------------------- +-- Records of QRTZ_LOCKS +-- ---------------------------- +BEGIN; +INSERT INTO `QRTZ_LOCKS` (`SCHED_NAME`, `LOCK_NAME`) VALUES ('schedulerName', 'STATE_ACCESS'), ('schedulerName', 'TRIGGER_ACCESS'); +COMMIT; + +-- ---------------------------- +-- Table structure for QRTZ_PAUSED_TRIGGER_GRPS +-- ---------------------------- +DROP TABLE IF EXISTS `QRTZ_PAUSED_TRIGGER_GRPS`; +CREATE TABLE `QRTZ_PAUSED_TRIGGER_GRPS` ( + `SCHED_NAME` varchar(120) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `TRIGGER_GROUP` varchar(190) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + PRIMARY KEY (`SCHED_NAME`, `TRIGGER_GROUP`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci; + +-- ---------------------------- +-- Records of QRTZ_PAUSED_TRIGGER_GRPS +-- ---------------------------- +BEGIN; +COMMIT; + +-- ---------------------------- +-- Table structure for QRTZ_SCHEDULER_STATE +-- ---------------------------- +DROP TABLE IF EXISTS `QRTZ_SCHEDULER_STATE`; +CREATE TABLE `QRTZ_SCHEDULER_STATE` ( + `SCHED_NAME` varchar(120) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `INSTANCE_NAME` varchar(190) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `LAST_CHECKIN_TIME` bigint NOT NULL, + `CHECKIN_INTERVAL` bigint NOT NULL, + PRIMARY KEY (`SCHED_NAME`, `INSTANCE_NAME`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci; + +-- ---------------------------- +-- Records of QRTZ_SCHEDULER_STATE +-- ---------------------------- +BEGIN; +INSERT INTO `QRTZ_SCHEDULER_STATE` (`SCHED_NAME`, `INSTANCE_NAME`, `LAST_CHECKIN_TIME`, `CHECKIN_INTERVAL`) VALUES ('schedulerName', 'MacBook-Pro.local1713489703551', 1713742509534, 15000); +COMMIT; + +-- ---------------------------- +-- Table structure for QRTZ_SIMPLE_TRIGGERS +-- ---------------------------- +DROP TABLE IF EXISTS `QRTZ_SIMPLE_TRIGGERS`; +CREATE TABLE `QRTZ_SIMPLE_TRIGGERS` ( + `SCHED_NAME` varchar(120) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `TRIGGER_NAME` varchar(190) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `TRIGGER_GROUP` varchar(190) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `REPEAT_COUNT` bigint NOT NULL, + `REPEAT_INTERVAL` bigint NOT NULL, + `TIMES_TRIGGERED` bigint NOT NULL, + PRIMARY KEY (`SCHED_NAME`, `TRIGGER_NAME`, `TRIGGER_GROUP`) USING BTREE, + CONSTRAINT `qrtz_simple_triggers_ibfk_1` FOREIGN KEY (`SCHED_NAME`, `TRIGGER_NAME`, `TRIGGER_GROUP`) REFERENCES `QRTZ_TRIGGERS` (`SCHED_NAME`, `TRIGGER_NAME`, `TRIGGER_GROUP`) ON DELETE RESTRICT ON UPDATE RESTRICT +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci; + +-- ---------------------------- +-- Records of QRTZ_SIMPLE_TRIGGERS +-- ---------------------------- +BEGIN; +COMMIT; + +-- ---------------------------- +-- Table structure for QRTZ_SIMPROP_TRIGGERS +-- ---------------------------- +DROP TABLE IF EXISTS `QRTZ_SIMPROP_TRIGGERS`; +CREATE TABLE `QRTZ_SIMPROP_TRIGGERS` ( + `SCHED_NAME` varchar(120) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `TRIGGER_NAME` varchar(190) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `TRIGGER_GROUP` varchar(190) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `STR_PROP_1` varchar(512) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL, + `STR_PROP_2` varchar(512) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL, + `STR_PROP_3` varchar(512) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL, + `INT_PROP_1` int NULL DEFAULT NULL, + `INT_PROP_2` int NULL DEFAULT NULL, + `LONG_PROP_1` bigint NULL DEFAULT NULL, + `LONG_PROP_2` bigint NULL DEFAULT NULL, + `DEC_PROP_1` decimal(13, 4) NULL DEFAULT NULL, + `DEC_PROP_2` decimal(13, 4) NULL DEFAULT NULL, + `BOOL_PROP_1` varchar(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL, + `BOOL_PROP_2` varchar(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL, + PRIMARY KEY (`SCHED_NAME`, `TRIGGER_NAME`, `TRIGGER_GROUP`) USING BTREE, + CONSTRAINT `qrtz_simprop_triggers_ibfk_1` FOREIGN KEY (`SCHED_NAME`, `TRIGGER_NAME`, `TRIGGER_GROUP`) REFERENCES `QRTZ_TRIGGERS` (`SCHED_NAME`, `TRIGGER_NAME`, `TRIGGER_GROUP`) ON DELETE RESTRICT ON UPDATE RESTRICT +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci; + +-- ---------------------------- +-- Records of QRTZ_SIMPROP_TRIGGERS +-- ---------------------------- +BEGIN; +COMMIT; + +-- ---------------------------- +-- Table structure for QRTZ_TRIGGERS +-- ---------------------------- +DROP TABLE IF EXISTS `QRTZ_TRIGGERS`; +CREATE TABLE `QRTZ_TRIGGERS` ( + `SCHED_NAME` varchar(120) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `TRIGGER_NAME` varchar(190) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `TRIGGER_GROUP` varchar(190) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `JOB_NAME` varchar(190) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `JOB_GROUP` varchar(190) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `DESCRIPTION` varchar(250) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL, + `NEXT_FIRE_TIME` bigint NULL DEFAULT NULL, + `PREV_FIRE_TIME` bigint NULL DEFAULT NULL, + `PRIORITY` int NULL DEFAULT NULL, + `TRIGGER_STATE` varchar(16) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `TRIGGER_TYPE` varchar(8) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `START_TIME` bigint NOT NULL, + `END_TIME` bigint NULL DEFAULT NULL, + `CALENDAR_NAME` varchar(190) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL, + `MISFIRE_INSTR` smallint NULL DEFAULT NULL, + `JOB_DATA` blob NULL, + PRIMARY KEY (`SCHED_NAME`, `TRIGGER_NAME`, `TRIGGER_GROUP`) USING BTREE, + INDEX `IDX_QRTZ_T_J`(`SCHED_NAME` ASC, `JOB_NAME` ASC, `JOB_GROUP` ASC) USING BTREE, + INDEX `IDX_QRTZ_T_JG`(`SCHED_NAME` ASC, `JOB_GROUP` ASC) USING BTREE, + INDEX `IDX_QRTZ_T_C`(`SCHED_NAME` ASC, `CALENDAR_NAME` ASC) USING BTREE, + INDEX `IDX_QRTZ_T_G`(`SCHED_NAME` ASC, `TRIGGER_GROUP` ASC) USING BTREE, + INDEX `IDX_QRTZ_T_STATE`(`SCHED_NAME` ASC, `TRIGGER_STATE` ASC) USING BTREE, + INDEX `IDX_QRTZ_T_N_STATE`(`SCHED_NAME` ASC, `TRIGGER_NAME` ASC, `TRIGGER_GROUP` ASC, `TRIGGER_STATE` ASC) USING BTREE, + INDEX `IDX_QRTZ_T_N_G_STATE`(`SCHED_NAME` ASC, `TRIGGER_GROUP` ASC, `TRIGGER_STATE` ASC) USING BTREE, + INDEX `IDX_QRTZ_T_NEXT_FIRE_TIME`(`SCHED_NAME` ASC, `NEXT_FIRE_TIME` ASC) USING BTREE, + INDEX `IDX_QRTZ_T_NFT_ST`(`SCHED_NAME` ASC, `TRIGGER_STATE` ASC, `NEXT_FIRE_TIME` ASC) USING BTREE, + INDEX `IDX_QRTZ_T_NFT_MISFIRE`(`SCHED_NAME` ASC, `MISFIRE_INSTR` ASC, `NEXT_FIRE_TIME` ASC) USING BTREE, + INDEX `IDX_QRTZ_T_NFT_ST_MISFIRE`(`SCHED_NAME` ASC, `MISFIRE_INSTR` ASC, `NEXT_FIRE_TIME` ASC, `TRIGGER_STATE` ASC) USING BTREE, + INDEX `IDX_QRTZ_T_NFT_ST_MISFIRE_GRP`(`SCHED_NAME` ASC, `MISFIRE_INSTR` ASC, `NEXT_FIRE_TIME` ASC, `TRIGGER_GROUP` ASC, `TRIGGER_STATE` ASC) USING BTREE, + CONSTRAINT `qrtz_triggers_ibfk_1` FOREIGN KEY (`SCHED_NAME`, `JOB_NAME`, `JOB_GROUP`) REFERENCES `QRTZ_JOB_DETAILS` (`SCHED_NAME`, `JOB_NAME`, `JOB_GROUP`) ON DELETE RESTRICT ON UPDATE RESTRICT +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci; + +-- ---------------------------- +-- Records of QRTZ_TRIGGERS +-- ---------------------------- +BEGIN; +INSERT INTO `QRTZ_TRIGGERS` (`SCHED_NAME`, `TRIGGER_NAME`, `TRIGGER_GROUP`, `JOB_NAME`, `JOB_GROUP`, `DESCRIPTION`, `NEXT_FIRE_TIME`, `PREV_FIRE_TIME`, `PRIORITY`, `TRIGGER_STATE`, `TRIGGER_TYPE`, `START_TIME`, `END_TIME`, `CALENDAR_NAME`, `MISFIRE_INSTR`, `JOB_DATA`) VALUES ('schedulerName', 'accessLogCleanJob', 'DEFAULT', 'accessLogCleanJob', 'DEFAULT', NULL, 1696348800000, -1, 5, 'PAUSED', 'CRON', 1696301981000, 0, NULL, 0, 0xACED0005737200156F72672E71756172747A2E4A6F62446174614D61709FB083E8BFA9B0CB020000787200266F72672E71756172747A2E7574696C732E537472696E674B65794469727479466C61674D61708208E8C3FBC55D280200015A0013616C6C6F77735472616E7369656E74446174617872001D6F72672E71756172747A2E7574696C732E4469727479466C61674D617013E62EAD28760ACE0200025A000564697274794C00036D617074000F4C6A6176612F7574696C2F4D61703B787001737200116A6176612E7574696C2E486173684D61700507DAC1C31660D103000246000A6C6F6164466163746F724900097468726573686F6C6478703F4000000000000C770800000010000000037400114A4F425F48414E444C45525F504152414D7400007400124A4F425F52455452595F494E54455256414C737200116A6176612E6C616E672E496E746567657212E2A0A4F781873802000149000576616C7565787200106A6176612E6C616E672E4E756D62657286AC951D0B94E08B02000078700000000074000F4A4F425F52455452595F434F554E547371007E000A000000037800), ('schedulerName', 'brokerageRecordUnfreezeJob', 'DEFAULT', 'brokerageRecordUnfreezeJob', 'DEFAULT', NULL, 1695909720000, -1, 5, 'PAUSED', 'CRON', 1695909706000, 0, NULL, 0, 0xACED0005737200156F72672E71756172747A2E4A6F62446174614D61709FB083E8BFA9B0CB020000787200266F72672E71756172747A2E7574696C732E537472696E674B65794469727479466C61674D61708208E8C3FBC55D280200015A0013616C6C6F77735472616E7369656E74446174617872001D6F72672E71756172747A2E7574696C732E4469727479466C61674D617013E62EAD28760ACE0200025A000564697274794C00036D617074000F4C6A6176612F7574696C2F4D61703B787001737200116A6176612E7574696C2E486173684D61700507DAC1C31660D103000246000A6C6F6164466163746F724900097468726573686F6C6478703F4000000000000C770800000010000000037400114A4F425F48414E444C45525F504152414D7400007400124A4F425F52455452595F494E54455256414C737200116A6176612E6C616E672E496E746567657212E2A0A4F781873802000149000576616C7565787200106A6176612E6C616E672E4E756D62657286AC951D0B94E08B02000078700000000074000F4A4F425F52455452595F434F554E547371007E000A000000037800), ('schedulerName', 'errorLogCleanJob', 'DEFAULT', 'errorLogCleanJob', 'DEFAULT', NULL, 1696348800000, -1, 5, 'PAUSED', 'CRON', 1696302043000, 0, NULL, 0, 0xACED0005737200156F72672E71756172747A2E4A6F62446174614D61709FB083E8BFA9B0CB020000787200266F72672E71756172747A2E7574696C732E537472696E674B65794469727479466C61674D61708208E8C3FBC55D280200015A0013616C6C6F77735472616E7369656E74446174617872001D6F72672E71756172747A2E7574696C732E4469727479466C61674D617013E62EAD28760ACE0200025A000564697274794C00036D617074000F4C6A6176612F7574696C2F4D61703B787001737200116A6176612E7574696C2E486173684D61700507DAC1C31660D103000246000A6C6F6164466163746F724900097468726573686F6C6478703F4000000000000C770800000010000000037400114A4F425F48414E444C45525F504152414D7400007400124A4F425F52455452595F494E54455256414C737200116A6176612E6C616E672E496E746567657212E2A0A4F781873802000149000576616C7565787200106A6176612E6C616E672E4E756D62657286AC951D0B94E08B02000078700000000074000F4A4F425F52455452595F434F554E547371007E000A000000037800), ('schedulerName', 'jobLogCleanJob', 'DEFAULT', 'jobLogCleanJob', 'DEFAULT', NULL, 1696348800000, -1, 5, 'PAUSED', 'CRON', 1696302092000, 0, NULL, 0, 0xACED0005737200156F72672E71756172747A2E4A6F62446174614D61709FB083E8BFA9B0CB020000787200266F72672E71756172747A2E7574696C732E537472696E674B65794469727479466C61674D61708208E8C3FBC55D280200015A0013616C6C6F77735472616E7369656E74446174617872001D6F72672E71756172747A2E7574696C732E4469727479466C61674D617013E62EAD28760ACE0200025A000564697274794C00036D617074000F4C6A6176612F7574696C2F4D61703B787001737200116A6176612E7574696C2E486173684D61700507DAC1C31660D103000246000A6C6F6164466163746F724900097468726573686F6C6478703F4000000000000C770800000010000000037400114A4F425F48414E444C45525F504152414D7400007400124A4F425F52455452595F494E54455256414C737200116A6176612E6C616E672E496E746567657212E2A0A4F781873802000149000576616C7565787200106A6176612E6C616E672E4E756D62657286AC951D0B94E08B02000078700000000074000F4A4F425F52455452595F434F554E547371007E000A000000037800), ('schedulerName', 'payNotifyJob', 'DEFAULT', 'payNotifyJob', 'DEFAULT', NULL, 1688907102000, 1688907101000, 5, 'PAUSED', 'CRON', 1635294882000, 0, NULL, 0, 0xACED0005737200156F72672E71756172747A2E4A6F62446174614D61709FB083E8BFA9B0CB020000787200266F72672E71756172747A2E7574696C732E537472696E674B65794469727479466C61674D61708208E8C3FBC55D280200015A0013616C6C6F77735472616E7369656E74446174617872001D6F72672E71756172747A2E7574696C732E4469727479466C61674D617013E62EAD28760ACE0200025A000564697274794C00036D617074000F4C6A6176612F7574696C2F4D61703B787001737200116A6176612E7574696C2E486173684D61700507DAC1C31660D103000246000A6C6F6164466163746F724900097468726573686F6C6478703F4000000000000C770800000010000000037400114A4F425F48414E444C45525F504152414D707400124A4F425F52455452595F494E54455256414C737200116A6176612E6C616E672E496E746567657212E2A0A4F781873802000149000576616C7565787200106A6176612E6C616E672E4E756D62657286AC951D0B94E08B02000078700000000074000F4A4F425F52455452595F434F554E5471007E000B7800), ('schedulerName', 'payOrderExpireJob', 'DEFAULT', 'payOrderExpireJob', 'DEFAULT', NULL, 1690011600000, -1, 5, 'PAUSED', 'CRON', 1690011553000, 0, NULL, 0, 0xACED0005737200156F72672E71756172747A2E4A6F62446174614D61709FB083E8BFA9B0CB020000787200266F72672E71756172747A2E7574696C732E537472696E674B65794469727479466C61674D61708208E8C3FBC55D280200015A0013616C6C6F77735472616E7369656E74446174617872001D6F72672E71756172747A2E7574696C732E4469727479466C61674D617013E62EAD28760ACE0200025A000564697274794C00036D617074000F4C6A6176612F7574696C2F4D61703B787001737200116A6176612E7574696C2E486173684D61700507DAC1C31660D103000246000A6C6F6164466163746F724900097468726573686F6C6478703F4000000000000C770800000010000000037400114A4F425F48414E444C45525F504152414D707400124A4F425F52455452595F494E54455256414C737200116A6176612E6C616E672E496E746567657212E2A0A4F781873802000149000576616C7565787200106A6176612E6C616E672E4E756D62657286AC951D0B94E08B02000078700000000074000F4A4F425F52455452595F434F554E5471007E000B7800), ('schedulerName', 'payOrderSyncJob', 'DEFAULT', 'payOrderSyncJob', 'DEFAULT', NULL, 1690011600000, 1690011540000, 5, 'PAUSED', 'CRON', 1690007785000, 0, NULL, 0, 0xACED0005737200156F72672E71756172747A2E4A6F62446174614D61709FB083E8BFA9B0CB020000787200266F72672E71756172747A2E7574696C732E537472696E674B65794469727479466C61674D61708208E8C3FBC55D280200015A0013616C6C6F77735472616E7369656E74446174617872001D6F72672E71756172747A2E7574696C732E4469727479466C61674D617013E62EAD28760ACE0200025A000564697274794C00036D617074000F4C6A6176612F7574696C2F4D61703B787001737200116A6176612E7574696C2E486173684D61700507DAC1C31660D103000246000A6C6F6164466163746F724900097468726573686F6C6478703F4000000000000C770800000010000000037400114A4F425F48414E444C45525F504152414D707400124A4F425F52455452595F494E54455256414C737200116A6176612E6C616E672E496E746567657212E2A0A4F781873802000149000576616C7565787200106A6176612E6C616E672E4E756D62657286AC951D0B94E08B02000078700000000074000F4A4F425F52455452595F434F554E5471007E000B7800), ('schedulerName', 'payRefundSyncJob', 'DEFAULT', 'payRefundSyncJob', 'DEFAULT', NULL, 1690117560000, 1690117500000, 5, 'PAUSED', 'CRON', 1690117424000, 0, NULL, 0, 0xACED0005737200156F72672E71756172747A2E4A6F62446174614D61709FB083E8BFA9B0CB020000787200266F72672E71756172747A2E7574696C732E537472696E674B65794469727479466C61674D61708208E8C3FBC55D280200015A0013616C6C6F77735472616E7369656E74446174617872001D6F72672E71756172747A2E7574696C732E4469727479466C61674D617013E62EAD28760ACE0200025A000564697274794C00036D617074000F4C6A6176612F7574696C2F4D61703B787001737200116A6176612E7574696C2E486173684D61700507DAC1C31660D103000246000A6C6F6164466163746F724900097468726573686F6C6478703F4000000000000C770800000010000000037400114A4F425F48414E444C45525F504152414D707400124A4F425F52455452595F494E54455256414C737200116A6176612E6C616E672E496E746567657212E2A0A4F781873802000149000576616C7565787200106A6176612E6C616E672E4E756D62657286AC951D0B94E08B02000078700000000074000F4A4F425F52455452595F434F554E5471007E000B7800), ('schedulerName', 'tradeOrderAutoCancelJob', 'DEFAULT', 'tradeOrderAutoCancelJob', 'DEFAULT', NULL, 1695727440000, 1695727380000, 5, 'PAUSED', 'CRON', 1695656605000, 0, NULL, 0, 0xACED0005737200156F72672E71756172747A2E4A6F62446174614D61709FB083E8BFA9B0CB020000787200266F72672E71756172747A2E7574696C732E537472696E674B65794469727479466C61674D61708208E8C3FBC55D280200015A0013616C6C6F77735472616E7369656E74446174617872001D6F72672E71756172747A2E7574696C732E4469727479466C61674D617013E62EAD28760ACE0200025A000564697274794C00036D617074000F4C6A6176612F7574696C2F4D61703B787001737200116A6176612E7574696C2E486173684D61700507DAC1C31660D103000246000A6C6F6164466163746F724900097468726573686F6C6478703F4000000000000C770800000010000000037400114A4F425F48414E444C45525F504152414D7400007400124A4F425F52455452595F494E54455256414C737200116A6176612E6C616E672E496E746567657212E2A0A4F781873802000149000576616C7565787200106A6176612E6C616E672E4E756D62657286AC951D0B94E08B02000078700000000074000F4A4F425F52455452595F434F554E547371007E000A000000037800), ('schedulerName', 'tradeOrderAutoCommentJob', 'DEFAULT', 'tradeOrderAutoCommentJob', 'DEFAULT', NULL, 1695783840000, 1695783780000, 5, 'PAUSED', 'CRON', 1695742709000, 0, NULL, 0, 0xACED0005737200156F72672E71756172747A2E4A6F62446174614D61709FB083E8BFA9B0CB020000787200266F72672E71756172747A2E7574696C732E537472696E674B65794469727479466C61674D61708208E8C3FBC55D280200015A0013616C6C6F77735472616E7369656E74446174617872001D6F72672E71756172747A2E7574696C732E4469727479466C61674D617013E62EAD28760ACE0200025A000564697274794C00036D617074000F4C6A6176612F7574696C2F4D61703B787001737200116A6176612E7574696C2E486173684D61700507DAC1C31660D103000246000A6C6F6164466163746F724900097468726573686F6C6478703F4000000000000C770800000010000000037400114A4F425F48414E444C45525F504152414D7400007400124A4F425F52455452595F494E54455256414C737200116A6176612E6C616E672E496E746567657212E2A0A4F781873802000149000576616C7565787200106A6176612E6C616E672E4E756D62657286AC951D0B94E08B02000078700000000074000F4A4F425F52455452595F434F554E547371007E000A000000037800), ('schedulerName', 'tradeOrderAutoReceiveJob', 'DEFAULT', 'tradeOrderAutoReceiveJob', 'DEFAULT', NULL, 1695742740000, 1695742680000, 5, 'PAUSED', 'CRON', 1695727433000, 0, NULL, 0, 0xACED0005737200156F72672E71756172747A2E4A6F62446174614D61709FB083E8BFA9B0CB020000787200266F72672E71756172747A2E7574696C732E537472696E674B65794469727479466C61674D61708208E8C3FBC55D280200015A0013616C6C6F77735472616E7369656E74446174617872001D6F72672E71756172747A2E7574696C732E4469727479466C61674D617013E62EAD28760ACE0200025A000564697274794C00036D617074000F4C6A6176612F7574696C2F4D61703B787001737200116A6176612E7574696C2E486173684D61700507DAC1C31660D103000246000A6C6F6164466163746F724900097468726573686F6C6478703F4000000000000C770800000010000000037400114A4F425F48414E444C45525F504152414D7400007400124A4F425F52455452595F494E54455256414C737200116A6176612E6C616E672E496E746567657212E2A0A4F781873802000149000576616C7565787200106A6176612E6C616E672E4E756D62657286AC951D0B94E08B02000078700000000074000F4A4F425F52455452595F434F554E547371007E000A000000037800); +COMMIT; + +SET FOREIGN_KEY_CHECKS = 1; diff --git a/sql/mysql/ruoyi-vue-pro.sql b/sql/mysql/ruoyi-vue-pro.sql new file mode 100644 index 0000000..db376e3 --- /dev/null +++ b/sql/mysql/ruoyi-vue-pro.sql @@ -0,0 +1,11205 @@ +/* + Navicat Premium Data Transfer + + Source Server : 127.0.0.1 MySQL + Source Server Type : MySQL + Source Server Version : 80200 (8.2.0) + Source Host : 127.0.0.1:3306 + Source Schema : ruoyi-vue-pro + + Target Server Type : MySQL + Target Server Version : 80200 (8.2.0) + File Encoding : 65001 + + Date: 31/12/2024 09:16:18 +*/ + +SET NAMES utf8mb4; +SET +FOREIGN_KEY_CHECKS = 0; + +-- ---------------------------- +-- Table structure for infra_api_access_log +-- ---------------------------- +DROP TABLE IF EXISTS `infra_api_access_log`; +CREATE TABLE `infra_api_access_log` +( + `id` bigint NOT NULL AUTO_INCREMENT COMMENT '日志主键', + `trace_id` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '链路追踪编号', + `user_id` bigint NOT NULL DEFAULT 0 COMMENT '用户编号', + `user_type` tinyint NOT NULL DEFAULT 0 COMMENT '用户类型', + `application_name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '应用名', + `request_method` varchar(16) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '请求方法名', + `request_url` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '请求地址', + `request_params` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL COMMENT '请求参数', + `response_body` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL COMMENT '响应结果', + `user_ip` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '用户 IP', + `user_agent` varchar(512) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '浏览器 UA', + `operate_module` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '操作模块', + `operate_name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '操作名', + `operate_type` tinyint NULL DEFAULT 0 COMMENT '操作分类', + `begin_time` datetime NOT NULL COMMENT '开始请求时间', + `end_time` datetime NOT NULL COMMENT '结束请求时间', + `duration` int NOT NULL COMMENT '执行时长', + `result_code` int NOT NULL DEFAULT 0 COMMENT '结果码', + `result_msg` varchar(512) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '结果提示', + `creator` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '创建者', + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `updater` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '更新者', + `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除', + `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号', + PRIMARY KEY (`id`) USING BTREE, + INDEX `idx_create_time`(`create_time` ASC) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 35942 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = 'API 访问日志表'; + +-- ---------------------------- +-- Records of infra_api_access_log +-- ---------------------------- +BEGIN; +COMMIT; + +-- ---------------------------- +-- Table structure for infra_api_error_log +-- ---------------------------- +DROP TABLE IF EXISTS `infra_api_error_log`; +CREATE TABLE `infra_api_error_log` +( + `id` bigint NOT NULL AUTO_INCREMENT COMMENT '编号', + `trace_id` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '链路追踪编号', + `user_id` bigint NOT NULL DEFAULT 0 COMMENT '用户编号', + `user_type` tinyint NOT NULL DEFAULT 0 COMMENT '用户类型', + `application_name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '应用名', + `request_method` varchar(16) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '请求方法名', + `request_url` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '请求地址', + `request_params` varchar(8000) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '请求参数', + `user_ip` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '用户 IP', + `user_agent` varchar(512) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '浏览器 UA', + `exception_time` datetime NOT NULL COMMENT '异常发生时间', + `exception_name` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '异常名', + `exception_message` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '异常导致的消息', + `exception_root_cause_message` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '异常导致的根消息', + `exception_stack_trace` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '异常的栈轨迹', + `exception_class_name` varchar(512) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '异常发生的类全名', + `exception_file_name` varchar(512) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '异常发生的类文件', + `exception_method_name` varchar(512) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '异常发生的方法名', + `exception_line_number` int NOT NULL COMMENT '异常发生的方法所在行', + `process_status` tinyint NOT NULL COMMENT '处理状态', + `process_time` datetime NULL DEFAULT NULL COMMENT '处理时间', + `process_user_id` int NULL DEFAULT 0 COMMENT '处理用户编号', + `creator` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '创建者', + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `updater` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '更新者', + `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除', + `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 21226 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '系统异常日志'; + +-- ---------------------------- +-- Records of infra_api_error_log +-- ---------------------------- +BEGIN; +COMMIT; + +-- ---------------------------- +-- Table structure for infra_codegen_column +-- ---------------------------- +DROP TABLE IF EXISTS `infra_codegen_column`; +CREATE TABLE `infra_codegen_column` +( + `id` bigint NOT NULL AUTO_INCREMENT COMMENT '编号', + `table_id` bigint NOT NULL COMMENT '表编号', + `column_name` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '字段名', + `data_type` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '字段类型', + `column_comment` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '字段描述', + `nullable` bit(1) NOT NULL COMMENT '是否允许为空', + `primary_key` bit(1) NOT NULL COMMENT '是否主键', + `ordinal_position` int NOT NULL COMMENT '排序', + `java_type` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT 'Java 属性类型', + `java_field` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT 'Java 属性名', + `dict_type` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '字典类型', + `example` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '数据示例', + `create_operation` bit(1) NOT NULL COMMENT '是否为 Create 创建操作的字段', + `update_operation` bit(1) NOT NULL COMMENT '是否为 Update 更新操作的字段', + `list_operation` bit(1) NOT NULL COMMENT '是否为 List 查询操作的字段', + `list_operation_condition` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '=' COMMENT 'List 查询操作的条件类型', + `list_operation_result` bit(1) NOT NULL COMMENT '是否为 List 查询操作的返回字段', + `html_type` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '显示类型', + `creator` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '创建者', + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `updater` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '更新者', + `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 2483 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '代码生成表字段定义'; + +-- ---------------------------- +-- Records of infra_codegen_column +-- ---------------------------- +BEGIN; +COMMIT; + +-- ---------------------------- +-- Table structure for infra_codegen_table +-- ---------------------------- +DROP TABLE IF EXISTS `infra_codegen_table`; +CREATE TABLE `infra_codegen_table` +( + `id` bigint NOT NULL AUTO_INCREMENT COMMENT '编号', + `data_source_config_id` bigint NOT NULL COMMENT '数据源配置的编号', + `scene` tinyint NOT NULL DEFAULT 1 COMMENT '生成场景', + `table_name` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '表名称', + `table_comment` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '表描述', + `remark` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '备注', + `module_name` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '模块名', + `business_name` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '业务名', + `class_name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '类名称', + `class_comment` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '类描述', + `author` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '作者', + `template_type` tinyint NOT NULL DEFAULT 1 COMMENT '模板类型', + `front_type` tinyint NOT NULL COMMENT '前端类型', + `parent_menu_id` bigint NULL DEFAULT NULL COMMENT '父菜单编号', + `master_table_id` bigint NULL DEFAULT NULL COMMENT '主表的编号', + `sub_join_column_id` bigint NULL DEFAULT NULL COMMENT '子表关联主表的字段编号', + `sub_join_many` bit(1) NULL DEFAULT NULL COMMENT '主表与子表是否一对多', + `tree_parent_column_id` bigint NULL DEFAULT NULL COMMENT '树表的父字段编号', + `tree_name_column_id` bigint NULL DEFAULT NULL COMMENT '树表的名字字段编号', + `creator` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '创建者', + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `updater` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '更新者', + `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 187 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '代码生成表定义'; + +-- ---------------------------- +-- Records of infra_codegen_table +-- ---------------------------- +BEGIN; +COMMIT; + +-- ---------------------------- +-- Table structure for infra_config +-- ---------------------------- +DROP TABLE IF EXISTS `infra_config`; +CREATE TABLE `infra_config` +( + `id` bigint NOT NULL AUTO_INCREMENT COMMENT '参数主键', + `category` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '参数分组', + `type` tinyint NOT NULL COMMENT '参数类型', + `name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '参数名称', + `config_key` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '参数键名', + `value` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '参数键值', + `visible` bit(1) NOT NULL COMMENT '是否可见', + `remark` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '备注', + `creator` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '创建者', + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `updater` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '更新者', + `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 13 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '参数配置表'; + +-- ---------------------------- +-- Records of infra_config +-- ---------------------------- +BEGIN; +INSERT INTO `infra_config` (`id`, `category`, `type`, `name`, `config_key`, `value`, `visible`, `remark`, `creator`, + `create_time`, `updater`, `update_time`, `deleted`) +VALUES (2, 'biz', 1, '用户管理-账号初始密码', 'system.user.init-password', '123456', b'0', '初始化密码 123456', 'admin', + '2021-01-05 17:03:48', '1', '2024-07-20 17:22:47', b'0'); +INSERT INTO `infra_config` (`id`, `category`, `type`, `name`, `config_key`, `value`, `visible`, `remark`, `creator`, + `create_time`, `updater`, `update_time`, `deleted`) +VALUES (7, 'url', 2, 'MySQL 监控的地址', 'url.druid', '', b'1', '', '1', '2023-04-07 13:41:16', '1', + '2023-04-07 14:33:38', b'0'); +INSERT INTO `infra_config` (`id`, `category`, `type`, `name`, `config_key`, `value`, `visible`, `remark`, `creator`, + `create_time`, `updater`, `update_time`, `deleted`) +VALUES (8, 'url', 2, 'SkyWalking 监控的地址', 'url.skywalking', '', b'1', '', '1', '2023-04-07 13:41:16', '1', + '2023-04-07 14:57:03', b'0'); +INSERT INTO `infra_config` (`id`, `category`, `type`, `name`, `config_key`, `value`, `visible`, `remark`, `creator`, + `create_time`, `updater`, `update_time`, `deleted`) +VALUES (9, 'url', 2, 'Spring Boot Admin 监控的地址', 'url.spring-boot-admin', '', b'1', '', '1', '2023-04-07 13:41:16', + '1', '2023-04-07 14:52:07', b'0'); +INSERT INTO `infra_config` (`id`, `category`, `type`, `name`, `config_key`, `value`, `visible`, `remark`, `creator`, + `create_time`, `updater`, `update_time`, `deleted`) +VALUES (11, 'ui', 2, '腾讯地图 key', 'tencent.lbs.key', 'TVDBZ-TDILD-4ON4B-PFDZA-RNLKH-VVF6E', b'1', '腾讯地图 key', + '1', '2023-06-03 19:16:27', '1', '2023-06-03 19:16:27', b'0'); +INSERT INTO `infra_config` (`id`, `category`, `type`, `name`, `config_key`, `value`, `visible`, `remark`, `creator`, + `create_time`, `updater`, `update_time`, `deleted`) +VALUES (12, 'test2', 2, 'test3', 'test4', 'test5', b'1', 'test6', '1', '2023-12-03 09:55:16', '1', + '2023-12-03 09:55:27', b'0'); +COMMIT; + +-- ---------------------------- +-- Table structure for infra_data_source_config +-- ---------------------------- +DROP TABLE IF EXISTS `infra_data_source_config`; +CREATE TABLE `infra_data_source_config` +( + `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键编号', + `name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '参数名称', + `url` varchar(1024) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '数据源连接', + `username` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '用户名', + `password` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '密码', + `creator` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '创建者', + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `updater` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '更新者', + `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 14 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '数据源配置表'; + +-- ---------------------------- +-- Records of infra_data_source_config +-- ---------------------------- +BEGIN; +COMMIT; + +-- ---------------------------- +-- Table structure for infra_file +-- ---------------------------- +DROP TABLE IF EXISTS `infra_file`; +CREATE TABLE `infra_file` +( + `id` bigint NOT NULL AUTO_INCREMENT COMMENT '文件编号', + `config_id` bigint NULL DEFAULT NULL COMMENT '配置编号', + `name` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '文件名', + `path` varchar(512) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '文件路径', + `url` varchar(1024) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '文件 URL', + `type` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '文件类型', + `size` int NOT NULL COMMENT '文件大小', + `creator` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '创建者', + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `updater` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '更新者', + `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 1577 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '文件表'; + +-- ---------------------------- +-- Records of infra_file +-- ---------------------------- +BEGIN; +COMMIT; + +-- ---------------------------- +-- Table structure for infra_file_config +-- ---------------------------- +DROP TABLE IF EXISTS `infra_file_config`; +CREATE TABLE `infra_file_config` +( + `id` bigint NOT NULL AUTO_INCREMENT COMMENT '编号', + `name` varchar(63) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '配置名', + `storage` tinyint NOT NULL COMMENT '存储器', + `remark` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '备注', + `master` bit(1) NOT NULL COMMENT '是否为主配置', + `config` varchar(4096) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '存储配置', + `creator` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '创建者', + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `updater` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '更新者', + `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 29 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '文件配置表'; + +-- ---------------------------- +-- Records of infra_file_config +-- ---------------------------- +BEGIN; +INSERT INTO `infra_file_config` (`id`, `name`, `storage`, `remark`, `master`, `config`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (4, '数据库(示例)', 1, '我是数据库', b'0', + '{\"@class\":\"db.client.core.file.framework.com.tashow.cloud.infraapi.DBFileClientConfig\",\"domain\":\"http://127.0.0.1:48080\"}', + '1', '2022-03-15 23:56:24', '1', '2024-11-09 18:09:28', b'0'); +INSERT INTO `infra_file_config` (`id`, `name`, `storage`, `remark`, `master`, `config`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (22, '七牛存储器(示例)', 20, '请换成你自己的密钥!!!', b'1', + '{\"@class\":\"s3.client.core.file.framework.com.tashow.cloud.infraapi.S3FileClientConfig\",\"endpoint\":\"s3.cn-south-1.qiniucs.com\",\"domain\":\"http://test.yudao.iocoder.cn\",\"bucket\":\"ruoyi-vue-pro\",\"accessKey\":\"3TvrJ70gl2Gt6IBe7_IZT1F6i_k0iMuRtyEv4EyS\",\"accessSecret\":\"wd0tbVBYlp0S-ihA8Qg2hPLncoP83wyrIq24OZuY\"}', + '1', '2024-01-13 22:11:12', '1', '2024-11-09 18:09:28', b'0'); +INSERT INTO `infra_file_config` (`id`, `name`, `storage`, `remark`, `master`, `config`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (24, '腾讯云存储(示例)', 20, '请换成你的密钥!!!', b'0', + '{\"@class\":\"s3.client.core.file.framework.com.tashow.cloud.infraapi.S3FileClientConfig\",\"endpoint\":\"https://cos.ap-shanghai.myqcloud.com\",\"domain\":\"http://tengxun-oss.iocoder.cn\",\"bucket\":\"aoteman-1255880240\",\"accessKey\":\"AKIDAF6WSh1uiIjwqtrOsGSN3WryqTM6cTMt\",\"accessSecret\":\"X\"}', + '1', '2024-11-09 16:03:22', '1', '2024-11-09 18:15:39', b'0'); +INSERT INTO `infra_file_config` (`id`, `name`, `storage`, `remark`, `master`, `config`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (25, '阿里云存储(示例)', 20, '', b'0', + '{\"@class\":\"s3.client.core.file.framework.com.tashow.cloud.infraapi.S3FileClientConfig\",\"endpoint\":\"oss-cn-beijing.aliyuncs.com\",\"domain\":\"http://ali-oss.iocoder.cn\",\"bucket\":\"yunai-aoteman\",\"accessKey\":\"LTAI5tEQLgnDyjh3WpNcdMKA\",\"accessSecret\":\"X\"}', + '1', '2024-11-09 16:47:08', '1', '2024-11-09 18:15:43', b'0'); +INSERT INTO `infra_file_config` (`id`, `name`, `storage`, `remark`, `master`, `config`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (26, '火山云存储(示例)', 20, '', b'0', + '{\"@class\":\"s3.client.core.file.framework.com.tashow.cloud.infraapi.S3FileClientConfig\",\"endpoint\":\"tos-s3-cn-beijing.volces.com\",\"domain\":null,\"bucket\":\"yunai\",\"accessKey\":\"AKLTZjc3Zjc4MzZmMjU3NDk0ZTgxYmIyMmFkNTIwMDI1ZGE\",\"accessSecret\":\"X==\"}', + '1', '2024-11-09 16:56:42', '1', '2024-11-09 18:15:46', b'0'); +INSERT INTO `infra_file_config` (`id`, `name`, `storage`, `remark`, `master`, `config`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (27, '华为云存储(示例)', 20, '', b'0', + '{\"@class\":\"s3.client.core.file.framework.com.tashow.cloud.infraapi.S3FileClientConfig\",\"endpoint\":\"obs.cn-east-3.myhuaweicloud.com\",\"domain\":\"\",\"bucket\":\"yudao\",\"accessKey\":\"PVDONDEIOTW88LF8DC4U\",\"accessSecret\":\"X\"}', + '1', '2024-11-09 17:18:41', '1', '2024-11-09 18:15:49', b'0'); +INSERT INTO `infra_file_config` (`id`, `name`, `storage`, `remark`, `master`, `config`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (28, 'MinIO 存储(示例)', 20, '', b'0', + '{\"@class\":\"s3.client.core.file.framework.com.tashow.cloud.infraapi.S3FileClientConfig\",\"endpoint\":\"http://127.0.0.1:9000\",\"domain\":\"http://127.0.0.1:9000/yudao\",\"bucket\":\"yudao\",\"accessKey\":\"admin\",\"accessSecret\":\"password\"}', + '1', '2024-11-09 17:43:10', '1', '2024-11-09 18:15:52', b'0'); +COMMIT; + +-- ---------------------------- +-- Table structure for infra_file_content +-- ---------------------------- +DROP TABLE IF EXISTS `infra_file_content`; +CREATE TABLE `infra_file_content` +( + `id` bigint NOT NULL AUTO_INCREMENT COMMENT '编号', + `config_id` bigint NOT NULL COMMENT '配置编号', + `path` varchar(512) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '文件路径', + `content` mediumblob NOT NULL COMMENT '文件内容', + `creator` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '创建者', + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `updater` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '更新者', + `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 283 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '文件表'; + +-- ---------------------------- +-- Records of infra_file_content +-- ---------------------------- +BEGIN; +COMMIT; + +-- ---------------------------- +-- Table structure for infra_job +-- ---------------------------- +DROP TABLE IF EXISTS `infra_job`; +CREATE TABLE `infra_job` +( + `id` bigint NOT NULL AUTO_INCREMENT COMMENT '任务编号', + `name` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '任务名称', + `status` tinyint NOT NULL COMMENT '任务状态', + `handler_name` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '处理器的名字', + `handler_param` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '处理器的参数', + `cron_expression` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT 'CRON 表达式', + `retry_count` int NOT NULL DEFAULT 0 COMMENT '重试次数', + `retry_interval` int NOT NULL DEFAULT 0 COMMENT '重试间隔', + `monitor_timeout` int NOT NULL DEFAULT 0 COMMENT '监控超时时间', + `creator` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '创建者', + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `updater` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '更新者', + `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 34 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '定时任务表'; + +-- ---------------------------- +-- Records of infra_job +-- ---------------------------- +BEGIN; +INSERT INTO `infra_job` (`id`, `name`, `status`, `handler_name`, `handler_param`, `cron_expression`, `retry_count`, + `retry_interval`, `monitor_timeout`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`) +VALUES (5, '支付通知 Job', 2, 'payNotifyJob', NULL, '* * * * * ?', 0, 0, 0, '1', '2021-10-27 08:34:42', '1', + '2024-09-12 13:32:48', b'0'); +INSERT INTO `infra_job` (`id`, `name`, `status`, `handler_name`, `handler_param`, `cron_expression`, `retry_count`, + `retry_interval`, `monitor_timeout`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`) +VALUES (17, '支付订单同步 Job', 2, 'payOrderSyncJob', NULL, '0 0/1 * * * ?', 0, 0, 0, '1', '2023-07-22 14:36:26', '1', + '2023-07-22 15:39:08', b'0'); +INSERT INTO `infra_job` (`id`, `name`, `status`, `handler_name`, `handler_param`, `cron_expression`, `retry_count`, + `retry_interval`, `monitor_timeout`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`) +VALUES (18, '支付订单过期 Job', 2, 'payOrderExpireJob', NULL, '0 0/1 * * * ?', 0, 0, 0, '1', '2023-07-22 15:36:23', '1', + '2023-07-22 15:39:54', b'0'); +INSERT INTO `infra_job` (`id`, `name`, `status`, `handler_name`, `handler_param`, `cron_expression`, `retry_count`, + `retry_interval`, `monitor_timeout`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`) +VALUES (19, '退款订单的同步 Job', 2, 'payRefundSyncJob', NULL, '0 0/1 * * * ?', 0, 0, 0, '1', '2023-07-23 21:03:44', + '1', '2023-07-23 21:09:00', b'0'); +INSERT INTO `infra_job` (`id`, `name`, `status`, `handler_name`, `handler_param`, `cron_expression`, `retry_count`, + `retry_interval`, `monitor_timeout`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`) +VALUES (21, '交易订单的自动过期 Job', 2, 'tradeOrderAutoCancelJob', '', '0 * * * * ?', 3, 0, 0, '1', + '2023-09-25 23:43:26', '1', '2023-09-26 19:23:30', b'0'); +INSERT INTO `infra_job` (`id`, `name`, `status`, `handler_name`, `handler_param`, `cron_expression`, `retry_count`, + `retry_interval`, `monitor_timeout`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`) +VALUES (22, '交易订单的自动收货 Job', 2, 'tradeOrderAutoReceiveJob', '', '0 * * * * ?', 3, 0, 0, '1', + '2023-09-26 19:23:53', '1', '2023-09-26 23:38:08', b'0'); +INSERT INTO `infra_job` (`id`, `name`, `status`, `handler_name`, `handler_param`, `cron_expression`, `retry_count`, + `retry_interval`, `monitor_timeout`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`) +VALUES (23, '交易订单的自动评论 Job', 2, 'tradeOrderAutoCommentJob', '', '0 * * * * ?', 3, 0, 0, '1', + '2023-09-26 23:38:29', '1', '2023-09-27 11:03:10', b'0'); +INSERT INTO `infra_job` (`id`, `name`, `status`, `handler_name`, `handler_param`, `cron_expression`, `retry_count`, + `retry_interval`, `monitor_timeout`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`) +VALUES (24, '佣金解冻 Job', 2, 'brokerageRecordUnfreezeJob', '', '0 * * * * ?', 3, 0, 0, '1', '2023-09-28 22:01:46', + '1', '2023-09-28 22:01:56', b'0'); +INSERT INTO `infra_job` (`id`, `name`, `status`, `handler_name`, `handler_param`, `cron_expression`, `retry_count`, + `retry_interval`, `monitor_timeout`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`) +VALUES (25, '访问日志清理 Job', 2, 'accessLogCleanJob', '', '0 0 0 * * ?', 3, 0, 0, '1', '2023-10-03 10:59:41', '1', + '2023-10-03 11:01:10', b'0'); +INSERT INTO `infra_job` (`id`, `name`, `status`, `handler_name`, `handler_param`, `cron_expression`, `retry_count`, + `retry_interval`, `monitor_timeout`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`) +VALUES (26, '错误日志清理 Job', 2, 'errorLogCleanJob', '', '0 0 0 * * ?', 3, 0, 0, '1', '2023-10-03 11:00:43', '1', + '2023-10-03 11:01:12', b'0'); +INSERT INTO `infra_job` (`id`, `name`, `status`, `handler_name`, `handler_param`, `cron_expression`, `retry_count`, + `retry_interval`, `monitor_timeout`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`) +VALUES (27, '任务日志清理 Job', 2, 'jobLogCleanJob', '', '0 0 0 * * ?', 3, 0, 0, '1', '2023-10-03 11:01:33', '1', + '2024-09-12 13:40:34', b'0'); +INSERT INTO `infra_job` (`id`, `name`, `status`, `handler_name`, `handler_param`, `cron_expression`, `retry_count`, + `retry_interval`, `monitor_timeout`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`) +VALUES (33, 'demoJob', 2, 'demoJob', '', '0 * * * * ?', 1, 1, 0, '1', '2024-10-27 19:38:46', '1', '2024-10-27 19:40:23', + b'0'); +COMMIT; + +-- ---------------------------- +-- Table structure for infra_job_log +-- ---------------------------- +DROP TABLE IF EXISTS `infra_job_log`; +CREATE TABLE `infra_job_log` +( + `id` bigint NOT NULL AUTO_INCREMENT COMMENT '日志编号', + `job_id` bigint NOT NULL COMMENT '任务编号', + `handler_name` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '处理器的名字', + `handler_param` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '处理器的参数', + `execute_index` tinyint NOT NULL DEFAULT 1 COMMENT '第几次执行', + `begin_time` datetime NOT NULL COMMENT '开始执行时间', + `end_time` datetime NULL DEFAULT NULL COMMENT '结束执行时间', + `duration` int NULL DEFAULT NULL COMMENT '执行时长', + `status` tinyint NOT NULL COMMENT '任务状态', + `result` varchar(4000) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '结果数据', + `creator` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '创建者', + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `updater` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '更新者', + `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 638 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '定时任务日志表'; + +-- ---------------------------- +-- Records of infra_job_log +-- ---------------------------- +BEGIN; +COMMIT; + +-- ---------------------------- +-- Table structure for system_dept +-- ---------------------------- +DROP TABLE IF EXISTS `system_dept`; +CREATE TABLE `system_dept` +( + `id` bigint NOT NULL AUTO_INCREMENT COMMENT '部门id', + `name` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '部门名称', + `parent_id` bigint NOT NULL DEFAULT 0 COMMENT '父部门id', + `sort` int NOT NULL DEFAULT 0 COMMENT '显示顺序', + `leader_user_id` bigint NULL DEFAULT NULL COMMENT '负责人', + `phone` varchar(11) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '联系电话', + `email` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '邮箱', + `status` tinyint NOT NULL COMMENT '部门状态(0正常 1停用)', + `creator` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '创建者', + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `updater` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '更新者', + `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除', + `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 115 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '部门表'; + +-- ---------------------------- +-- Records of system_dept +-- ---------------------------- +BEGIN; +INSERT INTO `system_dept` (`id`, `name`, `parent_id`, `sort`, `leader_user_id`, `phone`, `email`, `status`, `creator`, + `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) +VALUES (100, '芋道源码', 0, 0, 1, '15888888888', 'ry@qq.com', 0, 'admin', '2021-01-05 17:03:47', '1', + '2023-11-14 23:30:36', b'0', 1); +INSERT INTO `system_dept` (`id`, `name`, `parent_id`, `sort`, `leader_user_id`, `phone`, `email`, `status`, `creator`, + `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) +VALUES (101, '深圳总公司', 100, 1, 104, '15888888888', 'ry@qq.com', 0, 'admin', '2021-01-05 17:03:47', '1', + '2023-12-02 09:53:35', b'0', 1); +INSERT INTO `system_dept` (`id`, `name`, `parent_id`, `sort`, `leader_user_id`, `phone`, `email`, `status`, `creator`, + `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) +VALUES (102, '长沙分公司', 100, 2, NULL, '15888888888', 'ry@qq.com', 0, 'admin', '2021-01-05 17:03:47', '', + '2021-12-15 05:01:40', b'0', 1); +INSERT INTO `system_dept` (`id`, `name`, `parent_id`, `sort`, `leader_user_id`, `phone`, `email`, `status`, `creator`, + `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) +VALUES (103, '研发部门', 101, 1, 1, '15888888888', 'ry@qq.com', 0, 'admin', '2021-01-05 17:03:47', '1', + '2024-10-02 10:22:03', b'0', 1); +INSERT INTO `system_dept` (`id`, `name`, `parent_id`, `sort`, `leader_user_id`, `phone`, `email`, `status`, `creator`, + `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) +VALUES (104, '市场部门', 101, 2, NULL, '15888888888', 'ry@qq.com', 0, 'admin', '2021-01-05 17:03:47', '', + '2021-12-15 05:01:38', b'0', 1); +INSERT INTO `system_dept` (`id`, `name`, `parent_id`, `sort`, `leader_user_id`, `phone`, `email`, `status`, `creator`, + `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) +VALUES (105, '测试部门', 101, 3, NULL, '15888888888', 'ry@qq.com', 0, 'admin', '2021-01-05 17:03:47', '1', + '2022-05-16 20:25:15', b'0', 1); +INSERT INTO `system_dept` (`id`, `name`, `parent_id`, `sort`, `leader_user_id`, `phone`, `email`, `status`, `creator`, + `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) +VALUES (106, '财务部门', 101, 4, 103, '15888888888', 'ry@qq.com', 0, 'admin', '2021-01-05 17:03:47', '103', + '2022-01-15 21:32:22', b'0', 1); +INSERT INTO `system_dept` (`id`, `name`, `parent_id`, `sort`, `leader_user_id`, `phone`, `email`, `status`, `creator`, + `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) +VALUES (107, '运维部门', 101, 5, 1, '15888888888', 'ry@qq.com', 0, 'admin', '2021-01-05 17:03:47', '1', + '2023-12-02 09:28:22', b'0', 1); +INSERT INTO `system_dept` (`id`, `name`, `parent_id`, `sort`, `leader_user_id`, `phone`, `email`, `status`, `creator`, + `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) +VALUES (108, '市场部门', 102, 1, NULL, '15888888888', 'ry@qq.com', 0, 'admin', '2021-01-05 17:03:47', '1', + '2022-02-16 08:35:45', b'0', 1); +INSERT INTO `system_dept` (`id`, `name`, `parent_id`, `sort`, `leader_user_id`, `phone`, `email`, `status`, `creator`, + `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) +VALUES (109, '财务部门', 102, 2, NULL, '15888888888', 'ry@qq.com', 0, 'admin', '2021-01-05 17:03:47', '', + '2021-12-15 05:01:29', b'0', 1); +INSERT INTO `system_dept` (`id`, `name`, `parent_id`, `sort`, `leader_user_id`, `phone`, `email`, `status`, `creator`, + `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) +VALUES (110, '新部门', 0, 1, NULL, NULL, NULL, 0, '110', '2022-02-23 20:46:30', '110', '2022-02-23 20:46:30', b'0', + 121); +INSERT INTO `system_dept` (`id`, `name`, `parent_id`, `sort`, `leader_user_id`, `phone`, `email`, `status`, `creator`, + `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) +VALUES (111, '顶级部门', 0, 1, NULL, NULL, NULL, 0, '113', '2022-03-07 21:44:50', '113', '2022-03-07 21:44:50', b'0', + 122); +INSERT INTO `system_dept` (`id`, `name`, `parent_id`, `sort`, `leader_user_id`, `phone`, `email`, `status`, `creator`, + `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) +VALUES (112, '产品部门', 101, 100, 1, NULL, NULL, 1, '1', '2023-12-02 09:45:13', '1', '2023-12-02 09:45:31', b'0', 1); +INSERT INTO `system_dept` (`id`, `name`, `parent_id`, `sort`, `leader_user_id`, `phone`, `email`, `status`, `creator`, + `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) +VALUES (113, '支持部门', 102, 3, 104, NULL, NULL, 1, '1', '2023-12-02 09:47:38', '1', '2023-12-02 09:47:38', b'0', 1); +COMMIT; + +-- ---------------------------- +-- Table structure for system_dict_data +-- ---------------------------- +DROP TABLE IF EXISTS `system_dict_data`; +CREATE TABLE `system_dict_data` +( + `id` bigint NOT NULL AUTO_INCREMENT COMMENT '字典编码', + `sort` int NOT NULL DEFAULT 0 COMMENT '字典排序', + `label` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '字典标签', + `value` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '字典键值', + `dict_type` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '字典类型', + `status` tinyint NOT NULL DEFAULT 0 COMMENT '状态(0正常 1停用)', + `color_type` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '颜色类型', + `css_class` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT 'css 样式', + `remark` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '备注', + `creator` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '创建者', + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `updater` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '更新者', + `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 1683 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '字典数据表'; + +-- ---------------------------- +-- Records of system_dict_data +-- ---------------------------- +BEGIN; +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1, 1, '男', '1', 'system_user_sex', 0, 'default', 'A', '性别男', 'admin', '2021-01-05 17:03:48', '1', + '2022-03-29 00:14:39', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (2, 2, '女', '2', 'system_user_sex', 0, 'success', '', '性别女', 'admin', '2021-01-05 17:03:48', '1', + '2023-11-15 23:30:37', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (8, 1, '正常', '1', 'infra_job_status', 0, 'success', '', '正常状态', 'admin', '2021-01-05 17:03:48', '1', + '2022-02-16 19:33:38', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (9, 2, '暂停', '2', 'infra_job_status', 0, 'danger', '', '停用状态', 'admin', '2021-01-05 17:03:48', '1', + '2022-02-16 19:33:45', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (12, 1, '系统内置', '1', 'infra_config_type', 0, 'danger', '', '参数类型 - 系统内置', 'admin', + '2021-01-05 17:03:48', '1', '2022-02-16 19:06:02', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (13, 2, '自定义', '2', 'infra_config_type', 0, 'primary', '', '参数类型 - 自定义', 'admin', + '2021-01-05 17:03:48', '1', '2022-02-16 19:06:07', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (14, 1, '通知', '1', 'system_notice_type', 0, 'success', '', '通知', 'admin', '2021-01-05 17:03:48', '1', + '2022-02-16 13:05:57', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (15, 2, '公告', '2', 'system_notice_type', 0, 'info', '', '公告', 'admin', '2021-01-05 17:03:48', '1', + '2022-02-16 13:06:01', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (16, 0, '其它', '0', 'infra_operate_type', 0, 'default', '', '其它操作', 'admin', '2021-01-05 17:03:48', '1', + '2024-03-14 12:44:19', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (17, 1, '查询', '1', 'infra_operate_type', 0, 'info', '', '查询操作', 'admin', '2021-01-05 17:03:48', '1', + '2024-03-14 12:44:20', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (18, 2, '新增', '2', 'infra_operate_type', 0, 'primary', '', '新增操作', 'admin', '2021-01-05 17:03:48', '1', + '2024-03-14 12:44:21', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (19, 3, '修改', '3', 'infra_operate_type', 0, 'warning', '', '修改操作', 'admin', '2021-01-05 17:03:48', '1', + '2024-03-14 12:44:22', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (20, 4, '删除', '4', 'infra_operate_type', 0, 'danger', '', '删除操作', 'admin', '2021-01-05 17:03:48', '1', + '2024-03-14 12:44:23', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (22, 5, '导出', '5', 'infra_operate_type', 0, 'default', '', '导出操作', 'admin', '2021-01-05 17:03:48', '1', + '2024-03-14 12:44:24', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (23, 6, '导入', '6', 'infra_operate_type', 0, 'default', '', '导入操作', 'admin', '2021-01-05 17:03:48', '1', + '2024-03-14 12:44:25', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (27, 1, '开启', '0', 'common_status', 0, 'primary', '', '开启状态', 'admin', '2021-01-05 17:03:48', '1', + '2022-02-16 08:00:39', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (28, 2, '关闭', '1', 'common_status', 0, 'info', '', '关闭状态', 'admin', '2021-01-05 17:03:48', '1', + '2022-02-16 08:00:44', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (29, 1, '目录', '1', 'system_menu_type', 0, '', '', '目录', 'admin', '2021-01-05 17:03:48', '', + '2022-02-01 16:43:45', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (30, 2, '菜单', '2', 'system_menu_type', 0, '', '', '菜单', 'admin', '2021-01-05 17:03:48', '', + '2022-02-01 16:43:41', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (31, 3, '按钮', '3', 'system_menu_type', 0, '', '', '按钮', 'admin', '2021-01-05 17:03:48', '', + '2022-02-01 16:43:39', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (32, 1, '内置', '1', 'system_role_type', 0, 'danger', '', '内置角色', 'admin', '2021-01-05 17:03:48', '1', + '2022-02-16 13:02:08', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (33, 2, '自定义', '2', 'system_role_type', 0, 'primary', '', '自定义角色', 'admin', '2021-01-05 17:03:48', '1', + '2022-02-16 13:02:12', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (34, 1, '全部数据权限', '1', 'system_data_scope', 0, '', '', '全部数据权限', 'admin', '2021-01-05 17:03:48', '', + '2022-02-01 16:47:17', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (35, 2, '指定部门数据权限', '2', 'system_data_scope', 0, '', '', '指定部门数据权限', 'admin', + '2021-01-05 17:03:48', '', '2022-02-01 16:47:18', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (36, 3, '本部门数据权限', '3', 'system_data_scope', 0, '', '', '本部门数据权限', 'admin', '2021-01-05 17:03:48', + '', '2022-02-01 16:47:16', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (37, 4, '本部门及以下数据权限', '4', 'system_data_scope', 0, '', '', '本部门及以下数据权限', 'admin', + '2021-01-05 17:03:48', '', '2022-02-01 16:47:21', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (38, 5, '仅本人数据权限', '5', 'system_data_scope', 0, '', '', '仅本人数据权限', 'admin', '2021-01-05 17:03:48', + '', '2022-02-01 16:47:23', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (39, 0, '成功', '0', 'system_login_result', 0, 'success', '', '登陆结果 - 成功', '', '2021-01-18 06:17:36', '1', + '2022-02-16 13:23:49', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (40, 10, '账号或密码不正确', '10', 'system_login_result', 0, 'primary', '', '登陆结果 - 账号或密码不正确', '', + '2021-01-18 06:17:54', '1', '2022-02-16 13:24:27', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (41, 20, '用户被禁用', '20', 'system_login_result', 0, 'warning', '', '登陆结果 - 用户被禁用', '', + '2021-01-18 06:17:54', '1', '2022-02-16 13:23:57', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (42, 30, '验证码不存在', '30', 'system_login_result', 0, 'info', '', '登陆结果 - 验证码不存在', '', + '2021-01-18 06:17:54', '1', '2022-02-16 13:24:07', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (43, 31, '验证码不正确', '31', 'system_login_result', 0, 'info', '', '登陆结果 - 验证码不正确', '', + '2021-01-18 06:17:54', '1', '2022-02-16 13:24:11', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (44, 100, '未知异常', '100', 'system_login_result', 0, 'danger', '', '登陆结果 - 未知异常', '', + '2021-01-18 06:17:54', '1', '2022-02-16 13:24:23', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (45, 1, '是', 'true', 'infra_boolean_string', 0, 'danger', '', 'Boolean 是否类型 - 是', '', + '2021-01-19 03:20:55', '1', '2022-03-15 23:01:45', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (46, 1, '否', 'false', 'infra_boolean_string', 0, 'info', '', 'Boolean 是否类型 - 否', '', '2021-01-19 03:20:55', + '1', '2022-03-15 23:09:45', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (50, 1, '单表(增删改查)', '1', 'infra_codegen_template_type', 0, '', '', NULL, '', '2021-02-05 07:09:06', '', + '2022-03-10 16:33:15', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (51, 2, '树表(增删改查)', '2', 'infra_codegen_template_type', 0, '', '', NULL, '', '2021-02-05 07:14:46', '', + '2022-03-10 16:33:19', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (53, 0, '初始化中', '0', 'infra_job_status', 0, 'primary', '', NULL, '', '2021-02-07 07:46:49', '1', + '2022-02-16 19:33:29', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (57, 0, '运行中', '0', 'infra_job_log_status', 0, 'primary', '', 'RUNNING', '', '2021-02-08 10:04:24', '1', + '2022-02-16 19:07:48', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (58, 1, '成功', '1', 'infra_job_log_status', 0, 'success', '', NULL, '', '2021-02-08 10:06:57', '1', + '2022-02-16 19:07:52', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (59, 2, '失败', '2', 'infra_job_log_status', 0, 'warning', '', '失败', '', '2021-02-08 10:07:38', '1', + '2022-02-16 19:07:56', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (60, 1, '会员', '1', 'user_type', 0, 'primary', '', NULL, '', '2021-02-26 00:16:27', '1', '2022-02-16 10:22:19', + b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (61, 2, '管理员', '2', 'user_type', 0, 'success', '', NULL, '', '2021-02-26 00:16:34', '1', + '2022-02-16 10:22:22', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (62, 0, '未处理', '0', 'infra_api_error_log_process_status', 0, 'primary', '', NULL, '', '2021-02-26 07:07:19', + '1', '2022-02-16 20:14:17', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (63, 1, '已处理', '1', 'infra_api_error_log_process_status', 0, 'success', '', NULL, '', '2021-02-26 07:07:26', + '1', '2022-02-16 20:14:08', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (64, 2, '已忽略', '2', 'infra_api_error_log_process_status', 0, 'danger', '', NULL, '', '2021-02-26 07:07:34', + '1', '2022-02-16 20:14:14', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (66, 1, '阿里云', 'ALIYUN', 'system_sms_channel_code', 0, 'primary', '', NULL, '1', '2021-04-05 01:05:26', '1', + '2024-07-22 22:23:25', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (67, 1, '验证码', '1', 'system_sms_template_type', 0, 'warning', '', NULL, '1', '2021-04-05 21:50:57', '1', + '2022-02-16 12:48:30', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (68, 2, '通知', '2', 'system_sms_template_type', 0, 'primary', '', NULL, '1', '2021-04-05 21:51:08', '1', + '2022-02-16 12:48:27', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (69, 0, '营销', '3', 'system_sms_template_type', 0, 'danger', '', NULL, '1', '2021-04-05 21:51:15', '1', + '2022-02-16 12:48:22', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (70, 0, '初始化', '0', 'system_sms_send_status', 0, 'primary', '', NULL, '1', '2021-04-11 20:18:33', '1', + '2022-02-16 10:26:07', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (71, 1, '发送成功', '10', 'system_sms_send_status', 0, 'success', '', NULL, '1', '2021-04-11 20:18:43', '1', + '2022-02-16 10:25:56', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (72, 2, '发送失败', '20', 'system_sms_send_status', 0, 'danger', '', NULL, '1', '2021-04-11 20:18:49', '1', + '2022-02-16 10:26:03', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (73, 3, '不发送', '30', 'system_sms_send_status', 0, 'info', '', NULL, '1', '2021-04-11 20:19:44', '1', + '2022-02-16 10:26:10', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (74, 0, '等待结果', '0', 'system_sms_receive_status', 0, 'primary', '', NULL, '1', '2021-04-11 20:27:43', '1', + '2022-02-16 10:28:24', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (75, 1, '接收成功', '10', 'system_sms_receive_status', 0, 'success', '', NULL, '1', '2021-04-11 20:29:25', '1', + '2022-02-16 10:28:28', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (76, 2, '接收失败', '20', 'system_sms_receive_status', 0, 'danger', '', NULL, '1', '2021-04-11 20:29:31', '1', + '2022-02-16 10:28:32', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (77, 0, '调试(钉钉)', 'DEBUG_DING_TALK', 'system_sms_channel_code', 0, 'info', '', NULL, '1', + '2021-04-13 00:20:37', '1', '2022-02-16 10:10:00', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (80, 100, '账号登录', '100', 'system_login_type', 0, 'primary', '', '账号登录', '1', '2021-10-06 00:52:02', '1', + '2022-02-16 13:11:34', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (81, 101, '社交登录', '101', 'system_login_type', 0, 'info', '', '社交登录', '1', '2021-10-06 00:52:17', '1', + '2022-02-16 13:11:40', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (83, 200, '主动登出', '200', 'system_login_type', 0, 'primary', '', '主动登出', '1', '2021-10-06 00:52:58', '1', + '2022-02-16 13:11:49', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (85, 202, '强制登出', '202', 'system_login_type', 0, 'danger', '', '强制退出', '1', '2021-10-06 00:53:41', '1', + '2022-02-16 13:11:57', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (86, 0, '病假', '1', 'bpm_oa_leave_type', 0, 'primary', '', NULL, '1', '2021-09-21 22:35:28', '1', + '2022-02-16 10:00:41', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (87, 1, '事假', '2', 'bpm_oa_leave_type', 0, 'info', '', NULL, '1', '2021-09-21 22:36:11', '1', + '2022-02-16 10:00:49', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (88, 2, '婚假', '3', 'bpm_oa_leave_type', 0, 'warning', '', NULL, '1', '2021-09-21 22:36:38', '1', + '2022-02-16 10:00:53', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (112, 0, '微信 Wap 网站支付', 'wx_wap', 'pay_channel_code', 0, 'success', '', '微信 Wap 网站支付', '1', + '2023-07-19 20:08:06', '1', '2023-07-19 20:09:08', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (113, 1, '微信公众号支付', 'wx_pub', 'pay_channel_code', 0, 'success', '', '微信公众号支付', '1', + '2021-12-03 10:40:24', '1', '2023-07-19 20:08:47', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (114, 2, '微信小程序支付', 'wx_lite', 'pay_channel_code', 0, 'success', '', '微信小程序支付', '1', + '2021-12-03 10:41:06', '1', '2023-07-19 20:08:50', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (115, 3, '微信 App 支付', 'wx_app', 'pay_channel_code', 0, 'success', '', '微信 App 支付', '1', + '2021-12-03 10:41:20', '1', '2023-07-19 20:08:56', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (116, 10, '支付宝 PC 网站支付', 'alipay_pc', 'pay_channel_code', 0, 'primary', '', '支付宝 PC 网站支付', '1', + '2021-12-03 10:42:09', '1', '2023-07-19 20:09:12', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (117, 11, '支付宝 Wap 网站支付', 'alipay_wap', 'pay_channel_code', 0, 'primary', '', '支付宝 Wap 网站支付', '1', + '2021-12-03 10:42:26', '1', '2023-07-19 20:09:16', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (118, 12, '支付宝 App 支付', 'alipay_app', 'pay_channel_code', 0, 'primary', '', '支付宝 App 支付', '1', + '2021-12-03 10:42:55', '1', '2023-07-19 20:09:20', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (119, 14, '支付宝扫码支付', 'alipay_qr', 'pay_channel_code', 0, 'primary', '', '支付宝扫码支付', '1', + '2021-12-03 10:43:10', '1', '2023-07-19 20:09:28', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (120, 10, '通知成功', '10', 'pay_notify_status', 0, 'success', '', '通知成功', '1', '2021-12-03 11:02:41', '1', + '2023-07-19 10:08:19', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (121, 20, '通知失败', '20', 'pay_notify_status', 0, 'danger', '', '通知失败', '1', '2021-12-03 11:02:59', '1', + '2023-07-19 10:08:21', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (122, 0, '等待通知', '0', 'pay_notify_status', 0, 'info', '', '未通知', '1', '2021-12-03 11:03:10', '1', + '2023-07-19 10:08:24', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (123, 10, '支付成功', '10', 'pay_order_status', 0, 'success', '', '支付成功', '1', '2021-12-03 11:18:29', '1', + '2023-07-19 18:04:28', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (124, 30, '支付关闭', '30', 'pay_order_status', 0, 'info', '', '支付关闭', '1', '2021-12-03 11:18:42', '1', + '2023-07-19 18:05:07', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (125, 0, '等待支付', '0', 'pay_order_status', 0, 'info', '', '未支付', '1', '2021-12-03 11:18:18', '1', + '2023-07-19 18:04:15', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (600, 5, '首页', '1', 'promotion_banner_position', 0, 'warning', '', '', '1', '2023-10-11 07:45:24', '1', + '2023-10-11 07:45:38', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (601, 4, '秒杀活动页', '2', 'promotion_banner_position', 0, 'warning', '', '', '1', '2023-10-11 07:45:24', '1', + '2023-10-11 07:45:38', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (602, 3, '砍价活动页', '3', 'promotion_banner_position', 0, 'warning', '', '', '1', '2023-10-11 07:45:24', '1', + '2023-10-11 07:45:38', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (603, 2, '限时折扣页', '4', 'promotion_banner_position', 0, 'warning', '', '', '1', '2023-10-11 07:45:24', '1', + '2023-10-11 07:45:38', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (604, 1, '满减送页', '5', 'promotion_banner_position', 0, 'warning', '', '', '1', '2023-10-11 07:45:24', '1', + '2023-10-11 07:45:38', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1118, 0, '等待退款', '0', 'pay_refund_status', 0, 'info', '', '等待退款', '1', '2021-12-10 16:44:59', '1', + '2023-07-19 10:14:39', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1119, 20, '退款失败', '20', 'pay_refund_status', 0, 'danger', '', '退款失败', '1', '2021-12-10 16:45:10', '1', + '2023-07-19 10:15:10', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1124, 10, '退款成功', '10', 'pay_refund_status', 0, 'success', '', '退款成功', '1', '2021-12-10 16:46:26', '1', + '2023-07-19 10:15:00', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1127, 1, '审批中', '1', 'bpm_process_instance_status', 0, 'default', '', '流程实例的状态 - 进行中', '1', + '2022-01-07 23:47:22', '1', '2024-03-16 16:11:45', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1128, 2, '审批通过', '2', 'bpm_process_instance_status', 0, 'success', '', '流程实例的状态 - 已完成', '1', + '2022-01-07 23:47:49', '1', '2024-03-16 16:11:54', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1129, 1, '审批中', '1', 'bpm_task_status', 0, 'primary', '', '流程实例的结果 - 处理中', '1', + '2022-01-07 23:48:32', '1', '2024-03-08 22:41:37', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1130, 2, '审批通过', '2', 'bpm_task_status', 0, 'success', '', '流程实例的结果 - 通过', '1', + '2022-01-07 23:48:45', '1', '2024-03-08 22:41:38', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1131, 3, '审批不通过', '3', 'bpm_task_status', 0, 'danger', '', '流程实例的结果 - 不通过', '1', + '2022-01-07 23:48:55', '1', '2024-03-08 22:41:38', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1132, 4, '已取消', '4', 'bpm_task_status', 0, 'info', '', '流程实例的结果 - 撤销', '1', '2022-01-07 23:49:06', + '1', '2024-03-08 22:41:39', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1133, 10, '流程表单', '10', 'bpm_model_form_type', 0, '', '', '流程的表单类型 - 流程表单', '103', + '2022-01-11 23:51:30', '103', '2022-01-11 23:51:30', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1134, 20, '业务表单', '20', 'bpm_model_form_type', 0, '', '', '流程的表单类型 - 业务表单', '103', + '2022-01-11 23:51:47', '103', '2022-01-11 23:51:47', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1135, 10, '角色', '10', 'bpm_task_candidate_strategy', 0, 'info', '', '任务分配规则的类型 - 角色', '103', + '2022-01-12 23:21:22', '1', '2024-03-06 02:53:16', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1136, 20, '部门的成员', '20', 'bpm_task_candidate_strategy', 0, 'primary', '', + '任务分配规则的类型 - 部门的成员', '103', '2022-01-12 23:21:47', '1', '2024-03-06 02:53:17', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1137, 21, '部门的负责人', '21', 'bpm_task_candidate_strategy', 0, 'primary', '', + '任务分配规则的类型 - 部门的负责人', '103', '2022-01-12 23:33:36', '1', '2024-03-06 02:53:18', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1138, 30, '用户', '30', 'bpm_task_candidate_strategy', 0, 'info', '', '任务分配规则的类型 - 用户', '103', + '2022-01-12 23:34:02', '1', '2024-03-06 02:53:19', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1139, 40, '用户组', '40', 'bpm_task_candidate_strategy', 0, 'warning', '', '任务分配规则的类型 - 用户组', '103', + '2022-01-12 23:34:21', '1', '2024-03-06 02:53:20', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1140, 60, '流程表达式', '60', 'bpm_task_candidate_strategy', 0, 'danger', '', '任务分配规则的类型 - 流程表达式', + '103', '2022-01-12 23:34:43', '1', '2024-03-06 02:53:20', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1141, 22, '岗位', '22', 'bpm_task_candidate_strategy', 0, 'success', '', '任务分配规则的类型 - 岗位', '103', + '2022-01-14 18:41:55', '1', '2024-03-06 02:53:21', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1145, 1, '管理后台', '1', 'infra_codegen_scene', 0, '', '', '代码生成的场景枚举 - 管理后台', '1', + '2022-02-02 13:15:06', '1', '2022-03-10 16:32:59', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1146, 2, '用户 APP', '2', 'infra_codegen_scene', 0, '', '', '代码生成的场景枚举 - 用户 APP', '1', + '2022-02-02 13:15:19', '1', '2022-03-10 16:33:03', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1150, 1, '数据库', '1', 'infra_file_storage', 0, 'default', '', NULL, '1', '2022-03-15 00:25:28', '1', + '2022-03-15 00:25:28', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1151, 10, '本地磁盘', '10', 'infra_file_storage', 0, 'default', '', NULL, '1', '2022-03-15 00:25:41', '1', + '2022-03-15 00:25:56', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1152, 11, 'FTP 服务器', '11', 'infra_file_storage', 0, 'default', '', NULL, '1', '2022-03-15 00:26:06', '1', + '2022-03-15 00:26:10', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1153, 12, 'SFTP 服务器', '12', 'infra_file_storage', 0, 'default', '', NULL, '1', '2022-03-15 00:26:22', '1', + '2022-03-15 00:26:22', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1154, 20, 'S3 对象存储', '20', 'infra_file_storage', 0, 'default', '', NULL, '1', '2022-03-15 00:26:31', '1', + '2022-03-15 00:26:45', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1155, 103, '短信登录', '103', 'system_login_type', 0, 'default', '', NULL, '1', '2022-05-09 23:57:58', '1', + '2022-05-09 23:58:09', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1156, 1, 'password', 'password', 'system_oauth2_grant_type', 0, 'default', '', '密码模式', '1', + '2022-05-12 00:22:05', '1', '2022-05-11 16:26:01', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1157, 2, 'authorization_code', 'authorization_code', 'system_oauth2_grant_type', 0, 'primary', '', '授权码模式', + '1', '2022-05-12 00:22:59', '1', '2022-05-11 16:26:02', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1158, 3, 'implicit', 'implicit', 'system_oauth2_grant_type', 0, 'success', '', '简化模式', '1', + '2022-05-12 00:23:40', '1', '2022-05-11 16:26:05', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1159, 4, 'client_credentials', 'client_credentials', 'system_oauth2_grant_type', 0, 'default', '', '客户端模式', + '1', '2022-05-12 00:23:51', '1', '2022-05-11 16:26:08', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1160, 5, 'refresh_token', 'refresh_token', 'system_oauth2_grant_type', 0, 'info', '', '刷新模式', '1', + '2022-05-12 00:24:02', '1', '2022-05-11 16:26:11', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1162, 1, '销售中', '1', 'product_spu_status', 0, 'success', '', '商品 SPU 状态 - 销售中', '1', + '2022-10-24 21:19:47', '1', '2022-10-24 21:20:38', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1163, 0, '仓库中', '0', 'product_spu_status', 0, 'info', '', '商品 SPU 状态 - 仓库中', '1', + '2022-10-24 21:20:54', '1', '2022-10-24 21:21:22', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1164, 0, '回收站', '-1', 'product_spu_status', 0, 'default', '', '商品 SPU 状态 - 回收站', '1', + '2022-10-24 21:21:11', '1', '2022-10-24 21:21:11', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1165, 1, '满减', '1', 'promotion_discount_type', 0, 'success', '', '优惠类型 - 满减', '1', + '2022-11-01 12:46:41', '1', '2022-11-01 12:50:11', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1166, 2, '折扣', '2', 'promotion_discount_type', 0, 'primary', '', '优惠类型 - 折扣', '1', + '2022-11-01 12:46:51', '1', '2022-11-01 12:50:08', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1167, 1, '固定日期', '1', 'promotion_coupon_template_validity_type', 0, 'default', '', + '优惠劵模板的有限期类型 - 固定日期', '1', '2022-11-02 00:07:34', '1', '2022-11-04 00:07:49', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1168, 2, '领取之后', '2', 'promotion_coupon_template_validity_type', 0, 'default', '', + '优惠劵模板的有限期类型 - 领取之后', '1', '2022-11-02 00:07:54', '1', '2022-11-04 00:07:52', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1169, 1, '通用劵', '1', 'promotion_product_scope', 0, 'default', '', '营销的商品范围 - 全部商品参与', '1', + '2022-11-02 00:28:22', '1', '2023-09-28 00:27:42', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1170, 2, '商品劵', '2', 'promotion_product_scope', 0, 'default', '', '营销的商品范围 - 指定商品参与', '1', + '2022-11-02 00:28:34', '1', '2023-09-28 00:27:44', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1171, 1, '未使用', '1', 'promotion_coupon_status', 0, 'primary', '', '优惠劵的状态 - 已领取', '1', + '2022-11-04 00:15:08', '1', '2023-10-03 12:54:38', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1172, 2, '已使用', '2', 'promotion_coupon_status', 0, 'success', '', '优惠劵的状态 - 已使用', '1', + '2022-11-04 00:15:21', '1', '2022-11-04 19:16:08', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1173, 3, '已过期', '3', 'promotion_coupon_status', 0, 'info', '', '优惠劵的状态 - 已过期', '1', + '2022-11-04 00:15:43', '1', '2022-11-04 19:16:12', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1174, 1, '直接领取', '1', 'promotion_coupon_take_type', 0, 'primary', '', '优惠劵的领取方式 - 直接领取', '1', + '2022-11-04 19:13:00', '1', '2022-11-04 19:13:25', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1175, 2, '指定发放', '2', 'promotion_coupon_take_type', 0, 'success', '', '优惠劵的领取方式 - 指定发放', '1', + '2022-11-04 19:13:13', '1', '2022-11-04 19:14:48', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1176, 10, '未开始', '10', 'promotion_activity_status', 0, 'primary', '', '促销活动的状态枚举 - 未开始', '1', + '2022-11-04 22:54:49', '1', '2022-11-04 22:55:53', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1177, 20, '进行中', '20', 'promotion_activity_status', 0, 'success', '', '促销活动的状态枚举 - 进行中', '1', + '2022-11-04 22:55:06', '1', '2022-11-04 22:55:20', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1178, 30, '已结束', '30', 'promotion_activity_status', 0, 'info', '', '促销活动的状态枚举 - 已结束', '1', + '2022-11-04 22:55:41', '1', '2022-11-04 22:55:41', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1179, 40, '已关闭', '40', 'promotion_activity_status', 0, 'warning', '', '促销活动的状态枚举 - 已关闭', '1', + '2022-11-04 22:56:10', '1', '2022-11-04 22:56:18', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1180, 10, '满 N 元', '10', 'promotion_condition_type', 0, 'primary', '', '营销的条件类型 - 满 N 元', '1', + '2022-11-04 22:59:45', '1', '2022-11-04 22:59:45', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1181, 20, '满 N 件', '20', 'promotion_condition_type', 0, 'success', '', '营销的条件类型 - 满 N 件', '1', + '2022-11-04 23:00:02', '1', '2022-11-04 23:00:02', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1182, 10, '申请售后', '10', 'trade_after_sale_status', 0, 'primary', '', '交易售后状态 - 申请售后', '1', + '2022-11-19 20:53:33', '1', '2022-11-19 20:54:42', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1183, 20, '商品待退货', '20', 'trade_after_sale_status', 0, 'primary', '', '交易售后状态 - 商品待退货', '1', + '2022-11-19 20:54:36', '1', '2022-11-19 20:58:58', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1184, 30, '商家待收货', '30', 'trade_after_sale_status', 0, 'primary', '', '交易售后状态 - 商家待收货', '1', + '2022-11-19 20:56:56', '1', '2022-11-19 20:59:20', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1185, 40, '等待退款', '40', 'trade_after_sale_status', 0, 'primary', '', '交易售后状态 - 等待退款', '1', + '2022-11-19 20:59:54', '1', '2022-11-19 21:00:01', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1186, 50, '退款成功', '50', 'trade_after_sale_status', 0, 'default', '', '交易售后状态 - 退款成功', '1', + '2022-11-19 21:00:33', '1', '2022-11-19 21:00:33', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1187, 61, '买家取消', '61', 'trade_after_sale_status', 0, 'info', '', '交易售后状态 - 买家取消', '1', + '2022-11-19 21:01:29', '1', '2022-11-19 21:01:29', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1188, 62, '商家拒绝', '62', 'trade_after_sale_status', 0, 'info', '', '交易售后状态 - 商家拒绝', '1', + '2022-11-19 21:02:17', '1', '2022-11-19 21:02:17', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1189, 63, '商家拒收货', '63', 'trade_after_sale_status', 0, 'info', '', '交易售后状态 - 商家拒收货', '1', + '2022-11-19 21:02:37', '1', '2022-11-19 21:03:07', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1190, 10, '售中退款', '10', 'trade_after_sale_type', 0, 'success', '', '交易售后的类型 - 售中退款', '1', + '2022-11-19 21:05:05', '1', '2022-11-19 21:38:23', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1191, 20, '售后退款', '20', 'trade_after_sale_type', 0, 'primary', '', '交易售后的类型 - 售后退款', '1', + '2022-11-19 21:05:32', '1', '2022-11-19 21:38:32', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1192, 10, '仅退款', '10', 'trade_after_sale_way', 0, 'primary', '', '交易售后的方式 - 仅退款', '1', + '2022-11-19 21:39:19', '1', '2022-11-19 21:39:19', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1193, 20, '退货退款', '20', 'trade_after_sale_way', 0, 'success', '', '交易售后的方式 - 退货退款', '1', + '2022-11-19 21:39:38', '1', '2022-11-19 21:39:49', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1194, 10, '微信小程序', '10', 'terminal', 0, 'default', '', '终端 - 微信小程序', '1', '2022-12-10 10:51:11', + '1', '2022-12-10 10:51:57', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1195, 20, 'H5 网页', '20', 'terminal', 0, 'default', '', '终端 - H5 网页', '1', '2022-12-10 10:51:30', '1', + '2022-12-10 10:51:59', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1196, 11, '微信公众号', '11', 'terminal', 0, 'default', '', '终端 - 微信公众号', '1', '2022-12-10 10:54:16', + '1', '2022-12-10 10:52:01', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1197, 31, '苹果 App', '31', 'terminal', 0, 'default', '', '终端 - 苹果 App', '1', '2022-12-10 10:54:42', '1', + '2022-12-10 10:52:18', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1198, 32, '安卓 App', '32', 'terminal', 0, 'default', '', '终端 - 安卓 App', '1', '2022-12-10 10:55:02', '1', + '2022-12-10 10:59:17', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1199, 0, '普通订单', '0', 'trade_order_type', 0, 'default', '', '交易订单的类型 - 普通订单', '1', + '2022-12-10 16:34:14', '1', '2022-12-10 16:34:14', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1200, 1, '秒杀订单', '1', 'trade_order_type', 0, 'default', '', '交易订单的类型 - 秒杀订单', '1', + '2022-12-10 16:34:26', '1', '2022-12-10 16:34:26', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1201, 2, '砍价订单', '2', 'trade_order_type', 0, 'default', '', '交易订单的类型 - 拼团订单', '1', + '2022-12-10 16:34:36', '1', '2024-09-07 14:18:39', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1202, 3, '拼团订单', '3', 'trade_order_type', 0, 'default', '', '交易订单的类型 - 砍价订单', '1', + '2022-12-10 16:34:48', '1', '2024-09-07 14:18:32', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1203, 0, '待支付', '0', 'trade_order_status', 0, 'default', '', '交易订单状态 - 待支付', '1', + '2022-12-10 16:49:29', '1', '2022-12-10 16:49:29', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1204, 10, '待发货', '10', 'trade_order_status', 0, 'primary', '', '交易订单状态 - 待发货', '1', + '2022-12-10 16:49:53', '1', '2022-12-10 16:51:17', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1205, 20, '已发货', '20', 'trade_order_status', 0, 'primary', '', '交易订单状态 - 已发货', '1', + '2022-12-10 16:50:13', '1', '2022-12-10 16:51:31', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1206, 30, '已完成', '30', 'trade_order_status', 0, 'success', '', '交易订单状态 - 已完成', '1', + '2022-12-10 16:50:30', '1', '2022-12-10 16:51:06', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1207, 40, '已取消', '40', 'trade_order_status', 0, 'danger', '', '交易订单状态 - 已取消', '1', + '2022-12-10 16:50:50', '1', '2022-12-10 16:51:00', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1208, 0, '未售后', '0', 'trade_order_item_after_sale_status', 0, 'info', '', '交易订单项的售后状态 - 未售后', + '1', '2022-12-10 20:58:42', '1', '2022-12-10 20:59:29', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1209, 10, '售后中', '10', 'trade_order_item_after_sale_status', 0, 'primary', '', + '交易订单项的售后状态 - 售后中', '1', '2022-12-10 20:59:21', '1', '2024-07-21 17:01:24', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1210, 20, '已退款', '20', 'trade_order_item_after_sale_status', 0, 'success', '', + '交易订单项的售后状态 - 已退款', '1', '2022-12-10 20:59:46', '1', '2024-07-21 17:01:35', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1211, 1, '完全匹配', '1', 'mp_auto_reply_request_match', 0, 'primary', '', + '公众号自动回复的请求关键字匹配模式 - 完全匹配', '1', '2023-01-16 23:30:39', '1', '2023-01-16 23:31:00', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1212, 2, '半匹配', '2', 'mp_auto_reply_request_match', 0, 'success', '', + '公众号自动回复的请求关键字匹配模式 - 半匹配', '1', '2023-01-16 23:30:55', '1', '2023-01-16 23:31:10', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1213, 1, '文本', 'text', 'mp_message_type', 0, 'default', '', '公众号的消息类型 - 文本', '1', + '2023-01-17 22:17:32', '1', '2023-01-17 22:17:39', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1214, 2, '图片', 'image', 'mp_message_type', 0, 'default', '', '公众号的消息类型 - 图片', '1', + '2023-01-17 22:17:32', '1', '2023-01-17 14:19:47', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1215, 3, '语音', 'voice', 'mp_message_type', 0, 'default', '', '公众号的消息类型 - 语音', '1', + '2023-01-17 22:17:32', '1', '2023-01-17 14:20:08', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1216, 4, '视频', 'video', 'mp_message_type', 0, 'default', '', '公众号的消息类型 - 视频', '1', + '2023-01-17 22:17:32', '1', '2023-01-17 14:21:08', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1217, 5, '小视频', 'shortvideo', 'mp_message_type', 0, 'default', '', '公众号的消息类型 - 小视频', '1', + '2023-01-17 22:17:32', '1', '2023-01-17 14:19:59', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1218, 6, '图文', 'news', 'mp_message_type', 0, 'default', '', '公众号的消息类型 - 图文', '1', + '2023-01-17 22:17:32', '1', '2023-01-17 14:22:54', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1219, 7, '音乐', 'music', 'mp_message_type', 0, 'default', '', '公众号的消息类型 - 音乐', '1', + '2023-01-17 22:17:32', '1', '2023-01-17 14:22:54', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1220, 8, '地理位置', 'location', 'mp_message_type', 0, 'default', '', '公众号的消息类型 - 地理位置', '1', + '2023-01-17 22:17:32', '1', '2023-01-17 14:23:51', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1221, 9, '链接', 'link', 'mp_message_type', 0, 'default', '', '公众号的消息类型 - 链接', '1', + '2023-01-17 22:17:32', '1', '2023-01-17 14:24:49', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1222, 10, '事件', 'event', 'mp_message_type', 0, 'default', '', '公众号的消息类型 - 事件', '1', + '2023-01-17 22:17:32', '1', '2023-01-17 14:24:49', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1223, 0, '初始化', '0', 'system_mail_send_status', 0, 'primary', '', '邮件发送状态 - 初始化\n', '1', + '2023-01-26 09:53:49', '1', '2023-01-26 16:36:14', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1224, 10, '发送成功', '10', 'system_mail_send_status', 0, 'success', '', '邮件发送状态 - 发送成功', '1', + '2023-01-26 09:54:28', '1', '2023-01-26 16:36:22', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1225, 20, '发送失败', '20', 'system_mail_send_status', 0, 'danger', '', '邮件发送状态 - 发送失败', '1', + '2023-01-26 09:54:50', '1', '2023-01-26 16:36:26', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1226, 30, '不发送', '30', 'system_mail_send_status', 0, 'info', '', '邮件发送状态 - 不发送', '1', + '2023-01-26 09:55:06', '1', '2023-01-26 16:36:36', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1227, 1, '通知公告', '1', 'system_notify_template_type', 0, 'primary', '', '站内信模版的类型 - 通知公告', '1', + '2023-01-28 10:35:59', '1', '2023-01-28 10:35:59', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1228, 2, '系统消息', '2', 'system_notify_template_type', 0, 'success', '', '站内信模版的类型 - 系统消息', '1', + '2023-01-28 10:36:20', '1', '2023-01-28 10:36:25', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1230, 13, '支付宝条码支付', 'alipay_bar', 'pay_channel_code', 0, 'primary', '', '支付宝条码支付', '1', + '2023-02-18 23:32:24', '1', '2023-07-19 20:09:23', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1231, 10, 'Vue2 Element UI 标准模版', '10', 'infra_codegen_front_type', 0, '', '', '', '1', + '2023-04-13 00:03:55', '1', '2023-04-13 00:03:55', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1232, 20, 'Vue3 Element Plus 标准模版', '20', 'infra_codegen_front_type', 0, '', '', '', '1', + '2023-04-13 00:04:08', '1', '2023-04-13 00:04:08', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1234, 30, 'Vue3 vben 模版', '30', 'infra_codegen_front_type', 0, '', '', '', '1', '2023-04-13 00:04:26', '1', + '2023-04-13 00:04:26', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1244, 0, '按件', '1', 'trade_delivery_express_charge_mode', 0, '', '', '', '1', '2023-05-21 22:46:40', '1', + '2023-05-21 22:46:40', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1245, 1, '按重量', '2', 'trade_delivery_express_charge_mode', 0, '', '', '', '1', '2023-05-21 22:46:58', '1', + '2023-05-21 22:46:58', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1246, 2, '按体积', '3', 'trade_delivery_express_charge_mode', 0, '', '', '', '1', '2023-05-21 22:47:18', '1', + '2023-05-21 22:47:18', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1335, 11, '订单积分抵扣', '11', 'member_point_biz_type', 0, '', '', '', '1', '2023-06-10 12:15:27', '1', + '2023-10-11 07:41:43', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1336, 1, '签到', '1', 'member_point_biz_type', 0, '', '', '', '1', '2023-06-10 12:15:48', '1', + '2023-08-20 11:59:53', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1341, 20, '已退款', '20', 'pay_order_status', 0, 'danger', '', '已退款', '1', '2023-07-19 18:05:37', '1', + '2023-07-19 18:05:37', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1342, 21, '请求成功,但是结果失败', '21', 'pay_notify_status', 0, 'warning', '', '请求成功,但是结果失败', '1', + '2023-07-19 18:10:47', '1', '2023-07-19 18:11:38', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1343, 22, '请求失败', '22', 'pay_notify_status', 0, 'warning', '', NULL, '1', '2023-07-19 18:11:05', '1', + '2023-07-19 18:11:27', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1344, 4, '微信扫码支付', 'wx_native', 'pay_channel_code', 0, 'success', '', '微信扫码支付', '1', + '2023-07-19 20:07:47', '1', '2023-07-19 20:09:03', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1345, 5, '微信条码支付', 'wx_bar', 'pay_channel_code', 0, 'success', '', '微信条码支付\n', '1', + '2023-07-19 20:08:06', '1', '2023-07-19 20:09:08', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1346, 1, '支付单', '1', 'pay_notify_type', 0, 'primary', '', '支付单', '1', '2023-07-20 12:23:17', '1', + '2023-07-20 12:23:17', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1347, 2, '退款单', '2', 'pay_notify_type', 0, 'danger', '', NULL, '1', '2023-07-20 12:23:26', '1', + '2023-07-20 12:23:26', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1348, 20, '模拟支付', 'mock', 'pay_channel_code', 0, 'default', '', '模拟支付', '1', '2023-07-29 11:10:51', '1', + '2023-07-29 03:14:10', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1349, 12, '订单积分抵扣(整单取消)', '12', 'member_point_biz_type', 0, '', '', '', '1', '2023-08-20 12:00:03', + '1', '2023-10-11 07:42:01', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1350, 0, '管理员调整', '0', 'member_experience_biz_type', 0, '', '', NULL, '', '2023-08-22 12:41:01', '', + '2023-08-22 12:41:01', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1351, 1, '邀新奖励', '1', 'member_experience_biz_type', 0, '', '', NULL, '', '2023-08-22 12:41:01', '', + '2023-08-22 12:41:01', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1352, 11, '下单奖励', '11', 'member_experience_biz_type', 0, 'success', '', NULL, '', '2023-08-22 12:41:01', + '1', '2023-10-11 07:45:09', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1353, 12, '下单奖励(整单取消)', '12', 'member_experience_biz_type', 0, 'warning', '', NULL, '', + '2023-08-22 12:41:01', '1', '2023-10-11 07:45:01', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1354, 4, '签到奖励', '4', 'member_experience_biz_type', 0, '', '', NULL, '', '2023-08-22 12:41:01', '', + '2023-08-22 12:41:01', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1355, 5, '抽奖奖励', '5', 'member_experience_biz_type', 0, '', '', NULL, '', '2023-08-22 12:41:01', '', + '2023-08-22 12:41:01', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1356, 1, '快递发货', '1', 'trade_delivery_type', 0, '', '', '', '1', '2023-08-23 00:04:55', '1', + '2023-08-23 00:04:55', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1357, 2, '用户自提', '2', 'trade_delivery_type', 0, '', '', '', '1', '2023-08-23 00:05:05', '1', + '2023-08-23 00:05:05', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1358, 3, '品类劵', '3', 'promotion_product_scope', 0, 'default', '', '', '1', '2023-09-01 23:43:07', '1', + '2023-09-28 00:27:47', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1359, 1, '人人分销', '1', 'brokerage_enabled_condition', 0, '', '', '所有用户都可以分销', '', + '2023-09-28 02:46:05', '', '2023-09-28 02:46:05', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1360, 2, '指定分销', '2', 'brokerage_enabled_condition', 0, '', '', '仅可后台手动设置推广员', '', + '2023-09-28 02:46:05', '', '2023-09-28 02:46:05', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1361, 1, '首次绑定', '1', 'brokerage_bind_mode', 0, '', '', '只要用户没有推广人,随时都可以绑定推广关系', '', + '2023-09-28 02:46:05', '', '2023-09-28 02:46:05', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1362, 2, '注册绑定', '2', 'brokerage_bind_mode', 0, '', '', '仅新用户注册时才能绑定推广关系', '', + '2023-09-28 02:46:05', '', '2023-09-28 02:46:05', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1363, 3, '覆盖绑定', '3', 'brokerage_bind_mode', 0, '', '', '如果用户已经有推广人,推广人会被变更', '', + '2023-09-28 02:46:05', '', '2023-09-28 02:46:05', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1364, 1, '钱包', '1', 'brokerage_withdraw_type', 0, '', '', NULL, '', '2023-09-28 02:46:05', '', + '2023-09-28 02:46:05', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1365, 2, '银行卡', '2', 'brokerage_withdraw_type', 0, '', '', NULL, '', '2023-09-28 02:46:05', '', + '2023-09-28 02:46:05', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1366, 3, '微信', '3', 'brokerage_withdraw_type', 0, '', '', '手动打款', '', '2023-09-28 02:46:05', '1', + '2024-10-13 11:06:54', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1367, 4, '支付宝', '4', 'brokerage_withdraw_type', 0, '', '', NULL, '', '2023-09-28 02:46:05', '', + '2023-09-28 02:46:05', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1368, 1, '订单返佣', '1', 'brokerage_record_biz_type', 0, '', '', NULL, '', '2023-09-28 02:46:05', '', + '2023-09-28 02:46:05', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1369, 2, '申请提现', '2', 'brokerage_record_biz_type', 0, '', '', NULL, '', '2023-09-28 02:46:05', '', + '2023-09-28 02:46:05', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1370, 3, '申请提现驳回', '3', 'brokerage_record_biz_type', 0, '', '', NULL, '', '2023-09-28 02:46:05', '', + '2023-09-28 02:46:05', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1371, 0, '待结算', '0', 'brokerage_record_status', 0, '', '', NULL, '', '2023-09-28 02:46:05', '', + '2023-09-28 02:46:05', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1372, 1, '已结算', '1', 'brokerage_record_status', 0, '', '', NULL, '', '2023-09-28 02:46:05', '', + '2023-09-28 02:46:05', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1373, 2, '已取消', '2', 'brokerage_record_status', 0, '', '', NULL, '', '2023-09-28 02:46:05', '', + '2023-09-28 02:46:05', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1374, 0, '审核中', '0', 'brokerage_withdraw_status', 0, '', '', NULL, '', '2023-09-28 02:46:05', '', + '2023-09-28 02:46:05', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1375, 10, '审核通过', '10', 'brokerage_withdraw_status', 0, 'success', '', NULL, '', '2023-09-28 02:46:05', '', + '2023-09-28 02:46:05', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1376, 11, '提现成功', '11', 'brokerage_withdraw_status', 0, 'success', '', NULL, '', '2023-09-28 02:46:05', '', + '2023-09-28 02:46:05', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1377, 20, '审核不通过', '20', 'brokerage_withdraw_status', 0, 'danger', '', NULL, '', '2023-09-28 02:46:05', '', + '2023-09-28 02:46:05', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1378, 21, '提现失败', '21', 'brokerage_withdraw_status', 0, 'danger', '', NULL, '', '2023-09-28 02:46:05', '', + '2023-09-28 02:46:05', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1379, 0, '工商银行', '0', 'brokerage_bank_name', 0, '', '', NULL, '', '2023-09-28 02:46:05', '', + '2023-09-28 02:46:05', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1380, 1, '建设银行', '1', 'brokerage_bank_name', 0, '', '', NULL, '', '2023-09-28 02:46:05', '', + '2023-09-28 02:46:05', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1381, 2, '农业银行', '2', 'brokerage_bank_name', 0, '', '', NULL, '', '2023-09-28 02:46:05', '', + '2023-09-28 02:46:05', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1382, 3, '中国银行', '3', 'brokerage_bank_name', 0, '', '', NULL, '', '2023-09-28 02:46:05', '', + '2023-09-28 02:46:05', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1383, 4, '交通银行', '4', 'brokerage_bank_name', 0, '', '', NULL, '', '2023-09-28 02:46:05', '', + '2023-09-28 02:46:05', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1384, 5, '招商银行', '5', 'brokerage_bank_name', 0, '', '', NULL, '', '2023-09-28 02:46:05', '', + '2023-09-28 02:46:05', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1385, 21, '钱包', 'wallet', 'pay_channel_code', 0, 'primary', '', '', '1', '2023-10-01 21:46:19', '1', + '2023-10-01 21:48:01', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1386, 1, '砍价中', '1', 'promotion_bargain_record_status', 0, 'default', '', '', '1', '2023-10-05 10:41:26', + '1', '2023-10-05 10:41:26', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1387, 2, '砍价成功', '2', 'promotion_bargain_record_status', 0, 'success', '', '', '1', '2023-10-05 10:41:39', + '1', '2023-10-05 10:41:39', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1388, 3, '砍价失败', '3', 'promotion_bargain_record_status', 0, 'warning', '', '', '1', '2023-10-05 10:41:57', + '1', '2023-10-05 10:41:57', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1389, 0, '拼团中', '0', 'promotion_combination_record_status', 0, '', '', '', '1', '2023-10-08 07:24:44', '1', + '2024-10-13 10:08:17', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1390, 1, '拼团成功', '1', 'promotion_combination_record_status', 0, 'success', '', '', '1', + '2023-10-08 07:24:56', '1', '2024-10-13 10:08:20', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1391, 2, '拼团失败', '2', 'promotion_combination_record_status', 0, 'warning', '', '', '1', + '2023-10-08 07:25:11', '1', '2024-10-13 10:08:24', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1392, 2, '管理员修改', '2', 'member_point_biz_type', 0, 'default', '', '', '1', '2023-10-11 07:41:34', '1', + '2023-10-11 07:41:34', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1393, 13, '订单积分抵扣(单个退款)', '13', 'member_point_biz_type', 0, '', '', '', '1', '2023-10-11 07:42:29', + '1', '2023-10-11 07:42:29', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1394, 21, '订单积分奖励', '21', 'member_point_biz_type', 0, 'default', '', '', '1', '2023-10-11 07:42:44', '1', + '2023-10-11 07:42:44', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1395, 22, '订单积分奖励(整单取消)', '22', 'member_point_biz_type', 0, 'default', '', '', '1', + '2023-10-11 07:42:55', '1', '2023-10-11 07:43:01', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1396, 23, '订单积分奖励(单个退款)', '23', 'member_point_biz_type', 0, 'default', '', '', '1', + '2023-10-11 07:43:16', '1', '2023-10-11 07:43:16', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1397, 13, '下单奖励(单个退款)', '13', 'member_experience_biz_type', 0, 'warning', '', '', '1', + '2023-10-11 07:45:24', '1', '2023-10-11 07:45:38', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1398, 5, '网上转账', '5', 'crm_receivable_return_type', 0, 'default', '', '', '1', '2023-10-18 21:55:24', '1', + '2023-10-18 21:55:24', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1399, 6, '支付宝', '6', 'crm_receivable_return_type', 0, 'default', '', '', '1', '2023-10-18 21:55:38', '1', + '2023-10-18 21:55:38', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1400, 7, '微信支付', '7', 'crm_receivable_return_type', 0, 'default', '', '', '1', '2023-10-18 21:55:53', '1', + '2023-10-18 21:55:53', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1401, 8, '其他', '8', 'crm_receivable_return_type', 0, 'default', '', '', '1', '2023-10-18 21:56:06', '1', + '2023-10-18 21:56:06', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1402, 1, 'IT', '1', 'crm_customer_industry', 0, 'default', '', '', '1', '2023-10-28 23:02:15', '1', + '2024-02-18 23:30:38', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1403, 2, '金融业', '2', 'crm_customer_industry', 0, 'default', '', '', '1', '2023-10-28 23:02:29', '1', + '2024-02-18 23:30:43', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1404, 3, '房地产', '3', 'crm_customer_industry', 0, 'default', '', '', '1', '2023-10-28 23:02:41', '1', + '2024-02-18 23:30:48', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1405, 4, '商业服务', '4', 'crm_customer_industry', 0, 'default', '', '', '1', '2023-10-28 23:02:54', '1', + '2024-02-18 23:30:54', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1406, 5, '运输/物流', '5', 'crm_customer_industry', 0, 'default', '', '', '1', '2023-10-28 23:03:03', '1', + '2024-02-18 23:31:00', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1407, 6, '生产', '6', 'crm_customer_industry', 0, 'default', '', '', '1', '2023-10-28 23:03:13', '1', + '2024-02-18 23:31:08', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1408, 7, '政府', '7', 'crm_customer_industry', 0, 'default', '', '', '1', '2023-10-28 23:03:27', '1', + '2024-02-18 23:31:13', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1409, 8, '文化传媒', '8', 'crm_customer_industry', 0, 'default', '', '', '1', '2023-10-28 23:03:37', '1', + '2024-02-18 23:31:20', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1422, 1, 'A (重点客户)', '1', 'crm_customer_level', 0, 'primary', '', '', '1', '2023-10-28 23:07:13', '1', + '2023-10-28 23:07:13', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1423, 2, 'B (普通客户)', '2', 'crm_customer_level', 0, 'info', '', '', '1', '2023-10-28 23:07:35', '1', + '2023-10-28 23:07:35', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1424, 3, 'C (非优先客户)', '3', 'crm_customer_level', 0, 'default', '', '', '1', '2023-10-28 23:07:53', '1', + '2023-10-28 23:07:53', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1425, 1, '促销', '1', 'crm_customer_source', 0, 'default', '', '', '1', '2023-10-28 23:08:29', '1', + '2023-10-28 23:08:29', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1426, 2, '搜索引擎', '2', 'crm_customer_source', 0, 'default', '', '', '1', '2023-10-28 23:08:39', '1', + '2023-10-28 23:08:39', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1427, 3, '广告', '3', 'crm_customer_source', 0, 'default', '', '', '1', '2023-10-28 23:08:47', '1', + '2023-10-28 23:08:47', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1428, 4, '转介绍', '4', 'crm_customer_source', 0, 'default', '', '', '1', '2023-10-28 23:08:58', '1', + '2023-10-28 23:08:58', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1429, 5, '线上注册', '5', 'crm_customer_source', 0, 'default', '', '', '1', '2023-10-28 23:09:12', '1', + '2023-10-28 23:09:12', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1430, 6, '线上咨询', '6', 'crm_customer_source', 0, 'default', '', '', '1', '2023-10-28 23:09:22', '1', + '2023-10-28 23:09:22', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1431, 7, '预约上门', '7', 'crm_customer_source', 0, 'default', '', '', '1', '2023-10-28 23:09:39', '1', + '2023-10-28 23:09:39', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1432, 8, '陌拜', '8', 'crm_customer_source', 0, 'default', '', '', '1', '2023-10-28 23:10:04', '1', + '2023-10-28 23:10:04', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1433, 9, '电话咨询', '9', 'crm_customer_source', 0, 'default', '', '', '1', '2023-10-28 23:10:18', '1', + '2023-10-28 23:10:18', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1434, 10, '邮件咨询', '10', 'crm_customer_source', 0, 'default', '', '', '1', '2023-10-28 23:10:33', '1', + '2023-10-28 23:10:33', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1435, 10, 'Gitee', '10', 'system_social_type', 0, '', '', '', '1', '2023-11-04 13:04:42', '1', + '2023-11-04 13:04:42', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1436, 20, '钉钉', '20', 'system_social_type', 0, '', '', '', '1', '2023-11-04 13:04:54', '1', + '2023-11-04 13:04:54', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1437, 30, '企业微信', '30', 'system_social_type', 0, '', '', '', '1', '2023-11-04 13:05:09', '1', + '2023-11-04 13:05:09', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1438, 31, '微信公众平台', '31', 'system_social_type', 0, '', '', '', '1', '2023-11-04 13:05:18', '1', + '2023-11-04 13:05:18', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1439, 32, '微信开放平台', '32', 'system_social_type', 0, '', '', '', '1', '2023-11-04 13:05:30', '1', + '2023-11-04 13:05:30', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1440, 34, '微信小程序', '34', 'system_social_type', 0, '', '', '', '1', '2023-11-04 13:05:38', '1', + '2023-11-04 13:07:16', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1441, 1, '上架', '1', 'crm_product_status', 0, 'success', '', '', '1', '2023-10-30 21:49:34', '1', + '2023-10-30 21:49:34', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1442, 0, '下架', '0', 'crm_product_status', 0, 'success', '', '', '1', '2023-10-30 21:49:13', '1', + '2023-10-30 21:49:13', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1443, 15, '子表', '15', 'infra_codegen_template_type', 0, 'default', '', '', '1', '2023-11-13 23:06:16', '1', + '2023-11-13 23:06:16', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1444, 10, '主表(标准模式)', '10', 'infra_codegen_template_type', 0, 'default', '', '', '1', + '2023-11-14 12:32:49', '1', '2023-11-14 12:32:49', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1445, 11, '主表(ERP 模式)', '11', 'infra_codegen_template_type', 0, 'default', '', '', '1', + '2023-11-14 12:33:05', '1', '2023-11-14 12:33:05', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1446, 12, '主表(内嵌模式)', '12', 'infra_codegen_template_type', 0, '', '', '', '1', '2023-11-14 12:33:31', '1', + '2023-11-14 12:33:31', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1447, 1, '负责人', '1', 'crm_permission_level', 0, 'default', '', '', '1', '2023-11-30 09:53:12', '1', + '2023-11-30 09:53:12', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1448, 2, '只读', '2', 'crm_permission_level', 0, '', '', '', '1', '2023-11-30 09:53:29', '1', + '2023-11-30 09:53:29', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1449, 3, '读写', '3', 'crm_permission_level', 0, '', '', '', '1', '2023-11-30 09:53:36', '1', + '2023-11-30 09:53:36', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1450, 0, '未提交', '0', 'crm_audit_status', 0, '', '', '', '1', '2023-11-30 18:56:59', '1', + '2023-11-30 18:56:59', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1451, 10, '审批中', '10', 'crm_audit_status', 0, '', '', '', '1', '2023-11-30 18:57:10', '1', + '2023-11-30 18:57:10', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1452, 20, '审核通过', '20', 'crm_audit_status', 0, '', '', '', '1', '2023-11-30 18:57:24', '1', + '2023-11-30 18:57:24', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1453, 30, '审核不通过', '30', 'crm_audit_status', 0, '', '', '', '1', '2023-11-30 18:57:32', '1', + '2023-11-30 18:57:32', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1454, 40, '已取消', '40', 'crm_audit_status', 0, '', '', '', '1', '2023-11-30 18:57:42', '1', + '2023-11-30 18:57:42', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1456, 1, '支票', '1', 'crm_receivable_return_type', 0, 'default', '', '', '1', '2023-10-18 21:54:29', '1', + '2023-10-18 21:54:29', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1457, 2, '现金', '2', 'crm_receivable_return_type', 0, 'default', '', '', '1', '2023-10-18 21:54:41', '1', + '2023-10-18 21:54:41', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1458, 3, '邮政汇款', '3', 'crm_receivable_return_type', 0, 'default', '', '', '1', '2023-10-18 21:54:53', '1', + '2023-10-18 21:54:53', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1459, 4, '电汇', '4', 'crm_receivable_return_type', 0, 'default', '', '', '1', '2023-10-18 21:55:07', '1', + '2023-10-18 21:55:07', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1461, 1, '个', '1', 'crm_product_unit', 0, '', '', '', '1', '2023-12-05 23:02:26', '1', '2023-12-05 23:02:26', + b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1462, 2, '块', '2', 'crm_product_unit', 0, '', '', '', '1', '2023-12-05 23:02:34', '1', '2023-12-05 23:02:34', + b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1463, 3, '只', '3', 'crm_product_unit', 0, '', '', '', '1', '2023-12-05 23:02:57', '1', '2023-12-05 23:02:57', + b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1464, 4, '把', '4', 'crm_product_unit', 0, '', '', '', '1', '2023-12-05 23:03:05', '1', '2023-12-05 23:03:05', + b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1465, 5, '枚', '5', 'crm_product_unit', 0, '', '', '', '1', '2023-12-05 23:03:14', '1', '2023-12-05 23:03:14', + b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1466, 6, '瓶', '6', 'crm_product_unit', 0, '', '', '', '1', '2023-12-05 23:03:20', '1', '2023-12-05 23:03:20', + b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1467, 7, '盒', '7', 'crm_product_unit', 0, '', '', '', '1', '2023-12-05 23:03:30', '1', '2023-12-05 23:03:30', + b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1468, 8, '台', '8', 'crm_product_unit', 0, '', '', '', '1', '2023-12-05 23:03:41', '1', '2023-12-05 23:03:41', + b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1469, 9, '吨', '9', 'crm_product_unit', 0, '', '', '', '1', '2023-12-05 23:03:48', '1', '2023-12-05 23:03:48', + b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1470, 10, '千克', '10', 'crm_product_unit', 0, '', '', '', '1', '2023-12-05 23:04:03', '1', + '2023-12-05 23:04:03', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1471, 11, '米', '11', 'crm_product_unit', 0, '', '', '', '1', '2023-12-05 23:04:12', '1', '2023-12-05 23:04:12', + b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1472, 12, '箱', '12', 'crm_product_unit', 0, '', '', '', '1', '2023-12-05 23:04:25', '1', '2023-12-05 23:04:25', + b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1473, 13, '套', '13', 'crm_product_unit', 0, '', '', '', '1', '2023-12-05 23:04:34', '1', '2023-12-05 23:04:34', + b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1474, 1, '打电话', '1', 'crm_follow_up_type', 0, '', '', '', '1', '2024-01-15 20:48:20', '1', + '2024-01-15 20:48:20', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1475, 2, '发短信', '2', 'crm_follow_up_type', 0, '', '', '', '1', '2024-01-15 20:48:31', '1', + '2024-01-15 20:48:31', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1476, 3, '上门拜访', '3', 'crm_follow_up_type', 0, '', '', '', '1', '2024-01-15 20:49:07', '1', + '2024-01-15 20:49:07', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1477, 4, '微信沟通', '4', 'crm_follow_up_type', 0, '', '', '', '1', '2024-01-15 20:49:15', '1', + '2024-01-15 20:49:15', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1478, 4, '钱包余额', '4', 'pay_transfer_type', 0, 'info', '', '', '1', '2023-10-28 16:28:37', '1', + '2023-10-28 16:28:37', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1479, 3, '银行卡', '3', 'pay_transfer_type', 0, 'default', '', '', '1', '2023-10-28 16:28:21', '1', + '2023-10-28 16:28:21', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1480, 2, '微信余额', '2', 'pay_transfer_type', 0, 'info', '', '', '1', '2023-10-28 16:28:07', '1', + '2023-10-28 16:28:07', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1481, 1, '支付宝余额', '1', 'pay_transfer_type', 0, 'default', '', '', '1', '2023-10-28 16:27:44', '1', + '2023-10-28 16:27:44', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1482, 4, '转账失败', '30', 'pay_transfer_status', 0, 'warning', '', '', '1', '2023-10-28 16:24:16', '1', + '2023-10-28 16:24:16', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1483, 3, '转账成功', '20', 'pay_transfer_status', 0, 'success', '', '', '1', '2023-10-28 16:23:50', '1', + '2023-10-28 16:23:50', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1484, 2, '转账进行中', '10', 'pay_transfer_status', 0, 'info', '', '', '1', '2023-10-28 16:23:12', '1', + '2023-10-28 16:23:12', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1485, 1, '等待转账', '0', 'pay_transfer_status', 0, 'default', '', '', '1', '2023-10-28 16:21:43', '1', + '2023-10-28 16:23:22', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1486, 10, '其它入库', '10', 'erp_stock_record_biz_type', 0, '', '', '', '1', '2024-02-05 18:07:25', '1', + '2024-02-05 18:07:43', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1487, 11, '其它入库(作废)', '11', 'erp_stock_record_biz_type', 0, 'danger', '', '', '1', '2024-02-05 18:08:07', + '1', '2024-02-05 19:20:16', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1488, 20, '其它出库', '20', 'erp_stock_record_biz_type', 0, '', '', '', '1', '2024-02-05 18:08:51', '1', + '2024-02-05 18:08:51', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1489, 21, '其它出库(作废)', '21', 'erp_stock_record_biz_type', 0, 'danger', '', '', '1', '2024-02-05 18:09:00', + '1', '2024-02-05 19:20:10', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1490, 10, '未审核', '10', 'erp_audit_status', 0, 'default', '', '', '1', '2024-02-06 00:00:21', '1', + '2024-02-06 00:00:21', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1491, 20, '已审核', '20', 'erp_audit_status', 0, 'success', '', '', '1', '2024-02-06 00:00:35', '1', + '2024-02-06 00:00:35', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1492, 30, '调拨入库', '30', 'erp_stock_record_biz_type', 0, '', '', '', '1', '2024-02-07 20:34:19', '1', + '2024-02-07 12:36:31', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1493, 31, '调拨入库(作废)', '31', 'erp_stock_record_biz_type', 0, 'danger', '', '', '1', '2024-02-07 20:34:29', + '1', '2024-02-07 20:37:11', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1494, 32, '调拨出库', '32', 'erp_stock_record_biz_type', 0, '', '', '', '1', '2024-02-07 20:34:38', '1', + '2024-02-07 12:36:33', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1495, 33, '调拨出库(作废)', '33', 'erp_stock_record_biz_type', 0, 'danger', '', '', '1', '2024-02-07 20:34:49', + '1', '2024-02-07 20:37:06', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1496, 40, '盘盈入库', '40', 'erp_stock_record_biz_type', 0, '', '', '', '1', '2024-02-08 08:53:00', '1', + '2024-02-08 08:53:09', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1497, 41, '盘盈入库(作废)', '41', 'erp_stock_record_biz_type', 0, 'danger', '', '', '1', '2024-02-08 08:53:39', + '1', '2024-02-16 19:40:54', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1498, 42, '盘亏出库', '42', 'erp_stock_record_biz_type', 0, '', '', '', '1', '2024-02-08 08:54:16', '1', + '2024-02-08 08:54:16', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1499, 43, '盘亏出库(作废)', '43', 'erp_stock_record_biz_type', 0, 'danger', '', '', '1', '2024-02-08 08:54:31', + '1', '2024-02-16 19:40:46', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1500, 50, '销售出库', '50', 'erp_stock_record_biz_type', 0, '', '', '', '1', '2024-02-11 21:47:25', '1', + '2024-02-11 21:50:40', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1501, 51, '销售出库(作废)', '51', 'erp_stock_record_biz_type', 0, 'danger', '', '', '1', '2024-02-11 21:47:37', + '1', '2024-02-11 21:51:12', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1502, 60, '销售退货入库', '60', 'erp_stock_record_biz_type', 0, '', '', '', '1', '2024-02-12 06:51:05', '1', + '2024-02-12 06:51:05', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1503, 61, '销售退货入库(作废)', '61', 'erp_stock_record_biz_type', 0, 'danger', '', '', '1', + '2024-02-12 06:51:18', '1', '2024-02-12 06:51:18', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1504, 70, '采购入库', '70', 'erp_stock_record_biz_type', 0, '', '', '', '1', '2024-02-16 13:10:02', '1', + '2024-02-16 13:10:02', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1505, 71, '采购入库(作废)', '71', 'erp_stock_record_biz_type', 0, 'danger', '', '', '1', '2024-02-16 13:10:10', + '1', '2024-02-16 19:40:40', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1506, 80, '采购退货出库', '80', 'erp_stock_record_biz_type', 0, '', '', '', '1', '2024-02-16 13:10:17', '1', + '2024-02-16 13:10:17', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1507, 81, '采购退货出库(作废)', '81', 'erp_stock_record_biz_type', 0, 'danger', '', '', '1', + '2024-02-16 13:10:26', '1', '2024-02-16 19:40:33', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1509, 3, '审批不通过', '3', 'bpm_process_instance_status', 0, 'danger', '', '', '1', '2024-03-16 16:12:06', '1', + '2024-03-16 16:12:06', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1510, 4, '已取消', '4', 'bpm_process_instance_status', 0, 'warning', '', '', '1', '2024-03-16 16:12:22', '1', + '2024-03-16 16:12:22', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1511, 5, '已退回', '5', 'bpm_task_status', 0, 'warning', '', '', '1', '2024-03-16 19:10:46', '1', + '2024-03-08 22:41:40', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1512, 6, '委派中', '6', 'bpm_task_status', 0, 'primary', '', '', '1', '2024-03-17 10:06:22', '1', + '2024-03-08 22:41:40', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1513, 7, '审批通过中', '7', 'bpm_task_status', 0, 'success', '', '', '1', '2024-03-17 10:06:47', '1', + '2024-03-08 22:41:41', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1514, 0, '待审批', '0', 'bpm_task_status', 0, 'info', '', '', '1', '2024-03-17 10:07:11', '1', + '2024-03-08 22:41:42', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1515, 35, '发起人自选', '35', 'bpm_task_candidate_strategy', 0, '', '', '', '1', '2024-03-22 19:45:16', '1', + '2024-03-22 19:45:16', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1516, 1, '执行监听器', 'execution', 'bpm_process_listener_type', 0, 'primary', '', '', '1', + '2024-03-23 12:54:03', '1', '2024-03-23 19:14:19', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1517, 1, '任务监听器', 'task', 'bpm_process_listener_type', 0, 'success', '', '', '1', '2024-03-23 12:54:13', + '1', '2024-03-23 19:14:24', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1526, 1, 'Java 类', 'class', 'bpm_process_listener_value_type', 0, 'primary', '', '', '1', + '2024-03-23 15:08:45', '1', '2024-03-23 19:14:32', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1527, 2, '表达式', 'expression', 'bpm_process_listener_value_type', 0, 'success', '', '', '1', + '2024-03-23 15:09:06', '1', '2024-03-23 19:14:38', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1528, 3, '代理表达式', 'delegateExpression', 'bpm_process_listener_value_type', 0, 'info', '', '', '1', + '2024-03-23 15:11:23', '1', '2024-03-23 19:14:41', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1529, 1, '天', '1', 'date_interval', 0, '', '', '', '1', '2024-03-29 22:50:26', '1', '2024-03-29 22:50:26', + b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1530, 2, '周', '2', 'date_interval', 0, '', '', '', '1', '2024-03-29 22:50:36', '1', '2024-03-29 22:50:36', + b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1531, 3, '月', '3', 'date_interval', 0, '', '', '', '1', '2024-03-29 22:50:46', '1', '2024-03-29 22:50:54', + b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1532, 4, '季度', '4', 'date_interval', 0, '', '', '', '1', '2024-03-29 22:51:01', '1', '2024-03-29 22:51:01', + b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1533, 5, '年', '5', 'date_interval', 0, '', '', '', '1', '2024-03-29 22:51:07', '1', '2024-03-29 22:51:07', + b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1534, 1, '赢单', '1', 'crm_business_end_status_type', 0, 'success', '', '', '1', '2024-04-13 23:26:57', '1', + '2024-04-13 23:26:57', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1535, 2, '输单', '2', 'crm_business_end_status_type', 0, 'primary', '', '', '1', '2024-04-13 23:27:31', '1', + '2024-04-13 23:27:31', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1536, 3, '无效', '3', 'crm_business_end_status_type', 0, 'info', '', '', '1', '2024-04-13 23:27:59', '1', + '2024-04-13 23:27:59', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1537, 1, 'OpenAI', 'OpenAI', 'ai_platform', 0, '', '', '', '1', '2024-05-09 22:33:47', '1', + '2024-05-09 22:58:46', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1538, 2, 'Ollama', 'Ollama', 'ai_platform', 0, '', '', '', '1', '2024-05-17 23:02:55', '1', + '2024-05-17 23:02:55', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1539, 3, '文心一言', 'YiYan', 'ai_platform', 0, '', '', '', '1', '2024-05-18 09:24:20', '1', + '2024-05-18 09:29:01', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1540, 4, '讯飞星火', 'XingHuo', 'ai_platform', 0, '', '', '', '1', '2024-05-18 10:08:56', '1', + '2024-05-18 10:08:56', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1541, 5, '通义千问', 'TongYi', 'ai_platform', 0, '', '', '', '1', '2024-05-18 10:32:29', '1', + '2024-07-06 15:42:29', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1542, 6, 'StableDiffusion', 'StableDiffusion', 'ai_platform', 0, '', '', '', '1', '2024-06-01 15:09:31', '1', + '2024-06-01 15:10:25', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1543, 10, '进行中', '10', 'ai_image_status', 0, 'primary', '', '', '1', '2024-06-26 20:51:41', '1', + '2024-06-26 20:52:48', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1544, 20, '已完成', '20', 'ai_image_status', 0, 'success', '', '', '1', '2024-06-26 20:52:07', '1', + '2024-06-26 20:52:41', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1545, 30, '已失败', '30', 'ai_image_status', 0, 'warning', '', '', '1', '2024-06-26 20:52:25', '1', + '2024-06-26 20:52:35', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1546, 7, 'Midjourney', 'Midjourney', 'ai_platform', 0, '', '', '', '1', '2024-06-26 22:14:46', '1', + '2024-06-26 22:14:46', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1547, 10, '进行中', '10', 'ai_music_status', 0, 'primary', '', '', '1', '2024-06-27 22:45:22', '1', + '2024-06-28 00:56:17', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1548, 20, '已完成', '20', 'ai_music_status', 0, 'success', '', '', '1', '2024-06-27 22:45:33', '1', + '2024-06-28 00:56:18', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1549, 30, '已失败', '30', 'ai_music_status', 0, 'danger', '', '', '1', '2024-06-27 22:45:44', '1', + '2024-06-28 00:56:19', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1550, 1, '歌词模式', '1', 'ai_generate_mode', 0, '', '', '', '1', '2024-06-27 22:46:31', '1', + '2024-06-28 01:22:25', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1551, 2, '描述模式', '2', 'ai_generate_mode', 0, '', '', '', '1', '2024-06-27 22:46:37', '1', + '2024-06-28 01:22:24', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1552, 8, 'Suno', 'Suno', 'ai_platform', 0, '', '', '', '1', '2024-06-29 09:13:36', '1', '2024-06-29 09:13:41', + b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1553, 9, 'DeepSeek', 'DeepSeek', 'ai_platform', 0, '', '', '', '1', '2024-07-06 12:04:30', '1', + '2024-07-06 12:05:20', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1554, 10, '智谱', 'ZhiPu', 'ai_platform', 0, '', '', '', '1', '2024-07-06 18:00:35', '1', '2024-07-06 18:00:35', + b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1555, 4, '长', '4', 'ai_write_length', 0, '', '', '', '1', '2024-07-07 15:49:03', '1', '2024-07-07 15:49:03', + b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1556, 5, '段落', '5', 'ai_write_format', 0, '', '', '', '1', '2024-07-07 15:49:54', '1', '2024-07-07 15:49:54', + b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1557, 6, '文章', '6', 'ai_write_format', 0, '', '', '', '1', '2024-07-07 15:50:05', '1', '2024-07-07 15:50:05', + b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1558, 7, '博客文章', '7', 'ai_write_format', 0, '', '', '', '1', '2024-07-07 15:50:23', '1', + '2024-07-07 15:50:23', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1559, 8, '想法', '8', 'ai_write_format', 0, '', '', '', '1', '2024-07-07 15:50:31', '1', '2024-07-07 15:50:31', + b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1560, 9, '大纲', '9', 'ai_write_format', 0, '', '', '', '1', '2024-07-07 15:50:37', '1', '2024-07-07 15:50:37', + b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1561, 1, '自动', '1', 'ai_write_tone', 0, '', '', '', '1', '2024-07-07 15:51:06', '1', '2024-07-07 15:51:06', + b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1562, 2, '友善', '2', 'ai_write_tone', 0, '', '', '', '1', '2024-07-07 15:51:19', '1', '2024-07-07 15:51:19', + b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1563, 3, '随意', '3', 'ai_write_tone', 0, '', '', '', '1', '2024-07-07 15:51:27', '1', '2024-07-07 15:51:27', + b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1564, 4, '友好', '4', 'ai_write_tone', 0, '', '', '', '1', '2024-07-07 15:51:37', '1', '2024-07-07 15:51:37', + b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1565, 5, '专业', '5', 'ai_write_tone', 0, '', '', '', '1', '2024-07-07 15:51:49', '1', '2024-07-07 15:52:02', + b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1566, 6, '诙谐', '6', 'ai_write_tone', 0, '', '', '', '1', '2024-07-07 15:52:15', '1', '2024-07-07 15:52:15', + b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1567, 7, '有趣', '7', 'ai_write_tone', 0, '', '', '', '1', '2024-07-07 15:52:24', '1', '2024-07-07 15:52:24', + b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1568, 8, '正式', '8', 'ai_write_tone', 0, '', '', '', '1', '2024-07-07 15:54:33', '1', '2024-07-07 15:54:33', + b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1569, 5, '段落', '5', 'ai_write_format', 0, '', '', '', '1', '2024-07-07 15:49:54', '1', '2024-07-07 15:49:54', + b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1570, 1, '自动', '1', 'ai_write_format', 0, '', '', '', '1', '2024-07-07 15:19:34', '1', '2024-07-07 15:19:34', + b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1571, 2, '电子邮件', '2', 'ai_write_format', 0, '', '', '', '1', '2024-07-07 15:19:50', '1', + '2024-07-07 15:49:30', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1572, 3, '消息', '3', 'ai_write_format', 0, '', '', '', '1', '2024-07-07 15:20:01', '1', '2024-07-07 15:49:38', + b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1573, 4, '评论', '4', 'ai_write_format', 0, '', '', '', '1', '2024-07-07 15:20:13', '1', '2024-07-07 15:49:45', + b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1574, 1, '自动', '1', 'ai_write_language', 0, '', '', '', '1', '2024-07-07 15:44:18', '1', + '2024-07-07 15:44:18', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1575, 2, '中文', '2', 'ai_write_language', 0, '', '', '', '1', '2024-07-07 15:44:28', '1', + '2024-07-07 15:44:28', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1576, 3, '英文', '3', 'ai_write_language', 0, '', '', '', '1', '2024-07-07 15:44:37', '1', + '2024-07-07 15:44:37', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1577, 4, '韩语', '4', 'ai_write_language', 0, '', '', '', '1', '2024-07-07 15:46:28', '1', + '2024-07-07 15:46:28', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1578, 5, '日语', '5', 'ai_write_language', 0, '', '', '', '1', '2024-07-07 15:46:44', '1', + '2024-07-07 15:46:44', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1579, 1, '自动', '1', 'ai_write_length', 0, '', '', '', '1', '2024-07-07 15:48:34', '1', '2024-07-07 15:48:34', + b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1580, 2, '短', '2', 'ai_write_length', 0, '', '', '', '1', '2024-07-07 15:48:44', '1', '2024-07-07 15:48:44', + b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1581, 3, '中等', '3', 'ai_write_length', 0, '', '', '', '1', '2024-07-07 15:48:52', '1', '2024-07-07 15:48:52', + b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1582, 4, '长', '4', 'ai_write_length', 0, '', '', '', '1', '2024-07-07 15:49:03', '1', '2024-07-07 15:49:03', + b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1584, 1, '撰写', '1', 'ai_write_type', 0, '', '', '', '1', '2024-07-10 21:26:00', '1', '2024-07-10 21:26:00', + b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1585, 2, '回复', '2', 'ai_write_type', 0, '', '', '', '1', '2024-07-10 21:26:06', '1', '2024-07-10 21:26:06', + b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1586, 2, '腾讯云', 'TENCENT', 'system_sms_channel_code', 0, '', '', '', '1', '2024-07-22 22:23:16', '1', + '2024-07-22 22:23:16', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1587, 3, '华为云', 'HUAWEI', 'system_sms_channel_code', 0, '', '', '', '1', '2024-07-22 22:23:46', '1', + '2024-07-22 22:23:53', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1588, 1, 'OpenAI 微软', 'AzureOpenAI', 'ai_platform', 0, '', '', '', '1', '2024-08-10 14:07:41', '1', + '2024-08-10 14:07:41', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1589, 10, 'BPMN 设计器', '10', 'bpm_model_type', 0, 'primary', '', '', '1', '2024-08-26 15:22:17', '1', + '2024-08-26 16:46:02', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1590, 20, 'SIMPLE 设计器', '20', 'bpm_model_type', 0, 'success', '', '', '1', '2024-08-26 15:22:27', '1', + '2024-08-26 16:45:58', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1591, 4, '七牛云', 'QINIU', 'system_sms_channel_code', 0, '', '', '', '1', '2024-08-31 08:45:03', '1', + '2024-08-31 08:45:24', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1592, 3, '新人券', '3', 'promotion_coupon_take_type', 0, 'info', '', '新人注册后,自动发放', '1', + '2024-09-03 11:57:16', '1', '2024-09-03 11:57:28', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1593, 5, '微信零钱', '5', 'brokerage_withdraw_type', 0, '', '', '自动打款', '1', '2024-10-13 11:06:48', '1', + '2024-10-13 11:06:59', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1655, 0, '标准数据格式(JSON)', '0', 'iot_data_format', 0, 'default', '', '', '1', '2024-08-10 11:53:26', '1', + '2024-09-06 14:31:02', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1656, 1, '透传/自定义', '1', 'iot_data_format', 0, 'default', '', '', '1', '2024-08-10 11:53:37', '1', + '2024-09-06 14:30:54', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1657, 0, '直连设备', '0', 'iot_product_device_type', 0, 'default', '', '', '1', '2024-08-10 11:54:58', '1', + '2024-09-06 21:57:01', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1658, 2, '网关设备', '2', 'iot_product_device_type', 0, 'default', '', '', '1', '2024-08-10 11:55:08', '1', + '2024-09-06 21:56:46', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1659, 1, '网关子设备', '1', 'iot_product_device_type', 0, 'default', '', '', '1', '2024-08-10 11:55:20', '1', + '2024-09-06 21:57:10', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1661, 1, '已发布', '1', 'iot_product_status', 0, 'success', '', '', '1', '2024-08-10 12:10:33', '1', + '2024-09-06 22:06:22', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1663, 0, '开发中', '0', 'iot_product_status', 0, 'default', '', '', '1', '2024-08-10 14:19:18', '1', + '2024-09-07 10:58:07', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1665, 0, '弱校验', '0', 'iot_validate_type', 0, '', '', '', '1', '2024-09-06 20:05:48', '1', + '2024-09-06 22:02:44', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1666, 1, '免校验', '1', 'iot_validate_type', 0, '', '', '', '1', '2024-09-06 20:06:03', '1', + '2024-09-06 22:02:51', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1667, 0, 'Wi-Fi', '0', 'iot_net_type', 0, '', '', '', '1', '2024-09-06 22:04:47', '1', '2024-09-06 22:04:47', + b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1668, 1, '蜂窝(2G / 3G / 4G / 5G)', '1', 'iot_net_type', 0, '', '', '', '1', '2024-09-06 22:05:14', '1', + '2024-09-06 22:05:14', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1669, 2, '以太网', '2', 'iot_net_type', 0, '', '', '', '1', '2024-09-06 22:05:35', '1', '2024-09-06 22:05:35', + b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1670, 3, '其他', '3', 'iot_net_type', 0, '', '', '', '1', '2024-09-06 22:05:52', '1', '2024-09-06 22:05:52', + b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1671, 0, '自定义', '0', 'iot_protocol_type', 0, '', '', '', '1', '2024-09-06 22:26:10', '1', + '2024-09-06 22:26:10', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1672, 1, 'Modbus', '1', 'iot_protocol_type', 0, '', '', '', '1', '2024-09-06 22:26:21', '1', + '2024-09-06 22:26:21', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1673, 2, 'OPC UA', '2', 'iot_protocol_type', 0, '', '', '', '1', '2024-09-06 22:26:31', '1', + '2024-09-06 22:26:31', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1674, 3, 'ZigBee', '3', 'iot_protocol_type', 0, '', '', '', '1', '2024-09-06 22:26:39', '1', + '2024-09-06 22:26:39', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1675, 4, 'BLE', '4', 'iot_protocol_type', 0, '', '', '', '1', '2024-09-06 22:26:48', '1', '2024-09-06 22:26:48', + b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1676, 0, '未激活', '0', 'iot_device_status', 0, '', '', '', '1', '2024-09-21 08:13:34', '1', + '2024-09-21 08:13:34', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1677, 1, '在线', '1', 'iot_device_status', 0, '', '', '', '1', '2024-09-21 08:13:48', '1', + '2024-09-21 08:13:48', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1678, 2, '离线', '2', 'iot_device_status', 0, '', '', '', '1', '2024-09-21 08:13:59', '1', + '2024-09-21 08:13:59', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1679, 3, '已禁用', '3', 'iot_device_status', 0, '', '', '', '1', '2024-09-21 08:14:13', '1', + '2024-09-21 08:14:13', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1680, 1, '属性', '1', 'iot_product_function_type', 0, '', '', '', '1', '2024-09-29 20:03:01', '1', + '2024-09-29 20:09:41', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1681, 2, '服务', '2', 'iot_product_function_type', 0, '', '', '', '1', '2024-09-29 20:03:11', '1', + '2024-09-29 20:08:23', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1682, 3, '事件', '3', 'iot_product_function_type', 0, '', '', '', '1', '2024-09-29 20:03:20', '1', + '2024-09-29 20:08:20', b'0'); +COMMIT; + +-- ---------------------------- +-- Table structure for system_dict_type +-- ---------------------------- +DROP TABLE IF EXISTS `system_dict_type`; +CREATE TABLE `system_dict_type` +( + `id` bigint NOT NULL AUTO_INCREMENT COMMENT '字典主键', + `name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '字典名称', + `type` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '字典类型', + `status` tinyint NOT NULL DEFAULT 0 COMMENT '状态(0正常 1停用)', + `remark` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '备注', + `creator` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '创建者', + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `updater` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '更新者', + `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除', + `deleted_time` datetime NULL DEFAULT NULL COMMENT '删除时间', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 640 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '字典类型表'; + +-- ---------------------------- +-- Records of system_dict_type +-- ---------------------------- +BEGIN; +INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, + `update_time`, `deleted`, `deleted_time`) +VALUES (1, '用户性别', 'system_user_sex', 0, NULL, 'admin', '2021-01-05 17:03:48', '1', '2022-05-16 20:29:32', b'0', + NULL); +INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, + `update_time`, `deleted`, `deleted_time`) +VALUES (6, '参数类型', 'infra_config_type', 0, NULL, 'admin', '2021-01-05 17:03:48', '', '2022-02-01 16:36:54', b'0', + NULL); +INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, + `update_time`, `deleted`, `deleted_time`) +VALUES (7, '通知类型', 'system_notice_type', 0, NULL, 'admin', '2021-01-05 17:03:48', '', '2022-02-01 16:35:26', b'0', + NULL); +INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, + `update_time`, `deleted`, `deleted_time`) +VALUES (9, '操作类型', 'infra_operate_type', 0, NULL, 'admin', '2021-01-05 17:03:48', '1', '2024-03-14 12:44:01', b'0', + NULL); +INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, + `update_time`, `deleted`, `deleted_time`) +VALUES (10, '系统状态', 'common_status', 0, NULL, 'admin', '2021-01-05 17:03:48', '', '2022-02-01 16:21:28', b'0', + NULL); +INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, + `update_time`, `deleted`, `deleted_time`) +VALUES (11, 'Boolean 是否类型', 'infra_boolean_string', 0, 'boolean 转是否', '', '2021-01-19 03:20:08', '', + '2022-02-01 16:37:10', b'0', NULL); +INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, + `update_time`, `deleted`, `deleted_time`) +VALUES (104, '登陆结果', 'system_login_result', 0, '登陆结果', '', '2021-01-18 06:17:11', '', '2022-02-01 16:36:00', + b'0', NULL); +INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, + `update_time`, `deleted`, `deleted_time`) +VALUES (106, '代码生成模板类型', 'infra_codegen_template_type', 0, NULL, '', '2021-02-05 07:08:06', '1', + '2022-05-16 20:26:50', b'0', NULL); +INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, + `update_time`, `deleted`, `deleted_time`) +VALUES (107, '定时任务状态', 'infra_job_status', 0, NULL, '', '2021-02-07 07:44:16', '', '2022-02-01 16:51:11', b'0', + NULL); +INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, + `update_time`, `deleted`, `deleted_time`) +VALUES (108, '定时任务日志状态', 'infra_job_log_status', 0, NULL, '', '2021-02-08 10:03:51', '', '2022-02-01 16:50:43', + b'0', NULL); +INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, + `update_time`, `deleted`, `deleted_time`) +VALUES (109, '用户类型', 'user_type', 0, NULL, '', '2021-02-26 00:15:51', '', '2021-02-26 00:15:51', b'0', NULL); +INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, + `update_time`, `deleted`, `deleted_time`) +VALUES (110, 'API 异常数据的处理状态', 'infra_api_error_log_process_status', 0, NULL, '', '2021-02-26 07:07:01', '', + '2022-02-01 16:50:53', b'0', NULL); +INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, + `update_time`, `deleted`, `deleted_time`) +VALUES (111, '短信渠道编码', 'system_sms_channel_code', 0, NULL, '1', '2021-04-05 01:04:50', '1', '2022-02-16 02:09:08', + b'0', NULL); +INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, + `update_time`, `deleted`, `deleted_time`) +VALUES (112, '短信模板的类型', 'system_sms_template_type', 0, NULL, '1', '2021-04-05 21:50:43', '1', + '2022-02-01 16:35:06', b'0', NULL); +INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, + `update_time`, `deleted`, `deleted_time`) +VALUES (113, '短信发送状态', 'system_sms_send_status', 0, NULL, '1', '2021-04-11 20:18:03', '1', '2022-02-01 16:35:09', + b'0', NULL); +INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, + `update_time`, `deleted`, `deleted_time`) +VALUES (114, '短信接收状态', 'system_sms_receive_status', 0, NULL, '1', '2021-04-11 20:27:14', '1', + '2022-02-01 16:35:14', b'0', NULL); +INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, + `update_time`, `deleted`, `deleted_time`) +VALUES (116, '登陆日志的类型', 'system_login_type', 0, '登陆日志的类型', '1', '2021-10-06 00:50:46', '1', + '2022-02-01 16:35:56', b'0', NULL); +INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, + `update_time`, `deleted`, `deleted_time`) +VALUES (117, 'OA 请假类型', 'bpm_oa_leave_type', 0, NULL, '1', '2021-09-21 22:34:33', '1', '2022-01-22 10:41:37', b'0', + NULL); +INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, + `update_time`, `deleted`, `deleted_time`) +VALUES (130, '支付渠道编码类型', 'pay_channel_code', 0, '支付渠道的编码', '1', '2021-12-03 10:35:08', '1', + '2023-07-10 10:11:39', b'0', NULL); +INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, + `update_time`, `deleted`, `deleted_time`) +VALUES (131, '支付回调状态', 'pay_notify_status', 0, '支付回调状态(包括退款回调)', '1', '2021-12-03 10:53:29', '1', + '2023-07-19 18:09:43', b'0', NULL); +INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, + `update_time`, `deleted`, `deleted_time`) +VALUES (132, '支付订单状态', 'pay_order_status', 0, '支付订单状态', '1', '2021-12-03 11:17:50', '1', + '2021-12-03 11:17:50', b'0', NULL); +INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, + `update_time`, `deleted`, `deleted_time`) +VALUES (134, '退款订单状态', 'pay_refund_status', 0, '退款订单状态', '1', '2021-12-10 16:42:50', '1', + '2023-07-19 10:13:17', b'0', NULL); +INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, + `update_time`, `deleted`, `deleted_time`) +VALUES (139, '流程实例的状态', 'bpm_process_instance_status', 0, '流程实例的状态', '1', '2022-01-07 23:46:42', '1', + '2022-01-07 23:46:42', b'0', NULL); +INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, + `update_time`, `deleted`, `deleted_time`) +VALUES (140, '流程实例的结果', 'bpm_task_status', 0, '流程实例的结果', '1', '2022-01-07 23:48:10', '1', + '2024-03-08 22:42:03', b'0', NULL); +INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, + `update_time`, `deleted`, `deleted_time`) +VALUES (141, '流程的表单类型', 'bpm_model_form_type', 0, '流程的表单类型', '103', '2022-01-11 23:50:45', '103', + '2022-01-11 23:50:45', b'0', NULL); +INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, + `update_time`, `deleted`, `deleted_time`) +VALUES (142, '任务分配规则的类型', 'bpm_task_candidate_strategy', 0, 'BPM 任务的候选人的策略', '103', + '2022-01-12 23:21:04', '103', '2024-03-06 02:53:59', b'0', NULL); +INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, + `update_time`, `deleted`, `deleted_time`) +VALUES (144, '代码生成的场景枚举', 'infra_codegen_scene', 0, '代码生成的场景枚举', '1', '2022-02-02 13:14:45', '1', + '2022-03-10 16:33:46', b'0', NULL); +INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, + `update_time`, `deleted`, `deleted_time`) +VALUES (145, '角色类型', 'system_role_type', 0, '角色类型', '1', '2022-02-16 13:01:46', '1', '2022-02-16 13:01:46', + b'0', NULL); +INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, + `update_time`, `deleted`, `deleted_time`) +VALUES (146, '文件存储器', 'infra_file_storage', 0, '文件存储器', '1', '2022-03-15 00:24:38', '1', + '2022-03-15 00:24:38', b'0', NULL); +INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, + `update_time`, `deleted`, `deleted_time`) +VALUES (147, 'OAuth 2.0 授权类型', 'system_oauth2_grant_type', 0, 'OAuth 2.0 授权类型(模式)', '1', + '2022-05-12 00:20:52', '1', '2022-05-11 16:25:49', b'0', NULL); +INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, + `update_time`, `deleted`, `deleted_time`) +VALUES (149, '商品 SPU 状态', 'product_spu_status', 0, '商品 SPU 状态', '1', '2022-10-24 21:19:04', '1', + '2022-10-24 21:19:08', b'0', NULL); +INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, + `update_time`, `deleted`, `deleted_time`) +VALUES (150, '优惠类型', 'promotion_discount_type', 0, '优惠类型', '1', '2022-11-01 12:46:06', '1', + '2022-11-01 12:46:06', b'0', NULL); +INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, + `update_time`, `deleted`, `deleted_time`) +VALUES (151, '优惠劵模板的有限期类型', 'promotion_coupon_template_validity_type', 0, '优惠劵模板的有限期类型', '1', + '2022-11-02 00:06:20', '1', '2022-11-04 00:08:26', b'0', NULL); +INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, + `update_time`, `deleted`, `deleted_time`) +VALUES (152, '营销的商品范围', 'promotion_product_scope', 0, '营销的商品范围', '1', '2022-11-02 00:28:01', '1', + '2022-11-02 00:28:01', b'0', NULL); +INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, + `update_time`, `deleted`, `deleted_time`) +VALUES (153, '优惠劵的状态', 'promotion_coupon_status', 0, '优惠劵的状态', '1', '2022-11-04 00:14:49', '1', + '2022-11-04 00:14:49', b'0', NULL); +INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, + `update_time`, `deleted`, `deleted_time`) +VALUES (154, '优惠劵的领取方式', 'promotion_coupon_take_type', 0, '优惠劵的领取方式', '1', '2022-11-04 19:12:27', '1', + '2022-11-04 19:12:27', b'0', NULL); +INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, + `update_time`, `deleted`, `deleted_time`) +VALUES (155, '促销活动的状态', 'promotion_activity_status', 0, '促销活动的状态', '1', '2022-11-04 22:54:23', '1', + '2022-11-04 22:54:23', b'0', NULL); +INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, + `update_time`, `deleted`, `deleted_time`) +VALUES (156, '营销的条件类型', 'promotion_condition_type', 0, '营销的条件类型', '1', '2022-11-04 22:59:23', '1', + '2022-11-04 22:59:23', b'0', NULL); +INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, + `update_time`, `deleted`, `deleted_time`) +VALUES (157, '交易售后状态', 'trade_after_sale_status', 0, '交易售后状态', '1', '2022-11-19 20:52:56', '1', + '2022-11-19 20:52:56', b'0', NULL); +INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, + `update_time`, `deleted`, `deleted_time`) +VALUES (158, '交易售后的类型', 'trade_after_sale_type', 0, '交易售后的类型', '1', '2022-11-19 21:04:09', '1', + '2022-11-19 21:04:09', b'0', NULL); +INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, + `update_time`, `deleted`, `deleted_time`) +VALUES (159, '交易售后的方式', 'trade_after_sale_way', 0, '交易售后的方式', '1', '2022-11-19 21:39:04', '1', + '2022-11-19 21:39:04', b'0', NULL); +INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, + `update_time`, `deleted`, `deleted_time`) +VALUES (160, '终端', 'terminal', 0, '终端', '1', '2022-12-10 10:50:50', '1', '2022-12-10 10:53:11', b'0', NULL); +INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, + `update_time`, `deleted`, `deleted_time`) +VALUES (161, '交易订单的类型', 'trade_order_type', 0, '交易订单的类型', '1', '2022-12-10 16:33:54', '1', + '2022-12-10 16:33:54', b'0', NULL); +INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, + `update_time`, `deleted`, `deleted_time`) +VALUES (162, '交易订单的状态', 'trade_order_status', 0, '交易订单的状态', '1', '2022-12-10 16:48:44', '1', + '2022-12-10 16:48:44', b'0', NULL); +INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, + `update_time`, `deleted`, `deleted_time`) +VALUES (163, '交易订单项的售后状态', 'trade_order_item_after_sale_status', 0, '交易订单项的售后状态', '1', + '2022-12-10 20:58:08', '1', '2022-12-10 20:58:08', b'0', NULL); +INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, + `update_time`, `deleted`, `deleted_time`) +VALUES (164, '公众号自动回复的请求关键字匹配模式', 'mp_auto_reply_request_match', 0, + '公众号自动回复的请求关键字匹配模式', '1', '2023-01-16 23:29:56', '1', '2023-01-16 23:29:56', b'0', + '1970-01-01 00:00:00'); +INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, + `update_time`, `deleted`, `deleted_time`) +VALUES (165, '公众号的消息类型', 'mp_message_type', 0, '公众号的消息类型', '1', '2023-01-17 22:17:09', '1', + '2023-01-17 22:17:09', b'0', '1970-01-01 00:00:00'); +INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, + `update_time`, `deleted`, `deleted_time`) +VALUES (166, '邮件发送状态', 'system_mail_send_status', 0, '邮件发送状态', '1', '2023-01-26 09:53:13', '1', + '2023-01-26 09:53:13', b'0', '1970-01-01 00:00:00'); +INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, + `update_time`, `deleted`, `deleted_time`) +VALUES (167, '站内信模版的类型', 'system_notify_template_type', 0, '站内信模版的类型', '1', '2023-01-28 10:35:10', '1', + '2023-01-28 10:35:10', b'0', '1970-01-01 00:00:00'); +INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, + `update_time`, `deleted`, `deleted_time`) +VALUES (168, '代码生成的前端类型', 'infra_codegen_front_type', 0, '', '1', '2023-04-12 23:57:52', '1', + '2023-04-12 23:57:52', b'0', '1970-01-01 00:00:00'); +INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, + `update_time`, `deleted`, `deleted_time`) +VALUES (170, '快递计费方式', 'trade_delivery_express_charge_mode', 0, '用于商城交易模块配送管理', '1', + '2023-05-21 22:45:03', '1', '2023-05-21 22:45:03', b'0', '1970-01-01 00:00:00'); +INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, + `update_time`, `deleted`, `deleted_time`) +VALUES (171, '积分业务类型', 'member_point_biz_type', 0, '', '1', '2023-06-10 12:15:00', '1', '2023-06-28 13:48:20', + b'0', '1970-01-01 00:00:00'); +INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, + `update_time`, `deleted`, `deleted_time`) +VALUES (173, '支付通知类型', 'pay_notify_type', 0, NULL, '1', '2023-07-20 12:23:03', '1', '2023-07-20 12:23:03', b'0', + '1970-01-01 00:00:00'); +INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, + `update_time`, `deleted`, `deleted_time`) +VALUES (174, '会员经验业务类型', 'member_experience_biz_type', 0, NULL, '', '2023-08-22 12:41:01', '', + '2023-08-22 12:41:01', b'0', NULL); +INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, + `update_time`, `deleted`, `deleted_time`) +VALUES (175, '交易配送类型', 'trade_delivery_type', 0, '', '1', '2023-08-23 00:03:14', '1', '2023-08-23 00:03:14', b'0', + '1970-01-01 00:00:00'); +INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, + `update_time`, `deleted`, `deleted_time`) +VALUES (176, '分佣模式', 'brokerage_enabled_condition', 0, NULL, '', '2023-09-28 02:46:05', '', '2023-09-28 02:46:05', + b'0', NULL); +INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, + `update_time`, `deleted`, `deleted_time`) +VALUES (177, '分销关系绑定模式', 'brokerage_bind_mode', 0, NULL, '', '2023-09-28 02:46:05', '', '2023-09-28 02:46:05', + b'0', NULL); +INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, + `update_time`, `deleted`, `deleted_time`) +VALUES (178, '佣金提现类型', 'brokerage_withdraw_type', 0, NULL, '', '2023-09-28 02:46:05', '', '2023-09-28 02:46:05', + b'0', NULL); +INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, + `update_time`, `deleted`, `deleted_time`) +VALUES (179, '佣金记录业务类型', 'brokerage_record_biz_type', 0, NULL, '', '2023-09-28 02:46:05', '', + '2023-09-28 02:46:05', b'0', NULL); +INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, + `update_time`, `deleted`, `deleted_time`) +VALUES (180, '佣金记录状态', 'brokerage_record_status', 0, NULL, '', '2023-09-28 02:46:05', '', '2023-09-28 02:46:05', + b'0', NULL); +INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, + `update_time`, `deleted`, `deleted_time`) +VALUES (181, '佣金提现状态', 'brokerage_withdraw_status', 0, NULL, '', '2023-09-28 02:46:05', '', '2023-09-28 02:46:05', + b'0', NULL); +INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, + `update_time`, `deleted`, `deleted_time`) +VALUES (182, '佣金提现银行', 'brokerage_bank_name', 0, NULL, '', '2023-09-28 02:46:05', '', '2023-09-28 02:46:05', b'0', + NULL); +INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, + `update_time`, `deleted`, `deleted_time`) +VALUES (183, '砍价记录的状态', 'promotion_bargain_record_status', 0, '', '1', '2023-10-05 10:41:08', '1', + '2023-10-05 10:41:08', b'0', '1970-01-01 00:00:00'); +INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, + `update_time`, `deleted`, `deleted_time`) +VALUES (184, '拼团记录的状态', 'promotion_combination_record_status', 0, '', '1', '2023-10-08 07:24:25', '1', + '2023-10-08 07:24:25', b'0', '1970-01-01 00:00:00'); +INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, + `update_time`, `deleted`, `deleted_time`) +VALUES (185, '回款-回款方式', 'crm_receivable_return_type', 0, '回款-回款方式', '1', '2023-10-18 21:54:10', '1', + '2023-10-18 21:54:10', b'0', '1970-01-01 00:00:00'); +INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, + `update_time`, `deleted`, `deleted_time`) +VALUES (186, 'CRM 客户行业', 'crm_customer_industry', 0, 'CRM 客户所属行业', '1', '2023-10-28 22:57:07', '1', + '2024-02-18 23:30:22', b'0', NULL); +INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, + `update_time`, `deleted`, `deleted_time`) +VALUES (187, '客户等级', 'crm_customer_level', 0, 'CRM 客户等级', '1', '2023-10-28 22:59:12', '1', + '2023-10-28 15:11:16', b'0', NULL); +INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, + `update_time`, `deleted`, `deleted_time`) +VALUES (188, '客户来源', 'crm_customer_source', 0, 'CRM 客户来源', '1', '2023-10-28 23:00:34', '1', + '2023-10-28 15:11:16', b'0', NULL); +INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, + `update_time`, `deleted`, `deleted_time`) +VALUES (600, 'Banner 位置', 'promotion_banner_position', 0, '', '1', '2023-10-08 07:24:25', '1', '2023-11-04 13:04:02', + b'0', '1970-01-01 00:00:00'); +INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, + `update_time`, `deleted`, `deleted_time`) +VALUES (601, '社交类型', 'system_social_type', 0, '', '1', '2023-11-04 13:03:54', '1', '2023-11-04 13:03:54', b'0', + '1970-01-01 00:00:00'); +INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, + `update_time`, `deleted`, `deleted_time`) +VALUES (604, '产品状态', 'crm_product_status', 0, '', '1', '2023-10-30 21:47:59', '1', '2023-10-30 21:48:45', b'0', + '1970-01-01 00:00:00'); +INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, + `update_time`, `deleted`, `deleted_time`) +VALUES (605, 'CRM 数据权限的级别', 'crm_permission_level', 0, '', '1', '2023-11-30 09:51:59', '1', + '2023-11-30 09:51:59', b'0', '1970-01-01 00:00:00'); +INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, + `update_time`, `deleted`, `deleted_time`) +VALUES (606, 'CRM 审批状态', 'crm_audit_status', 0, '', '1', '2023-11-30 18:56:23', '1', '2023-11-30 18:56:23', b'0', + '1970-01-01 00:00:00'); +INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, + `update_time`, `deleted`, `deleted_time`) +VALUES (607, 'CRM 产品单位', 'crm_product_unit', 0, '', '1', '2023-12-05 23:01:51', '1', '2023-12-05 23:01:51', b'0', + '1970-01-01 00:00:00'); +INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, + `update_time`, `deleted`, `deleted_time`) +VALUES (608, 'CRM 跟进方式', 'crm_follow_up_type', 0, '', '1', '2024-01-15 20:48:05', '1', '2024-01-15 20:48:05', b'0', + '1970-01-01 00:00:00'); +INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, + `update_time`, `deleted`, `deleted_time`) +VALUES (609, '支付转账类型', 'pay_transfer_type', 0, '', '1', '2023-10-28 16:27:18', '1', '2023-10-28 16:27:18', b'0', + '1970-01-01 00:00:00'); +INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, + `update_time`, `deleted`, `deleted_time`) +VALUES (610, '转账订单状态', 'pay_transfer_status', 0, '', '1', '2023-10-28 16:18:32', '1', '2023-10-28 16:18:32', b'0', + '1970-01-01 00:00:00'); +INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, + `update_time`, `deleted`, `deleted_time`) +VALUES (611, 'ERP 库存明细的业务类型', 'erp_stock_record_biz_type', 0, 'ERP 库存明细的业务类型', '1', + '2024-02-05 18:07:02', '1', '2024-02-05 18:07:02', b'0', '1970-01-01 00:00:00'); +INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, + `update_time`, `deleted`, `deleted_time`) +VALUES (612, 'ERP 审批状态', 'erp_audit_status', 0, '', '1', '2024-02-06 00:00:07', '1', '2024-02-06 00:00:07', b'0', + '1970-01-01 00:00:00'); +INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, + `update_time`, `deleted`, `deleted_time`) +VALUES (613, 'BPM 监听器类型', 'bpm_process_listener_type', 0, '', '1', '2024-03-23 12:52:24', '1', + '2024-03-09 15:54:28', b'0', '1970-01-01 00:00:00'); +INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, + `update_time`, `deleted`, `deleted_time`) +VALUES (615, 'BPM 监听器值类型', 'bpm_process_listener_value_type', 0, '', '1', '2024-03-23 13:00:31', '1', + '2024-03-23 13:00:31', b'0', '1970-01-01 00:00:00'); +INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, + `update_time`, `deleted`, `deleted_time`) +VALUES (616, '时间间隔', 'date_interval', 0, '', '1', '2024-03-29 22:50:09', '1', '2024-03-29 22:50:09', b'0', + '1970-01-01 00:00:00'); +INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, + `update_time`, `deleted`, `deleted_time`) +VALUES (619, 'CRM 商机结束状态类型', 'crm_business_end_status_type', 0, '', '1', '2024-04-13 23:23:00', '1', + '2024-04-13 23:23:00', b'0', '1970-01-01 00:00:00'); +INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, + `update_time`, `deleted`, `deleted_time`) +VALUES (620, 'AI 模型平台', 'ai_platform', 0, '', '1', '2024-05-09 22:27:38', '1', '2024-05-09 22:27:38', b'0', + '1970-01-01 00:00:00'); +INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, + `update_time`, `deleted`, `deleted_time`) +VALUES (621, 'AI 绘画状态', 'ai_image_status', 0, '', '1', '2024-06-26 20:51:23', '1', '2024-06-26 20:51:23', b'0', + '1970-01-01 00:00:00'); +INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, + `update_time`, `deleted`, `deleted_time`) +VALUES (622, 'AI 音乐状态', 'ai_music_status', 0, '', '1', '2024-06-27 22:45:07', '1', '2024-06-28 00:56:27', b'0', + '1970-01-01 00:00:00'); +INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, + `update_time`, `deleted`, `deleted_time`) +VALUES (623, 'AI 音乐生成模式', 'ai_generate_mode', 0, '', '1', '2024-06-27 22:46:21', '1', '2024-06-28 01:22:29', b'0', + '1970-01-01 00:00:00'); +INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, + `update_time`, `deleted`, `deleted_time`) +VALUES (624, '写作语气', 'ai_write_tone', 0, '', '1', '2024-07-07 15:19:02', '1', '2024-07-07 15:19:02', b'0', + '1970-01-01 00:00:00'); +INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, + `update_time`, `deleted`, `deleted_time`) +VALUES (625, '写作语言', 'ai_write_language', 0, '', '1', '2024-07-07 15:18:52', '1', '2024-07-07 15:18:52', b'0', + '1970-01-01 00:00:00'); +INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, + `update_time`, `deleted`, `deleted_time`) +VALUES (626, '写作长度', 'ai_write_length', 0, '', '1', '2024-07-07 15:18:41', '1', '2024-07-07 15:18:41', b'0', + '1970-01-01 00:00:00'); +INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, + `update_time`, `deleted`, `deleted_time`) +VALUES (627, '写作格式', 'ai_write_format', 0, '', '1', '2024-07-07 15:14:34', '1', '2024-07-07 15:14:34', b'0', + '1970-01-01 00:00:00'); +INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, + `update_time`, `deleted`, `deleted_time`) +VALUES (628, 'AI 写作类型', 'ai_write_type', 0, '', '1', '2024-07-10 21:25:29', '1', '2024-07-10 21:25:29', b'0', + '1970-01-01 00:00:00'); +INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, + `update_time`, `deleted`, `deleted_time`) +VALUES (629, 'BPM 流程模型类型', 'bpm_model_type', 0, '', '1', '2024-08-26 15:21:43', '1', '2024-08-26 15:21:43', b'0', + '1970-01-01 00:00:00'); +INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, + `update_time`, `deleted`, `deleted_time`) +VALUES (630, 'IOT 接入网关协议', 'iot_protocol_type', 0, '', '1', '2024-09-06 22:20:17', '1', '2024-09-06 22:20:17', + b'0', '1970-01-01 00:00:00'); +INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, + `update_time`, `deleted`, `deleted_time`) +VALUES (631, 'IOT 设备状态', 'iot_device_status', 0, '', '1', '2024-09-21 08:12:55', '1', '2024-09-21 08:12:55', b'0', + '1970-01-01 00:00:00'); +INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, + `update_time`, `deleted`, `deleted_time`) +VALUES (632, 'IOT 物模型功能类型', 'iot_product_function_type', 0, '', '1', '2024-09-29 20:02:36', '1', + '2024-09-29 20:09:26', b'0', '1970-01-01 00:00:00'); +INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, + `update_time`, `deleted`, `deleted_time`) +VALUES (634, 'IOT 数据格式', 'iot_data_format', 0, '', '1', '2024-08-10 11:52:58', '1', '2024-09-06 14:30:14', b'0', + '1970-01-01 00:00:00'); +INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, + `update_time`, `deleted`, `deleted_time`) +VALUES (635, 'IOT 产品设备类型', 'iot_product_device_type', 0, '', '1', '2024-08-10 11:54:30', '1', + '2024-08-10 04:06:56', b'0', '1970-01-01 00:00:00'); +INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, + `update_time`, `deleted`, `deleted_time`) +VALUES (637, 'IOT 产品状态', 'iot_product_status', 0, '', '1', '2024-08-10 12:06:09', '1', '2024-08-10 12:06:09', b'0', + '1970-01-01 00:00:00'); +INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, + `update_time`, `deleted`, `deleted_time`) +VALUES (638, 'IOT 数据校验级别', 'iot_validate_type', 0, '', '1', '2024-09-06 20:05:13', '1', '2024-09-06 20:05:13', + b'0', '1970-01-01 00:00:00'); +INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, + `update_time`, `deleted`, `deleted_time`) +VALUES (639, 'IOT 联网方式', 'iot_net_type', 0, '', '1', '2024-09-06 22:04:13', '1', '2024-09-06 22:04:13', b'0', + '1970-01-01 00:00:00'); +COMMIT; + +-- ---------------------------- +-- Table structure for system_login_log +-- ---------------------------- +DROP TABLE IF EXISTS `system_login_log`; +CREATE TABLE `system_login_log` +( + `id` bigint NOT NULL AUTO_INCREMENT COMMENT '访问ID', + `log_type` bigint NOT NULL COMMENT '日志类型', + `trace_id` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '链路追踪编号', + `user_id` bigint NOT NULL DEFAULT 0 COMMENT '用户编号', + `user_type` tinyint NOT NULL DEFAULT 0 COMMENT '用户类型', + `username` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '用户账号', + `result` tinyint NOT NULL COMMENT '登陆结果', + `user_ip` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '用户 IP', + `user_agent` varchar(512) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '浏览器 UA', + `creator` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '创建者', + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `updater` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '更新者', + `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除', + `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 3415 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '系统访问记录'; + +-- ---------------------------- +-- Records of system_login_log +-- ---------------------------- +BEGIN; +COMMIT; + +-- ---------------------------- +-- Table structure for system_mail_account +-- ---------------------------- +DROP TABLE IF EXISTS `system_mail_account`; +CREATE TABLE `system_mail_account` +( + `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键', + `mail` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '邮箱', + `username` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '用户名', + `password` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '密码', + `host` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT 'SMTP 服务器域名', + `port` int NOT NULL COMMENT 'SMTP 服务器端口', + `ssl_enable` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否开启 SSL', + `starttls_enable` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否开启 STARTTLS', + `creator` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '创建者', + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `updater` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '更新者', + `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 5 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '邮箱账号表'; + +-- ---------------------------- +-- Records of system_mail_account +-- ---------------------------- +BEGIN; +INSERT INTO `system_mail_account` (`id`, `mail`, `username`, `password`, `host`, `port`, `ssl_enable`, + `starttls_enable`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (1, '7684413@qq.com', '7684413@qq.com', '1234576', '127.0.0.1', 8080, b'0', b'0', '1', '2023-01-25 17:39:52', + '1', '2024-07-27 22:39:12', b'0'); +INSERT INTO `system_mail_account` (`id`, `mail`, `username`, `password`, `host`, `port`, `ssl_enable`, + `starttls_enable`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (2, 'ydym_test@163.com', 'ydym_test@163.com', 'WBZTEINMIFVRYSOE', 'smtp.163.com', 465, b'1', b'0', '1', + '2023-01-26 01:26:03', '1', '2023-04-12 22:39:38', b'0'); +INSERT INTO `system_mail_account` (`id`, `mail`, `username`, `password`, `host`, `port`, `ssl_enable`, + `starttls_enable`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (3, '76854114@qq.com', '3335', '11234', 'yunai1.cn', 466, b'0', b'0', '1', '2023-01-27 15:06:38', '1', + '2023-01-27 07:08:36', b'1'); +INSERT INTO `system_mail_account` (`id`, `mail`, `username`, `password`, `host`, `port`, `ssl_enable`, + `starttls_enable`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (4, '7685413x@qq.com', '2', '3', '4', 5, b'1', b'0', '1', '2023-04-12 23:05:06', '1', '2023-04-12 15:05:11', + b'1'); +COMMIT; + +-- ---------------------------- +-- Table structure for system_mail_log +-- ---------------------------- +DROP TABLE IF EXISTS `system_mail_log`; +CREATE TABLE `system_mail_log` +( + `id` bigint NOT NULL AUTO_INCREMENT COMMENT '编号', + `user_id` bigint NULL DEFAULT NULL COMMENT '用户编号', + `user_type` tinyint NULL DEFAULT NULL COMMENT '用户类型', + `to_mail` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '接收邮箱地址', + `account_id` bigint NOT NULL COMMENT '邮箱账号编号', + `from_mail` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '发送邮箱地址', + `template_id` bigint NOT NULL COMMENT '模板编号', + `template_code` varchar(63) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '模板编码', + `template_nickname` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '模版发送人名称', + `template_title` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '邮件标题', + `template_content` varchar(10240) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '邮件内容', + `template_params` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '邮件参数', + `send_status` tinyint NOT NULL DEFAULT 0 COMMENT '发送状态', + `send_time` datetime NULL DEFAULT NULL COMMENT '发送时间', + `send_message_id` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '发送返回的消息 ID', + `send_exception` varchar(4096) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '发送异常', + `creator` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '创建者', + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `updater` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '更新者', + `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 359 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '邮件日志表'; + +-- ---------------------------- +-- Records of system_mail_log +-- ---------------------------- +BEGIN; +COMMIT; + +-- ---------------------------- +-- Table structure for system_mail_template +-- ---------------------------- +DROP TABLE IF EXISTS `system_mail_template`; +CREATE TABLE `system_mail_template` +( + `id` bigint NOT NULL AUTO_INCREMENT COMMENT '编号', + `name` varchar(63) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '模板名称', + `code` varchar(63) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '模板编码', + `account_id` bigint NOT NULL COMMENT '发送的邮箱账号编号', + `nickname` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '发送人名称', + `title` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '模板标题', + `content` varchar(10240) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '模板内容', + `params` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '参数数组', + `status` tinyint NOT NULL COMMENT '开启状态', + `remark` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '备注', + `creator` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '创建者', + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `updater` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '更新者', + `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 16 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '邮件模版表'; + +-- ---------------------------- +-- Records of system_mail_template +-- ---------------------------- +BEGIN; +INSERT INTO `system_mail_template` (`id`, `name`, `code`, `account_id`, `nickname`, `title`, `content`, `params`, + `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (13, '后台用户短信登录', 'admin-sms-login', 1, '奥特曼', '你猜我猜', '

您的验证码是{code},名字是{name}

', + '[\"code\",\"name\"]', 0, '3', '1', '2021-10-11 08:10:00', '1', '2023-12-02 19:51:14', b'0'); +INSERT INTO `system_mail_template` (`id`, `name`, `code`, `account_id`, `nickname`, `title`, `content`, `params`, + `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (14, '测试模版', 'test_01', 2, '芋艿', '一个标题', + '

你是 {key01} 吗?


是的话,赶紧 {key02} 一下!

', '[\"key01\",\"key02\"]', 0, NULL, '1', + '2023-01-26 01:27:40', '1', '2023-01-27 10:32:16', b'0'); +INSERT INTO `system_mail_template` (`id`, `name`, `code`, `account_id`, `nickname`, `title`, `content`, `params`, + `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (15, '3', '2', 2, '7', '4', '

45

', '[]', 1, '80', '1', '2023-01-27 15:50:35', '1', '2023-01-27 16:34:49', + b'0'); +COMMIT; + +-- ---------------------------- +-- Table structure for system_menu +-- ---------------------------- +DROP TABLE IF EXISTS `system_menu`; +CREATE TABLE `system_menu` +( + `id` bigint NOT NULL AUTO_INCREMENT COMMENT '菜单ID', + `name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '菜单名称', + `permission` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '权限标识', + `type` tinyint NOT NULL COMMENT '菜单类型', + `sort` int NOT NULL DEFAULT 0 COMMENT '显示顺序', + `parent_id` bigint NOT NULL DEFAULT 0 COMMENT '父菜单ID', + `path` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '路由地址', + `icon` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '#' COMMENT '菜单图标', + `component` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '组件路径', + `component_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '组件名', + `status` tinyint NOT NULL DEFAULT 0 COMMENT '菜单状态', + `visible` bit(1) NOT NULL DEFAULT b'1' COMMENT '是否可见', + `keep_alive` bit(1) NOT NULL DEFAULT b'1' COMMENT '是否缓存', + `always_show` bit(1) NOT NULL DEFAULT b'1' COMMENT '是否总是显示', + `creator` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '创建者', + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `updater` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '更新者', + `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 2913 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '菜单权限表'; + +-- ---------------------------- +-- Records of system_menu +-- ---------------------------- +BEGIN; +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (1, '系统管理', '', 1, 10, 0, '/system', 'ep:tools', NULL, NULL, 0, b'1', b'1', b'1', 'admin', + '2021-01-05 17:03:48', '1', '2024-06-18 01:19:41', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2, '基础设施', '', 1, 20, 0, '/infra', 'ep:monitor', NULL, NULL, 0, b'1', b'1', b'1', 'admin', + '2021-01-05 17:03:48', '1', '2024-03-01 08:28:40', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (5, 'OA 示例', '', 1, 40, 1185, 'oa', 'fa:road', NULL, NULL, 0, b'1', b'1', b'1', 'admin', '2021-09-20 16:26:19', + '1', '2024-02-29 12:38:13', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (100, '用户管理', 'system:user:list', 2, 1, 1, 'user', 'ep:avatar', 'system/user/index', 'SystemUser', 0, b'1', + b'1', b'1', 'admin', '2021-01-05 17:03:48', '1', '2024-02-29 01:02:04', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (101, '角色管理', '', 2, 2, 1, 'role', 'ep:user', 'system/role/index', 'SystemRole', 0, b'1', b'1', b'1', + 'admin', '2021-01-05 17:03:48', '1', '2024-05-01 18:35:29', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (102, '菜单管理', '', 2, 3, 1, 'menu', 'ep:menu', 'system/menu/index', 'SystemMenu', 0, b'1', b'1', b'1', + 'admin', '2021-01-05 17:03:48', '1', '2024-02-29 01:03:50', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (103, '部门管理', '', 2, 4, 1, 'dept', 'fa:address-card', 'system/dept/index', 'SystemDept', 0, b'1', b'1', b'1', + 'admin', '2021-01-05 17:03:48', '1', '2024-02-29 01:06:28', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (104, '岗位管理', '', 2, 5, 1, 'post', 'fa:address-book-o', 'system/post/index', 'SystemPost', 0, b'1', b'1', + b'1', 'admin', '2021-01-05 17:03:48', '1', '2024-02-29 01:06:39', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (105, '字典管理', '', 2, 6, 1, 'dict', 'ep:collection', 'system/dict/index', 'SystemDictType', 0, b'1', b'1', + b'1', 'admin', '2021-01-05 17:03:48', '1', '2024-02-29 01:07:12', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (106, '配置管理', '', 2, 8, 2, 'config', 'fa:connectdevelop', 'infra/config/index', 'InfraConfig', 0, b'1', b'1', + b'1', 'admin', '2021-01-05 17:03:48', '1', '2024-04-23 00:02:45', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (107, '通知公告', '', 2, 4, 2739, 'notice', 'ep:takeaway-box', 'system/notice/index', 'SystemNotice', 0, b'1', + b'1', b'1', 'admin', '2021-01-05 17:03:48', '1', '2024-04-22 23:56:17', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (108, '审计日志', '', 1, 9, 1, 'log', 'ep:document-copy', '', NULL, 0, b'1', b'1', b'1', 'admin', + '2021-01-05 17:03:48', '1', '2024-02-29 01:08:30', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (109, '令牌管理', '', 2, 2, 1261, 'token', 'fa:key', 'system/oauth2/token/index', 'SystemTokenClient', 0, b'1', + b'1', b'1', 'admin', '2021-01-05 17:03:48', '1', '2024-02-29 01:13:48', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (110, '定时任务', '', 2, 7, 2, 'job', 'fa-solid:tasks', 'infra/job/index', 'InfraJob', 0, b'1', b'1', b'1', + 'admin', '2021-01-05 17:03:48', '1', '2024-02-29 08:57:36', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (111, 'MySQL 监控', '', 2, 1, 2740, 'druid', 'fa-solid:box', 'infra/druid/index', 'InfraDruid', 0, b'1', b'1', + b'1', 'admin', '2021-01-05 17:03:48', '1', '2024-04-23 00:05:58', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (112, 'Java 监控', '', 2, 3, 2740, 'admin-server', 'ep:coffee-cup', 'infra/server/index', 'InfraAdminServer', 0, + b'1', b'1', b'1', 'admin', '2021-01-05 17:03:48', '1', '2024-04-23 00:06:57', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (113, 'Redis 监控', '', 2, 2, 2740, 'redis', 'fa:reddit-square', 'infra/redis/index', 'InfraRedis', 0, b'1', + b'1', b'1', 'admin', '2021-01-05 17:03:48', '1', '2024-04-23 00:06:09', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (114, '表单构建', 'infra:build:list', 2, 2, 2, 'build', 'fa:wpforms', 'infra/build/index', 'InfraBuild', 0, b'1', + b'1', b'1', 'admin', '2021-01-05 17:03:48', '1', '2024-02-29 08:51:35', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (115, '代码生成', 'infra:codegen:query', 2, 1, 2, 'codegen', 'ep:document-copy', 'infra/codegen/index', + 'InfraCodegen', 0, b'1', b'1', b'1', 'admin', '2021-01-05 17:03:48', '1', '2024-02-29 08:51:06', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (500, '操作日志', '', 2, 1, 108, 'operate-log', 'ep:position', 'system/operatelog/index', 'SystemOperateLog', 0, + b'1', b'1', b'1', 'admin', '2021-01-05 17:03:48', '1', '2024-02-29 01:09:59', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (501, '登录日志', '', 2, 2, 108, 'login-log', 'ep:promotion', 'system/loginlog/index', 'SystemLoginLog', 0, b'1', + b'1', b'1', 'admin', '2021-01-05 17:03:48', '1', '2024-02-29 01:10:29', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (1001, '用户查询', 'system:user:query', 3, 1, 100, '', '#', '', NULL, 0, b'1', b'1', b'1', 'admin', + '2021-01-05 17:03:48', '', '2022-04-20 17:03:10', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (1002, '用户新增', 'system:user:create', 3, 2, 100, '', '', '', NULL, 0, b'1', b'1', b'1', 'admin', + '2021-01-05 17:03:48', '1', '2022-04-20 17:03:10', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (1003, '用户修改', 'system:user:update', 3, 3, 100, '', '', '', NULL, 0, b'1', b'1', b'1', 'admin', + '2021-01-05 17:03:48', '1', '2022-04-20 17:03:10', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (1004, '用户删除', 'system:user:delete', 3, 4, 100, '', '', '', NULL, 0, b'1', b'1', b'1', 'admin', + '2021-01-05 17:03:48', '1', '2022-04-20 17:03:10', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (1005, '用户导出', 'system:user:export', 3, 5, 100, '', '#', '', NULL, 0, b'1', b'1', b'1', 'admin', + '2021-01-05 17:03:48', '', '2022-04-20 17:03:10', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (1006, '用户导入', 'system:user:import', 3, 6, 100, '', '#', '', NULL, 0, b'1', b'1', b'1', 'admin', + '2021-01-05 17:03:48', '', '2022-04-20 17:03:10', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (1007, '重置密码', 'system:user:update-password', 3, 7, 100, '', '', '', NULL, 0, b'1', b'1', b'1', 'admin', + '2021-01-05 17:03:48', '1', '2022-04-20 17:03:10', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (1008, '角色查询', 'system:role:query', 3, 1, 101, '', '#', '', NULL, 0, b'1', b'1', b'1', 'admin', + '2021-01-05 17:03:48', '', '2022-04-20 17:03:10', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (1009, '角色新增', 'system:role:create', 3, 2, 101, '', '', '', NULL, 0, b'1', b'1', b'1', 'admin', + '2021-01-05 17:03:48', '1', '2022-04-20 17:03:10', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (1010, '角色修改', 'system:role:update', 3, 3, 101, '', '', '', NULL, 0, b'1', b'1', b'1', 'admin', + '2021-01-05 17:03:48', '1', '2022-04-20 17:03:10', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (1011, '角色删除', 'system:role:delete', 3, 4, 101, '', '', '', NULL, 0, b'1', b'1', b'1', 'admin', + '2021-01-05 17:03:48', '1', '2022-04-20 17:03:10', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (1012, '角色导出', 'system:role:export', 3, 5, 101, '', '#', '', NULL, 0, b'1', b'1', b'1', 'admin', + '2021-01-05 17:03:48', '', '2022-04-20 17:03:10', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (1013, '菜单查询', 'system:menu:query', 3, 1, 102, '', '#', '', NULL, 0, b'1', b'1', b'1', 'admin', + '2021-01-05 17:03:48', '', '2022-04-20 17:03:10', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (1014, '菜单新增', 'system:menu:create', 3, 2, 102, '', '#', '', NULL, 0, b'1', b'1', b'1', 'admin', + '2021-01-05 17:03:48', '', '2022-04-20 17:03:10', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (1015, '菜单修改', 'system:menu:update', 3, 3, 102, '', '#', '', NULL, 0, b'1', b'1', b'1', 'admin', + '2021-01-05 17:03:48', '', '2022-04-20 17:03:10', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (1016, '菜单删除', 'system:menu:delete', 3, 4, 102, '', '#', '', NULL, 0, b'1', b'1', b'1', 'admin', + '2021-01-05 17:03:48', '', '2022-04-20 17:03:10', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (1017, '部门查询', 'system:dept:query', 3, 1, 103, '', '#', '', NULL, 0, b'1', b'1', b'1', 'admin', + '2021-01-05 17:03:48', '', '2022-04-20 17:03:10', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (1018, '部门新增', 'system:dept:create', 3, 2, 103, '', '', '', NULL, 0, b'1', b'1', b'1', 'admin', + '2021-01-05 17:03:48', '1', '2022-04-20 17:03:10', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (1019, '部门修改', 'system:dept:update', 3, 3, 103, '', '', '', NULL, 0, b'1', b'1', b'1', 'admin', + '2021-01-05 17:03:48', '1', '2022-04-20 17:03:10', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (1020, '部门删除', 'system:dept:delete', 3, 4, 103, '', '', '', NULL, 0, b'1', b'1', b'1', 'admin', + '2021-01-05 17:03:48', '1', '2022-04-20 17:03:10', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (1021, '岗位查询', 'system:post:query', 3, 1, 104, '', '#', '', NULL, 0, b'1', b'1', b'1', 'admin', + '2021-01-05 17:03:48', '', '2022-04-20 17:03:10', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (1022, '岗位新增', 'system:post:create', 3, 2, 104, '', '', '', NULL, 0, b'1', b'1', b'1', 'admin', + '2021-01-05 17:03:48', '1', '2022-04-20 17:03:10', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (1023, '岗位修改', 'system:post:update', 3, 3, 104, '', '', '', NULL, 0, b'1', b'1', b'1', 'admin', + '2021-01-05 17:03:48', '1', '2022-04-20 17:03:10', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (1024, '岗位删除', 'system:post:delete', 3, 4, 104, '', '', '', NULL, 0, b'1', b'1', b'1', 'admin', + '2021-01-05 17:03:48', '1', '2022-04-20 17:03:10', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (1025, '岗位导出', 'system:post:export', 3, 5, 104, '', '#', '', NULL, 0, b'1', b'1', b'1', 'admin', + '2021-01-05 17:03:48', '', '2022-04-20 17:03:10', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (1026, '字典查询', 'system:dict:query', 3, 1, 105, '#', '#', '', NULL, 0, b'1', b'1', b'1', 'admin', + '2021-01-05 17:03:48', '', '2022-04-20 17:03:10', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (1027, '字典新增', 'system:dict:create', 3, 2, 105, '', '', '', NULL, 0, b'1', b'1', b'1', 'admin', + '2021-01-05 17:03:48', '1', '2022-04-20 17:03:10', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (1028, '字典修改', 'system:dict:update', 3, 3, 105, '', '', '', NULL, 0, b'1', b'1', b'1', 'admin', + '2021-01-05 17:03:48', '1', '2022-04-20 17:03:10', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (1029, '字典删除', 'system:dict:delete', 3, 4, 105, '', '', '', NULL, 0, b'1', b'1', b'1', 'admin', + '2021-01-05 17:03:48', '1', '2022-04-20 17:03:10', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (1030, '字典导出', 'system:dict:export', 3, 5, 105, '#', '#', '', NULL, 0, b'1', b'1', b'1', 'admin', + '2021-01-05 17:03:48', '', '2022-04-20 17:03:10', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (1031, '配置查询', 'infra:config:query', 3, 1, 106, '', '', '', NULL, 0, b'1', b'1', b'1', 'admin', + '2021-01-05 17:03:48', '', '2022-04-20 17:03:10', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (1032, '配置新增', 'infra:config:create', 3, 2, 106, '', '', '', NULL, 0, b'1', b'1', b'1', 'admin', + '2021-01-05 17:03:48', '1', '2022-04-20 17:03:10', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (1033, '配置修改', 'infra:config:update', 3, 3, 106, '', '', '', NULL, 0, b'1', b'1', b'1', 'admin', + '2021-01-05 17:03:48', '1', '2022-04-20 17:03:10', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (1034, '配置删除', 'infra:config:delete', 3, 4, 106, '', '', '', NULL, 0, b'1', b'1', b'1', 'admin', + '2021-01-05 17:03:48', '1', '2022-04-20 17:03:10', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (1035, '配置导出', 'infra:config:export', 3, 5, 106, '', '', '', NULL, 0, b'1', b'1', b'1', 'admin', + '2021-01-05 17:03:48', '', '2022-04-20 17:03:10', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (1036, '公告查询', 'system:notice:query', 3, 1, 107, '#', '#', '', NULL, 0, b'1', b'1', b'1', 'admin', + '2021-01-05 17:03:48', '', '2022-04-20 17:03:10', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (1037, '公告新增', 'system:notice:create', 3, 2, 107, '', '', '', NULL, 0, b'1', b'1', b'1', 'admin', + '2021-01-05 17:03:48', '1', '2022-04-20 17:03:10', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (1038, '公告修改', 'system:notice:update', 3, 3, 107, '', '', '', NULL, 0, b'1', b'1', b'1', 'admin', + '2021-01-05 17:03:48', '1', '2022-04-20 17:03:10', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (1039, '公告删除', 'system:notice:delete', 3, 4, 107, '', '', '', NULL, 0, b'1', b'1', b'1', 'admin', + '2021-01-05 17:03:48', '1', '2022-04-20 17:03:10', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (1040, '操作查询', 'system:operate-log:query', 3, 1, 500, '', '', '', NULL, 0, b'1', b'1', b'1', 'admin', + '2021-01-05 17:03:48', '', '2022-04-20 17:03:10', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (1042, '日志导出', 'system:operate-log:export', 3, 2, 500, '', '', '', NULL, 0, b'1', b'1', b'1', 'admin', + '2021-01-05 17:03:48', '', '2022-04-20 17:03:10', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (1043, '登录查询', 'system:login-log:query', 3, 1, 501, '#', '#', '', NULL, 0, b'1', b'1', b'1', 'admin', + '2021-01-05 17:03:48', '', '2022-04-20 17:03:10', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (1045, '日志导出', 'system:login-log:export', 3, 3, 501, '#', '#', '', NULL, 0, b'1', b'1', b'1', 'admin', + '2021-01-05 17:03:48', '', '2022-04-20 17:03:10', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (1046, '令牌列表', 'system:oauth2-token:page', 3, 1, 109, '', '', '', NULL, 0, b'1', b'1', b'1', 'admin', + '2021-01-05 17:03:48', '1', '2022-05-09 23:54:42', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (1048, '令牌删除', 'system:oauth2-token:delete', 3, 2, 109, '', '', '', NULL, 0, b'1', b'1', b'1', 'admin', + '2021-01-05 17:03:48', '1', '2022-05-09 23:54:53', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (1050, '任务新增', 'infra:job:create', 3, 2, 110, '', '', '', NULL, 0, b'1', b'1', b'1', 'admin', + '2021-01-05 17:03:48', '', '2022-04-20 17:03:10', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (1051, '任务修改', 'infra:job:update', 3, 3, 110, '', '', '', NULL, 0, b'1', b'1', b'1', 'admin', + '2021-01-05 17:03:48', '', '2022-04-20 17:03:10', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (1052, '任务删除', 'infra:job:delete', 3, 4, 110, '', '', '', NULL, 0, b'1', b'1', b'1', 'admin', + '2021-01-05 17:03:48', '', '2022-04-20 17:03:10', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (1053, '状态修改', 'infra:job:update', 3, 5, 110, '', '', '', NULL, 0, b'1', b'1', b'1', 'admin', + '2021-01-05 17:03:48', '', '2022-04-20 17:03:10', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (1054, '任务导出', 'infra:job:export', 3, 7, 110, '', '', '', NULL, 0, b'1', b'1', b'1', 'admin', + '2021-01-05 17:03:48', '', '2022-04-20 17:03:10', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (1056, '生成修改', 'infra:codegen:update', 3, 2, 115, '', '', '', NULL, 0, b'1', b'1', b'1', 'admin', + '2021-01-05 17:03:48', '1', '2022-04-20 17:03:10', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (1057, '生成删除', 'infra:codegen:delete', 3, 3, 115, '', '', '', NULL, 0, b'1', b'1', b'1', 'admin', + '2021-01-05 17:03:48', '1', '2022-04-20 17:03:10', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (1058, '导入代码', 'infra:codegen:create', 3, 2, 115, '', '', '', NULL, 0, b'1', b'1', b'1', 'admin', + '2021-01-05 17:03:48', '1', '2022-04-20 17:03:10', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (1059, '预览代码', 'infra:codegen:preview', 3, 4, 115, '', '', '', NULL, 0, b'1', b'1', b'1', 'admin', + '2021-01-05 17:03:48', '1', '2022-04-20 17:03:10', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (1060, '生成代码', 'infra:codegen:download', 3, 5, 115, '', '', '', NULL, 0, b'1', b'1', b'1', 'admin', + '2021-01-05 17:03:48', '1', '2022-04-20 17:03:10', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (1063, '设置角色菜单权限', 'system:permission:assign-role-menu', 3, 6, 101, '', '', '', NULL, 0, b'1', b'1', + b'1', '', '2021-01-06 17:53:44', '', '2022-04-20 17:03:10', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (1064, '设置角色数据权限', 'system:permission:assign-role-data-scope', 3, 7, 101, '', '', '', NULL, 0, b'1', + b'1', b'1', '', '2021-01-06 17:56:31', '', '2022-04-20 17:03:10', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (1065, '设置用户角色', 'system:permission:assign-user-role', 3, 8, 101, '', '', '', NULL, 0, b'1', b'1', b'1', + '', '2021-01-07 10:23:28', '', '2022-04-20 17:03:10', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (1066, '获得 Redis 监控信息', 'infra:redis:get-monitor-info', 3, 1, 113, '', '', '', NULL, 0, b'1', b'1', b'1', + '', '2021-01-26 01:02:31', '', '2022-04-20 17:03:10', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (1067, '获得 Redis Key 列表', 'infra:redis:get-key-list', 3, 2, 113, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2021-01-26 01:02:52', '', '2022-04-20 17:03:10', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (1070, '代码生成案例', '', 1, 1, 2, 'demo', 'ep:aim', 'infra/testDemo/index', NULL, 0, b'1', b'1', b'1', '', + '2021-02-06 12:42:49', '1', '2023-11-15 23:45:53', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (1075, '任务触发', 'infra:job:trigger', 3, 8, 110, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2021-02-07 13:03:10', '', '2022-04-20 17:03:10', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (1077, '链路追踪', '', 2, 4, 2740, 'skywalking', 'fa:eye', 'infra/skywalking/index', 'InfraSkyWalking', 0, b'1', + b'1', b'1', '', '2021-02-08 20:41:31', '1', '2024-04-23 00:07:15', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (1078, '访问日志', '', 2, 1, 1083, 'api-access-log', 'ep:place', 'infra/apiAccessLog/index', 'InfraApiAccessLog', + 0, b'1', b'1', b'1', '', '2021-02-26 01:32:59', '1', '2024-02-29 08:54:57', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (1082, '日志导出', 'infra:api-access-log:export', 3, 2, 1078, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2021-02-26 01:32:59', '1', '2022-04-20 17:03:10', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (1083, 'API 日志', '', 2, 4, 2, 'log', 'fa:tasks', NULL, NULL, 0, b'1', b'1', b'1', '', '2021-02-26 02:18:24', + '1', '2024-04-22 23:58:36', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (1084, '错误日志', 'infra:api-error-log:query', 2, 2, 1083, 'api-error-log', 'ep:warning-filled', + 'infra/apiErrorLog/index', 'InfraApiErrorLog', 0, b'1', b'1', b'1', '', '2021-02-26 07:53:20', '1', + '2024-02-29 08:55:17', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (1085, '日志处理', 'infra:api-error-log:update-status', 3, 2, 1084, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2021-02-26 07:53:20', '1', '2022-04-20 17:03:10', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (1086, '日志导出', 'infra:api-error-log:export', 3, 3, 1084, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2021-02-26 07:53:20', '1', '2022-04-20 17:03:10', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (1087, '任务查询', 'infra:job:query', 3, 1, 110, '', '', '', NULL, 0, b'1', b'1', b'1', '1', + '2021-03-10 01:26:19', '1', '2022-04-20 17:03:10', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (1088, '日志查询', 'infra:api-access-log:query', 3, 1, 1078, '', '', '', NULL, 0, b'1', b'1', b'1', '1', + '2021-03-10 01:28:04', '1', '2022-04-20 17:03:10', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (1089, '日志查询', 'infra:api-error-log:query', 3, 1, 1084, '', '', '', NULL, 0, b'1', b'1', b'1', '1', + '2021-03-10 01:29:09', '1', '2022-04-20 17:03:10', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (1090, '文件列表', '', 2, 5, 1243, 'file', 'ep:upload-filled', 'infra/file/index', 'InfraFile', 0, b'1', b'1', + b'1', '', '2021-03-12 20:16:20', '1', '2024-02-29 08:53:02', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (1091, '文件查询', 'infra:file:query', 3, 1, 1090, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2021-03-12 20:16:20', '', '2022-04-20 17:03:10', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (1092, '文件删除', 'infra:file:delete', 3, 4, 1090, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2021-03-12 20:16:20', '', '2022-04-20 17:03:10', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (1093, '短信管理', '', 1, 1, 2739, 'sms', 'ep:message', NULL, NULL, 0, b'1', b'1', b'1', '1', + '2021-04-05 01:10:16', '1', '2024-04-22 23:56:03', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (1094, '短信渠道', '', 2, 0, 1093, 'sms-channel', 'fa:stack-exchange', 'system/sms/channel/index', + 'SystemSmsChannel', 0, b'1', b'1', b'1', '', '2021-04-01 11:07:15', '1', '2024-02-29 01:15:54', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (1095, '短信渠道查询', 'system:sms-channel:query', 3, 1, 1094, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2021-04-01 11:07:15', '', '2022-04-20 17:03:10', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (1096, '短信渠道创建', 'system:sms-channel:create', 3, 2, 1094, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2021-04-01 11:07:15', '', '2022-04-20 17:03:10', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (1097, '短信渠道更新', 'system:sms-channel:update', 3, 3, 1094, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2021-04-01 11:07:15', '', '2022-04-20 17:03:10', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (1098, '短信渠道删除', 'system:sms-channel:delete', 3, 4, 1094, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2021-04-01 11:07:15', '', '2022-04-20 17:03:10', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (1100, '短信模板', '', 2, 1, 1093, 'sms-template', 'ep:connection', 'system/sms/template/index', + 'SystemSmsTemplate', 0, b'1', b'1', b'1', '', '2021-04-01 17:35:17', '1', '2024-02-29 01:16:18', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (1101, '短信模板查询', 'system:sms-template:query', 3, 1, 1100, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2021-04-01 17:35:17', '', '2022-04-20 17:03:10', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (1102, '短信模板创建', 'system:sms-template:create', 3, 2, 1100, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2021-04-01 17:35:17', '', '2022-04-20 17:03:10', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (1103, '短信模板更新', 'system:sms-template:update', 3, 3, 1100, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2021-04-01 17:35:17', '', '2022-04-20 17:03:10', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (1104, '短信模板删除', 'system:sms-template:delete', 3, 4, 1100, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2021-04-01 17:35:17', '', '2022-04-20 17:03:10', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (1105, '短信模板导出', 'system:sms-template:export', 3, 5, 1100, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2021-04-01 17:35:17', '', '2022-04-20 17:03:10', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (1106, '发送测试短信', 'system:sms-template:send-sms', 3, 6, 1100, '', '', '', NULL, 0, b'1', b'1', b'1', '1', + '2021-04-11 00:26:40', '1', '2022-04-20 17:03:10', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (1107, '短信日志', '', 2, 2, 1093, 'sms-log', 'fa:edit', 'system/sms/log/index', 'SystemSmsLog', 0, b'1', b'1', + b'1', '', '2021-04-11 08:37:05', '1', '2024-02-29 08:49:02', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (1108, '短信日志查询', 'system:sms-log:query', 3, 1, 1107, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2021-04-11 08:37:05', '', '2022-04-20 17:03:10', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (1109, '短信日志导出', 'system:sms-log:export', 3, 5, 1107, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2021-04-11 08:37:05', '', '2022-04-20 17:03:10', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (1117, '支付管理', '', 1, 30, 0, '/pay', 'ep:money', NULL, NULL, 0, b'1', b'1', b'1', '1', '2021-12-25 16:43:41', + '1', '2024-02-29 08:58:38', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (1118, '请假查询', '', 2, 0, 5, 'leave', 'fa:leanpub', 'bpm/oa/leave/index', 'BpmOALeave', 0, b'1', b'1', b'1', + '', '2021-09-20 08:51:03', '1', '2024-02-29 12:38:21', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (1119, '请假申请查询', 'bpm:oa-leave:query', 3, 1, 1118, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2021-09-20 08:51:03', '1', '2022-04-20 17:03:10', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (1120, '请假申请创建', 'bpm:oa-leave:create', 3, 2, 1118, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2021-09-20 08:51:03', '1', '2022-04-20 17:03:10', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (1126, '应用信息', '', 2, 1, 1117, 'app', 'fa:apple', 'pay/app/index', 'PayApp', 0, b'1', b'1', b'1', '', + '2021-11-10 01:13:30', '1', '2024-02-29 08:59:55', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (1127, '支付应用信息查询', 'pay:app:query', 3, 1, 1126, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2021-11-10 01:13:31', '', '2022-04-20 17:03:10', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (1128, '支付应用信息创建', 'pay:app:create', 3, 2, 1126, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2021-11-10 01:13:31', '', '2022-04-20 17:03:10', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (1129, '支付应用信息更新', 'pay:app:update', 3, 3, 1126, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2021-11-10 01:13:31', '', '2022-04-20 17:03:10', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (1130, '支付应用信息删除', 'pay:app:delete', 3, 4, 1126, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2021-11-10 01:13:31', '', '2022-04-20 17:03:10', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (1132, '秘钥解析', 'pay:channel:parsing', 3, 6, 1129, '', '', '', NULL, 0, b'1', b'1', b'1', '1', + '2021-11-08 15:15:47', '1', '2022-04-20 17:03:10', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (1133, '支付商户信息查询', 'pay:merchant:query', 3, 1, 1132, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2021-11-10 01:13:41', '', '2022-04-20 17:03:10', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (1134, '支付商户信息创建', 'pay:merchant:create', 3, 2, 1132, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2021-11-10 01:13:41', '', '2022-04-20 17:03:10', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (1135, '支付商户信息更新', 'pay:merchant:update', 3, 3, 1132, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2021-11-10 01:13:41', '', '2022-04-20 17:03:10', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (1136, '支付商户信息删除', 'pay:merchant:delete', 3, 4, 1132, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2021-11-10 01:13:41', '', '2022-04-20 17:03:10', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (1137, '支付商户信息导出', 'pay:merchant:export', 3, 5, 1132, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2021-11-10 01:13:41', '', '2022-04-20 17:03:10', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (1138, '租户列表', '', 2, 0, 1224, 'list', 'ep:house', 'system/tenant/index', 'SystemTenant', 0, b'1', b'1', + b'1', '', '2021-12-14 12:31:43', '1', '2024-02-29 01:01:10', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (1139, '租户查询', 'system:tenant:query', 3, 1, 1138, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2021-12-14 12:31:44', '', '2022-04-20 17:03:10', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (1140, '租户创建', 'system:tenant:create', 3, 2, 1138, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2021-12-14 12:31:44', '', '2022-04-20 17:03:10', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (1141, '租户更新', 'system:tenant:update', 3, 3, 1138, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2021-12-14 12:31:44', '', '2022-04-20 17:03:10', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (1142, '租户删除', 'system:tenant:delete', 3, 4, 1138, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2021-12-14 12:31:44', '', '2022-04-20 17:03:10', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (1143, '租户导出', 'system:tenant:export', 3, 5, 1138, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2021-12-14 12:31:44', '', '2022-04-20 17:03:10', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (1150, '秘钥解析', '', 3, 6, 1129, '', '', '', NULL, 0, b'1', b'1', b'1', '1', '2021-11-08 15:15:47', '1', + '2022-04-20 17:03:10', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (1161, '退款订单', '', 2, 3, 1117, 'refund', 'fa:registered', 'pay/refund/index', 'PayRefund', 0, b'1', b'1', + b'1', '', '2021-12-25 08:29:07', '1', '2024-02-29 08:59:20', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (1162, '退款订单查询', 'pay:refund:query', 3, 1, 1161, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2021-12-25 08:29:07', '', '2022-04-20 17:03:10', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (1163, '退款订单创建', 'pay:refund:create', 3, 2, 1161, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2021-12-25 08:29:07', '', '2022-04-20 17:03:10', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (1164, '退款订单更新', 'pay:refund:update', 3, 3, 1161, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2021-12-25 08:29:07', '', '2022-04-20 17:03:10', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (1165, '退款订单删除', 'pay:refund:delete', 3, 4, 1161, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2021-12-25 08:29:07', '', '2022-04-20 17:03:10', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (1166, '退款订单导出', 'pay:refund:export', 3, 5, 1161, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2021-12-25 08:29:07', '', '2022-04-20 17:03:10', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (1173, '支付订单', '', 2, 2, 1117, 'order', 'fa:cc-paypal', 'pay/order/index', 'PayOrder', 0, b'1', b'1', b'1', + '', '2021-12-25 08:49:43', '1', '2024-02-29 08:59:43', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (1174, '支付订单查询', 'pay:order:query', 3, 1, 1173, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2021-12-25 08:49:43', '', '2022-04-20 17:03:10', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (1175, '支付订单创建', 'pay:order:create', 3, 2, 1173, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2021-12-25 08:49:43', '', '2022-04-20 17:03:10', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (1176, '支付订单更新', 'pay:order:update', 3, 3, 1173, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2021-12-25 08:49:43', '', '2022-04-20 17:03:10', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (1177, '支付订单删除', 'pay:order:delete', 3, 4, 1173, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2021-12-25 08:49:43', '', '2022-04-20 17:03:10', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (1178, '支付订单导出', 'pay:order:export', 3, 5, 1173, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2021-12-25 08:49:43', '', '2022-04-20 17:03:10', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (1185, '工作流程', '', 1, 50, 0, '/bpm', 'fa:medium', NULL, NULL, 0, b'1', b'1', b'1', '1', + '2021-12-30 20:26:36', '1', '2024-02-29 12:43:43', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (1186, '流程管理', '', 1, 10, 1185, 'manager', 'fa:dedent', NULL, NULL, 0, b'1', b'1', b'1', '1', + '2021-12-30 20:28:30', '1', '2024-02-29 12:36:02', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (1187, '流程表单', '', 2, 2, 1186, 'form', 'fa:hdd-o', 'bpm/form/index', 'BpmForm', 0, b'1', b'1', b'1', '', + '2021-12-30 12:38:22', '1', '2024-03-19 12:25:25', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (1188, '表单查询', 'bpm:form:query', 3, 1, 1187, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2021-12-30 12:38:22', '1', '2022-04-20 17:03:10', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (1189, '表单创建', 'bpm:form:create', 3, 2, 1187, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2021-12-30 12:38:22', '1', '2022-04-20 17:03:10', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (1190, '表单更新', 'bpm:form:update', 3, 3, 1187, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2021-12-30 12:38:22', '1', '2022-04-20 17:03:10', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (1191, '表单删除', 'bpm:form:delete', 3, 4, 1187, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2021-12-30 12:38:22', '1', '2022-04-20 17:03:10', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (1192, '表单导出', 'bpm:form:export', 3, 5, 1187, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2021-12-30 12:38:22', '1', '2022-04-20 17:03:10', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (1193, '流程模型', '', 2, 1, 1186, 'model', 'fa-solid:project-diagram', 'bpm/model/index', 'BpmModel', 0, b'1', + b'1', b'1', '1', '2021-12-31 23:24:58', '1', '2024-03-19 12:25:19', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (1194, '模型查询', 'bpm:model:query', 3, 1, 1193, '', '', '', NULL, 0, b'1', b'1', b'1', '1', + '2022-01-03 19:01:10', '1', '2022-04-20 17:03:10', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (1195, '模型创建', 'bpm:model:create', 3, 2, 1193, '', '', '', NULL, 0, b'1', b'1', b'1', '1', + '2022-01-03 19:01:24', '1', '2022-04-20 17:03:10', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (1197, '模型更新', 'bpm:model:update', 3, 4, 1193, '', '', '', NULL, 0, b'1', b'1', b'1', '1', + '2022-01-03 19:02:28', '1', '2022-04-20 17:03:10', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (1198, '模型删除', 'bpm:model:delete', 3, 5, 1193, '', '', '', NULL, 0, b'1', b'1', b'1', '1', + '2022-01-03 19:02:43', '1', '2022-04-20 17:03:10', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (1199, '模型发布', 'bpm:model:deploy', 3, 6, 1193, '', '', '', NULL, 0, b'1', b'1', b'1', '1', + '2022-01-03 19:03:24', '1', '2022-04-20 17:03:10', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (1200, '审批中心', '', 2, 20, 1185, 'task', 'fa:tasks', NULL, NULL, 0, b'1', b'1', b'1', '1', + '2022-01-07 23:51:48', '1', '2024-03-21 00:33:15', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (1201, '我的流程', '', 2, 1, 1200, 'my', 'fa-solid:book', 'bpm/processInstance/index', 'BpmProcessInstanceMy', 0, + b'1', b'1', b'1', '', '2022-01-07 15:53:44', '1', '2024-03-21 23:52:12', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (1202, '流程实例的查询', 'bpm:process-instance:query', 3, 1, 1201, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2022-01-07 15:53:44', '1', '2022-04-20 17:03:10', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (1207, '待办任务', '', 2, 10, 1200, 'todo', 'fa:slack', 'bpm/task/todo/index', 'BpmTodoTask', 0, b'1', b'1', + b'1', '1', '2022-01-08 10:33:37', '1', '2024-02-29 12:37:39', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (1208, '已办任务', '', 2, 20, 1200, 'done', 'fa:delicious', 'bpm/task/done/index', 'BpmDoneTask', 0, b'1', b'1', + b'1', '1', '2022-01-08 10:34:13', '1', '2024-02-29 12:37:54', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (1209, '用户分组', '', 2, 4, 1186, 'user-group', 'fa:user-secret', 'bpm/group/index', 'BpmUserGroup', 0, b'1', + b'1', b'1', '', '2022-01-14 02:14:20', '1', '2024-03-21 23:55:29', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (1210, '用户组查询', 'bpm:user-group:query', 3, 1, 1209, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2022-01-14 02:14:20', '', '2022-04-20 17:03:10', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (1211, '用户组创建', 'bpm:user-group:create', 3, 2, 1209, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2022-01-14 02:14:20', '', '2022-04-20 17:03:10', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (1212, '用户组更新', 'bpm:user-group:update', 3, 3, 1209, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2022-01-14 02:14:20', '', '2022-04-20 17:03:10', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (1213, '用户组删除', 'bpm:user-group:delete', 3, 4, 1209, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2022-01-14 02:14:20', '', '2022-04-20 17:03:10', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (1215, '流程定义查询', 'bpm:process-definition:query', 3, 10, 1193, '', '', '', NULL, 0, b'1', b'1', b'1', '1', + '2022-01-23 00:21:43', '1', '2022-04-20 17:03:10', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (1216, '流程任务分配规则查询', 'bpm:task-assign-rule:query', 3, 20, 1193, '', '', '', NULL, 0, b'1', b'1', b'1', + '1', '2022-01-23 00:26:53', '1', '2022-04-20 17:03:10', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (1217, '流程任务分配规则创建', 'bpm:task-assign-rule:create', 3, 21, 1193, '', '', '', NULL, 0, b'1', b'1', b'1', + '1', '2022-01-23 00:28:15', '1', '2022-04-20 17:03:10', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (1218, '流程任务分配规则更新', 'bpm:task-assign-rule:update', 3, 22, 1193, '', '', '', NULL, 0, b'1', b'1', b'1', + '1', '2022-01-23 00:28:41', '1', '2022-04-20 17:03:10', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (1219, '流程实例的创建', 'bpm:process-instance:create', 3, 2, 1201, '', '', '', NULL, 0, b'1', b'1', b'1', '1', + '2022-01-23 00:36:15', '1', '2022-04-20 17:03:10', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (1220, '流程实例的取消', 'bpm:process-instance:cancel', 3, 3, 1201, '', '', '', NULL, 0, b'1', b'1', b'1', '1', + '2022-01-23 00:36:33', '1', '2022-04-20 17:03:10', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (1221, '流程任务的查询', 'bpm:task:query', 3, 1, 1207, '', '', '', NULL, 0, b'1', b'1', b'1', '1', + '2022-01-23 00:38:52', '1', '2022-04-20 17:03:10', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (1222, '流程任务的更新', 'bpm:task:update', 3, 2, 1207, '', '', '', NULL, 0, b'1', b'1', b'1', '1', + '2022-01-23 00:39:24', '1', '2022-04-20 17:03:10', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (1224, '租户管理', '', 2, 0, 1, 'tenant', 'fa-solid:house-user', NULL, NULL, 0, b'1', b'1', b'1', '1', + '2022-02-20 01:41:13', '1', '2024-02-29 00:59:29', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (1225, '租户套餐', '', 2, 0, 1224, 'package', 'fa:bars', 'system/tenantPackage/index', 'SystemTenantPackage', 0, + b'1', b'1', b'1', '', '2022-02-19 17:44:06', '1', '2024-02-29 01:01:43', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (1226, '租户套餐查询', 'system:tenant-package:query', 3, 1, 1225, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2022-02-19 17:44:06', '', '2022-04-20 17:03:10', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (1227, '租户套餐创建', 'system:tenant-package:create', 3, 2, 1225, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2022-02-19 17:44:06', '', '2022-04-20 17:03:10', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (1228, '租户套餐更新', 'system:tenant-package:update', 3, 3, 1225, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2022-02-19 17:44:06', '', '2022-04-20 17:03:10', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (1229, '租户套餐删除', 'system:tenant-package:delete', 3, 4, 1225, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2022-02-19 17:44:06', '', '2022-04-20 17:03:10', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (1237, '文件配置', '', 2, 0, 1243, 'file-config', 'fa-solid:file-signature', 'infra/fileConfig/index', + 'InfraFileConfig', 0, b'1', b'1', b'1', '', '2022-03-15 14:35:28', '1', '2024-02-29 08:52:54', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (1238, '文件配置查询', 'infra:file-config:query', 3, 1, 1237, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2022-03-15 14:35:28', '', '2022-04-20 17:03:10', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (1239, '文件配置创建', 'infra:file-config:create', 3, 2, 1237, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2022-03-15 14:35:28', '', '2022-04-20 17:03:10', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (1240, '文件配置更新', 'infra:file-config:update', 3, 3, 1237, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2022-03-15 14:35:28', '', '2022-04-20 17:03:10', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (1241, '文件配置删除', 'infra:file-config:delete', 3, 4, 1237, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2022-03-15 14:35:28', '', '2022-04-20 17:03:10', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (1242, '文件配置导出', 'infra:file-config:export', 3, 5, 1237, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2022-03-15 14:35:28', '', '2022-04-20 17:03:10', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (1243, '文件管理', '', 2, 6, 2, 'file', 'ep:files', NULL, '', 0, b'1', b'1', b'1', '1', '2022-03-16 23:47:40', + '1', '2024-04-23 00:02:11', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (1254, '作者动态', '', 1, 0, 0, 'https://www.iocoder.cn', 'ep:avatar', NULL, NULL, 0, b'1', b'1', b'1', '1', + '2022-04-23 01:03:15', '1', '2024-09-06 09:19:42', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (1255, '数据源配置', '', 2, 1, 2, 'data-source-config', 'ep:data-analysis', 'infra/dataSourceConfig/index', + 'InfraDataSourceConfig', 0, b'1', b'1', b'1', '', '2022-04-27 14:37:32', '1', '2024-02-29 08:51:25', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (1256, '数据源配置查询', 'infra:data-source-config:query', 3, 1, 1255, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2022-04-27 14:37:32', '', '2022-04-27 14:37:32', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (1257, '数据源配置创建', 'infra:data-source-config:create', 3, 2, 1255, '', '', '', NULL, 0, b'1', b'1', b'1', + '', '2022-04-27 14:37:32', '', '2022-04-27 14:37:32', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (1258, '数据源配置更新', 'infra:data-source-config:update', 3, 3, 1255, '', '', '', NULL, 0, b'1', b'1', b'1', + '', '2022-04-27 14:37:32', '', '2022-04-27 14:37:32', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (1259, '数据源配置删除', 'infra:data-source-config:delete', 3, 4, 1255, '', '', '', NULL, 0, b'1', b'1', b'1', + '', '2022-04-27 14:37:32', '', '2022-04-27 14:37:32', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (1260, '数据源配置导出', 'infra:data-source-config:export', 3, 5, 1255, '', '', '', NULL, 0, b'1', b'1', b'1', + '', '2022-04-27 14:37:32', '', '2022-04-27 14:37:32', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (1261, 'OAuth 2.0', '', 2, 10, 1, 'oauth2', 'fa:dashcube', NULL, NULL, 0, b'1', b'1', b'1', '1', + '2022-05-09 23:38:17', '1', '2024-02-29 01:12:08', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (1263, '应用管理', '', 2, 0, 1261, 'oauth2/application', 'fa:hdd-o', 'system/oauth2/client/index', + 'SystemOAuth2Client', 0, b'1', b'1', b'1', '', '2022-05-10 16:26:33', '1', '2024-02-29 01:13:14', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (1264, '客户端查询', 'system:oauth2-client:query', 3, 1, 1263, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2022-05-10 16:26:33', '1', '2022-05-11 00:31:06', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (1265, '客户端创建', 'system:oauth2-client:create', 3, 2, 1263, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2022-05-10 16:26:33', '1', '2022-05-11 00:31:23', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (1266, '客户端更新', 'system:oauth2-client:update', 3, 3, 1263, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2022-05-10 16:26:33', '1', '2022-05-11 00:31:28', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (1267, '客户端删除', 'system:oauth2-client:delete', 3, 4, 1263, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2022-05-10 16:26:33', '1', '2022-05-11 00:31:33', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (1281, '报表管理', '', 2, 40, 0, '/report', 'ep:pie-chart', NULL, NULL, 0, b'1', b'1', b'1', '1', + '2022-07-10 20:22:15', '1', '2024-02-29 12:33:03', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (1282, '报表设计器', '', 2, 1, 1281, 'jimu-report', 'ep:trend-charts', 'report/jmreport/index', 'GoView', 0, + b'1', b'1', b'1', '1', '2022-07-10 20:26:36', '1', '2024-02-29 12:33:54', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2000, '商品中心', '', 1, 60, 2362, 'product', 'fa:product-hunt', NULL, NULL, 0, b'1', b'1', b'1', '', + '2022-07-29 15:53:53', '1', '2023-09-30 11:52:36', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2002, '商品分类', '', 2, 2, 2000, 'category', 'ep:cellphone', 'mall/product/category/index', 'ProductCategory', + 0, b'1', b'1', b'1', '', '2022-07-29 15:53:53', '1', '2023-08-21 10:27:15', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2003, '分类查询', 'product:category:query', 3, 1, 2002, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2022-07-29 15:53:53', '', '2022-07-29 15:53:53', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2004, '分类创建', 'product:category:create', 3, 2, 2002, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2022-07-29 15:53:53', '', '2022-07-29 15:53:53', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2005, '分类更新', 'product:category:update', 3, 3, 2002, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2022-07-29 15:53:53', '', '2022-07-29 15:53:53', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2006, '分类删除', 'product:category:delete', 3, 4, 2002, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2022-07-29 15:53:53', '', '2022-07-29 15:53:53', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2008, '商品品牌', '', 2, 3, 2000, 'brand', 'ep:chicken', 'mall/product/brand/index', 'ProductBrand', 0, b'1', + b'1', b'1', '', '2022-07-30 13:52:44', '1', '2023-08-21 10:27:28', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2009, '品牌查询', 'product:brand:query', 3, 1, 2008, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2022-07-30 13:52:44', '', '2022-07-30 13:52:44', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2010, '品牌创建', 'product:brand:create', 3, 2, 2008, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2022-07-30 13:52:44', '', '2022-07-30 13:52:44', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2011, '品牌更新', 'product:brand:update', 3, 3, 2008, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2022-07-30 13:52:44', '', '2022-07-30 13:52:44', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2012, '品牌删除', 'product:brand:delete', 3, 4, 2008, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2022-07-30 13:52:44', '', '2022-07-30 13:52:44', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2014, '商品列表', '', 2, 1, 2000, 'spu', 'ep:apple', 'mall/product/spu/index', 'ProductSpu', 0, b'1', b'1', + b'1', '', '2022-07-30 14:22:58', '1', '2023-08-21 10:27:01', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2015, '商品查询', 'product:spu:query', 3, 1, 2014, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2022-07-30 14:22:58', '', '2022-07-30 14:22:58', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2016, '商品创建', 'product:spu:create', 3, 2, 2014, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2022-07-30 14:22:58', '', '2022-07-30 14:22:58', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2017, '商品更新', 'product:spu:update', 3, 3, 2014, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2022-07-30 14:22:58', '', '2022-07-30 14:22:58', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2018, '商品删除', 'product:spu:delete', 3, 4, 2014, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2022-07-30 14:22:58', '', '2022-07-30 14:22:58', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2019, '商品属性', '', 2, 4, 2000, 'property', 'ep:cold-drink', 'mall/product/property/index', 'ProductProperty', + 0, b'1', b'1', b'1', '', '2022-08-01 14:55:35', '1', '2023-08-26 11:01:05', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2020, '规格查询', 'product:property:query', 3, 1, 2019, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2022-08-01 14:55:35', '', '2022-12-12 20:26:24', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2021, '规格创建', 'product:property:create', 3, 2, 2019, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2022-08-01 14:55:35', '', '2022-12-12 20:26:30', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2022, '规格更新', 'product:property:update', 3, 3, 2019, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2022-08-01 14:55:35', '', '2022-12-12 20:26:33', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2023, '规格删除', 'product:property:delete', 3, 4, 2019, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2022-08-01 14:55:35', '', '2022-12-12 20:26:37', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2025, 'Banner', '', 2, 100, 2387, 'banner', 'fa:bandcamp', 'mall/promotion/banner/index', NULL, 0, b'1', b'1', + b'1', '', '2022-08-01 14:56:14', '1', '2023-10-24 20:20:06', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2026, 'Banner查询', 'promotion:banner:query', 3, 1, 2025, '', '', '', '', 0, b'1', b'1', b'1', '', + '2022-08-01 14:56:14', '1', '2023-10-24 20:20:18', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2027, 'Banner创建', 'promotion:banner:create', 3, 2, 2025, '', '', '', '', 0, b'1', b'1', b'1', '', + '2022-08-01 14:56:14', '1', '2023-10-24 20:20:23', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2028, 'Banner更新', 'promotion:banner:update', 3, 3, 2025, '', '', '', '', 0, b'1', b'1', b'1', '', + '2022-08-01 14:56:14', '1', '2023-10-24 20:20:28', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2029, 'Banner删除', 'promotion:banner:delete', 3, 4, 2025, '', '', '', '', 0, b'1', b'1', b'1', '', + '2022-08-01 14:56:14', '1', '2023-10-24 20:20:36', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2030, '营销中心', '', 1, 70, 2362, 'promotion', 'ep:present', NULL, NULL, 0, b'1', b'1', b'1', '1', + '2022-10-31 21:25:09', '1', '2023-09-30 11:54:27', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2032, '优惠劵列表', '', 2, 1, 2365, 'template', 'ep:discount', 'mall/promotion/coupon/template/index', + 'PromotionCouponTemplate', 0, b'1', b'1', b'1', '', '2022-10-31 22:27:14', '1', '2023-10-03 12:40:06', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2033, '优惠劵模板查询', 'promotion:coupon-template:query', 3, 1, 2032, '', '', '', NULL, 0, b'1', b'1', b'1', + '', '2022-10-31 22:27:14', '', '2022-10-31 22:27:14', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2034, '优惠劵模板创建', 'promotion:coupon-template:create', 3, 2, 2032, '', '', '', NULL, 0, b'1', b'1', b'1', + '', '2022-10-31 22:27:14', '', '2022-10-31 22:27:14', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2035, '优惠劵模板更新', 'promotion:coupon-template:update', 3, 3, 2032, '', '', '', NULL, 0, b'1', b'1', b'1', + '', '2022-10-31 22:27:14', '', '2022-10-31 22:27:14', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2036, '优惠劵模板删除', 'promotion:coupon-template:delete', 3, 4, 2032, '', '', '', NULL, 0, b'1', b'1', b'1', + '', '2022-10-31 22:27:14', '', '2022-10-31 22:27:14', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2038, '领取记录', '', 2, 2, 2365, 'list', 'ep:collection-tag', 'mall/promotion/coupon/index', 'PromotionCoupon', + 0, b'1', b'1', b'1', '', '2022-11-03 23:21:31', '1', '2023-10-03 12:55:30', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2039, '优惠劵查询', 'promotion:coupon:query', 3, 1, 2038, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2022-11-03 23:21:31', '', '2022-11-03 23:21:31', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2040, '优惠劵删除', 'promotion:coupon:delete', 3, 4, 2038, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2022-11-03 23:21:31', '', '2022-11-03 23:21:31', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2041, '满减送', '', 2, 10, 2390, 'reward-activity', 'ep:goblet-square-full', + 'mall/promotion/rewardActivity/index', 'PromotionRewardActivity', 0, b'1', b'1', b'1', '', + '2022-11-04 23:47:49', '1', '2023-10-21 19:24:46', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2042, '满减送活动查询', 'promotion:reward-activity:query', 3, 1, 2041, '', '', '', NULL, 0, b'1', b'1', b'1', + '', '2022-11-04 23:47:49', '', '2022-11-04 23:47:49', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2043, '满减送活动创建', 'promotion:reward-activity:create', 3, 2, 2041, '', '', '', NULL, 0, b'1', b'1', b'1', + '', '2022-11-04 23:47:49', '', '2022-11-04 23:47:49', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2044, '满减送活动更新', 'promotion:reward-activity:update', 3, 3, 2041, '', '', '', NULL, 0, b'1', b'1', b'1', + '', '2022-11-04 23:47:50', '', '2022-11-04 23:47:50', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2045, '满减送活动删除', 'promotion:reward-activity:delete', 3, 4, 2041, '', '', '', NULL, 0, b'1', b'1', b'1', + '', '2022-11-04 23:47:50', '', '2022-11-04 23:47:50', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2046, '满减送活动关闭', 'promotion:reward-activity:close', 3, 5, 2041, '', '', '', NULL, 0, b'1', b'1', b'1', + '1', '2022-11-05 10:42:53', '1', '2022-11-05 10:42:53', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2047, '限时折扣', '', 2, 7, 2390, 'discount-activity', 'ep:timer', 'mall/promotion/discountActivity/index', + 'PromotionDiscountActivity', 0, b'1', b'1', b'1', '', '2022-11-05 17:12:15', '1', '2023-10-21 19:24:21', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2048, '限时折扣活动查询', 'promotion:discount-activity:query', 3, 1, 2047, '', '', '', NULL, 0, b'1', b'1', + b'1', '', '2022-11-05 17:12:15', '', '2022-11-05 17:12:15', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2049, '限时折扣活动创建', 'promotion:discount-activity:create', 3, 2, 2047, '', '', '', NULL, 0, b'1', b'1', + b'1', '', '2022-11-05 17:12:15', '', '2022-11-05 17:12:15', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2050, '限时折扣活动更新', 'promotion:discount-activity:update', 3, 3, 2047, '', '', '', NULL, 0, b'1', b'1', + b'1', '', '2022-11-05 17:12:16', '', '2022-11-05 17:12:16', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2051, '限时折扣活动删除', 'promotion:discount-activity:delete', 3, 4, 2047, '', '', '', NULL, 0, b'1', b'1', + b'1', '', '2022-11-05 17:12:16', '', '2022-11-05 17:12:16', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2052, '限时折扣活动关闭', 'promotion:discount-activity:close', 3, 5, 2047, '', '', '', NULL, 0, b'1', b'1', + b'1', '', '2022-11-05 17:12:16', '', '2022-11-05 17:12:16', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2059, '秒杀商品', '', 2, 2, 2209, 'activity', 'ep:basketball', 'mall/promotion/seckill/activity/index', + 'PromotionSeckillActivity', 0, b'1', b'1', b'1', '', '2022-11-06 22:24:49', '1', '2023-06-24 18:57:25', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2060, '秒杀活动查询', 'promotion:seckill-activity:query', 3, 1, 2059, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2022-11-06 22:24:49', '', '2022-11-06 22:24:49', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2061, '秒杀活动创建', 'promotion:seckill-activity:create', 3, 2, 2059, '', '', '', NULL, 0, b'1', b'1', b'1', + '', '2022-11-06 22:24:49', '', '2022-11-06 22:24:49', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2062, '秒杀活动更新', 'promotion:seckill-activity:update', 3, 3, 2059, '', '', '', NULL, 0, b'1', b'1', b'1', + '', '2022-11-06 22:24:49', '', '2022-11-06 22:24:49', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2063, '秒杀活动删除', 'promotion:seckill-activity:delete', 3, 4, 2059, '', '', '', NULL, 0, b'1', b'1', b'1', + '', '2022-11-06 22:24:49', '', '2022-11-06 22:24:49', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2066, '秒杀时段', '', 2, 1, 2209, 'config', 'ep:baseball', 'mall/promotion/seckill/config/index', + 'PromotionSeckillConfig', 0, b'1', b'1', b'1', '', '2022-11-15 19:46:50', '1', '2023-06-24 18:57:14', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2067, '秒杀时段查询', 'promotion:seckill-config:query', 3, 1, 2066, '', '', '', '', 0, b'1', b'1', b'1', '', + '2022-11-15 19:46:51', '1', '2023-06-24 17:50:25', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2068, '秒杀时段创建', 'promotion:seckill-config:create', 3, 2, 2066, '', '', '', '', 0, b'1', b'1', b'1', '', + '2022-11-15 19:46:51', '1', '2023-06-24 17:48:39', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2069, '秒杀时段更新', 'promotion:seckill-config:update', 3, 3, 2066, '', '', '', '', 0, b'1', b'1', b'1', '', + '2022-11-15 19:46:51', '1', '2023-06-24 17:50:29', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2070, '秒杀时段删除', 'promotion:seckill-config:delete', 3, 4, 2066, '', '', '', '', 0, b'1', b'1', b'1', '', + '2022-11-15 19:46:51', '1', '2023-06-24 17:50:32', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2072, '订单中心', '', 1, 65, 2362, 'trade', 'ep:eleme', NULL, NULL, 0, b'1', b'1', b'1', '1', + '2022-11-19 18:57:19', '1', '2023-09-30 11:54:07', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2073, '售后退款', '', 2, 2, 2072, 'after-sale', 'ep:refrigerator', 'mall/trade/afterSale/index', + 'TradeAfterSale', 0, b'1', b'1', b'1', '', '2022-11-19 20:15:32', '1', '2023-10-01 21:42:21', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2074, '售后查询', 'trade:after-sale:query', 3, 1, 2073, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2022-11-19 20:15:33', '1', '2022-12-10 21:04:29', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2075, '秒杀活动关闭', 'promotion:seckill-activity:close', 3, 5, 2059, '', '', '', '', 0, b'1', b'1', b'1', '1', + '2022-11-28 20:20:15', '1', '2023-10-03 18:34:28', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2076, '订单列表', '', 2, 1, 2072, 'order', 'ep:list', 'mall/trade/order/index', 'TradeOrder', 0, b'1', b'1', + b'1', '1', '2022-12-10 21:05:44', '1', '2023-10-01 21:42:08', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2083, '地区管理', '', 2, 14, 1, 'area', 'fa:map-marker', 'system/area/index', 'SystemArea', 0, b'1', b'1', b'1', + '1', '2022-12-23 17:35:05', '1', '2024-02-29 08:50:28', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2084, '公众号管理', '', 1, 100, 0, '/mp', 'ep:compass', NULL, NULL, 0, b'1', b'1', b'1', '1', + '2023-01-01 20:11:04', '1', '2024-02-29 12:39:30', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2085, '账号管理', '', 2, 1, 2084, 'account', 'fa:user', 'mp/account/index', 'MpAccount', 0, b'1', b'1', b'1', + '1', '2023-01-01 20:13:31', '1', '2024-02-29 12:42:10', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2086, '新增账号', 'mp:account:create', 3, 1, 2085, '', '', '', NULL, 0, b'1', b'1', b'1', '1', + '2023-01-01 20:21:40', '1', '2023-01-07 17:32:53', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2087, '修改账号', 'mp:account:update', 3, 2, 2085, '', '', '', NULL, 0, b'1', b'1', b'1', '1', + '2023-01-07 17:32:46', '1', '2023-01-07 17:32:46', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2088, '查询账号', 'mp:account:query', 3, 0, 2085, '', '', '', NULL, 0, b'1', b'1', b'1', '1', + '2023-01-07 17:33:07', '1', '2023-01-07 17:33:07', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2089, '删除账号', 'mp:account:delete', 3, 3, 2085, '', '', '', NULL, 0, b'1', b'1', b'1', '1', + '2023-01-07 17:33:21', '1', '2023-01-07 17:33:21', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2090, '生成二维码', 'mp:account:qr-code', 3, 4, 2085, '', '', '', NULL, 0, b'1', b'1', b'1', '1', + '2023-01-07 17:33:58', '1', '2023-01-07 17:33:58', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2091, '清空 API 配额', 'mp:account:clear-quota', 3, 5, 2085, '', '', '', NULL, 0, b'1', b'1', b'1', '1', + '2023-01-07 18:20:32', '1', '2023-01-07 18:20:59', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2092, '数据统计', 'mp:statistics:query', 2, 2, 2084, 'statistics', 'ep:trend-charts', 'mp/statistics/index', + 'MpStatistics', 0, b'1', b'1', b'1', '1', '2023-01-07 20:17:36', '1', '2024-02-29 12:42:21', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2093, '标签管理', '', 2, 3, 2084, 'tag', 'ep:collection-tag', 'mp/tag/index', 'MpTag', 0, b'1', b'1', b'1', '1', + '2023-01-08 11:37:32', '1', '2024-02-29 12:42:29', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2094, '查询标签', 'mp:tag:query', 3, 0, 2093, '', '', '', NULL, 0, b'1', b'1', b'1', '1', '2023-01-08 11:59:03', + '1', '2023-01-08 11:59:03', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2095, '新增标签', 'mp:tag:create', 3, 1, 2093, '', '', '', NULL, 0, b'1', b'1', b'1', '1', + '2023-01-08 11:59:23', '1', '2023-01-08 11:59:23', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2096, '修改标签', 'mp:tag:update', 3, 2, 2093, '', '', '', NULL, 0, b'1', b'1', b'1', '1', + '2023-01-08 11:59:41', '1', '2023-01-08 11:59:41', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2097, '删除标签', 'mp:tag:delete', 3, 3, 2093, '', '', '', NULL, 0, b'1', b'1', b'1', '1', + '2023-01-08 12:00:04', '1', '2023-01-08 12:00:13', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2098, '同步标签', 'mp:tag:sync', 3, 4, 2093, '', '', '', NULL, 0, b'1', b'1', b'1', '1', '2023-01-08 12:00:29', + '1', '2023-01-08 12:00:29', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2099, '粉丝管理', '', 2, 4, 2084, 'user', 'fa:user-secret', 'mp/user/index', 'MpUser', 0, b'1', b'1', b'1', '1', + '2023-01-08 16:51:20', '1', '2024-02-29 12:42:39', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2100, '查询粉丝', 'mp:user:query', 3, 0, 2099, '', '', '', NULL, 0, b'1', b'1', b'1', '1', + '2023-01-08 17:16:59', '1', '2023-01-08 17:17:23', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2101, '修改粉丝', 'mp:user:update', 3, 1, 2099, '', '', '', NULL, 0, b'1', b'1', b'1', '1', + '2023-01-08 17:17:11', '1', '2023-01-08 17:17:11', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2102, '同步粉丝', 'mp:user:sync', 3, 2, 2099, '', '', '', NULL, 0, b'1', b'1', b'1', '1', '2023-01-08 17:17:40', + '1', '2023-01-08 17:17:40', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2103, '消息管理', '', 2, 5, 2084, 'message', 'ep:message', 'mp/message/index', 'MpMessage', 0, b'1', b'1', b'1', + '1', '2023-01-08 18:44:19', '1', '2024-02-29 12:42:50', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2104, '图文发表记录', '', 2, 10, 2084, 'free-publish', 'ep:edit-pen', 'mp/freePublish/index', 'MpFreePublish', + 0, b'1', b'1', b'1', '1', '2023-01-13 00:30:50', '1', '2024-02-29 12:43:31', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2105, '查询发布列表', 'mp:free-publish:query', 3, 1, 2104, '', '', '', NULL, 0, b'1', b'1', b'1', '1', + '2023-01-13 07:19:17', '1', '2023-01-13 07:19:17', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2106, '发布草稿', 'mp:free-publish:submit', 3, 2, 2104, '', '', '', NULL, 0, b'1', b'1', b'1', '1', + '2023-01-13 07:19:46', '1', '2023-01-13 07:19:46', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2107, '删除发布记录', 'mp:free-publish:delete', 3, 3, 2104, '', '', '', NULL, 0, b'1', b'1', b'1', '1', + '2023-01-13 07:20:01', '1', '2023-01-13 07:20:01', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2108, '图文草稿箱', '', 2, 9, 2084, 'draft', 'ep:edit', 'mp/draft/index', 'MpDraft', 0, b'1', b'1', b'1', '1', + '2023-01-13 07:40:21', '1', '2024-02-29 12:43:26', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2109, '新建草稿', 'mp:draft:create', 3, 1, 2108, '', '', '', NULL, 0, b'1', b'1', b'1', '1', + '2023-01-13 23:15:30', '1', '2023-01-13 23:15:44', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2110, '修改草稿', 'mp:draft:update', 3, 2, 2108, '', '', '', NULL, 0, b'1', b'1', b'1', '1', + '2023-01-14 10:08:47', '1', '2023-01-14 10:08:47', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2111, '查询草稿', 'mp:draft:query', 3, 0, 2108, '', '', '', NULL, 0, b'1', b'1', b'1', '1', + '2023-01-14 10:09:01', '1', '2023-01-14 10:09:01', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2112, '删除草稿', 'mp:draft:delete', 3, 3, 2108, '', '', '', NULL, 0, b'1', b'1', b'1', '1', + '2023-01-14 10:09:19', '1', '2023-01-14 10:09:19', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2113, '素材管理', '', 2, 8, 2084, 'material', 'ep:basketball', 'mp/material/index', 'MpMaterial', 0, b'1', b'1', + b'1', '1', '2023-01-14 14:12:07', '1', '2024-02-29 12:43:18', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2114, '上传临时素材', 'mp:material:upload-temporary', 3, 1, 2113, '', '', '', NULL, 0, b'1', b'1', b'1', '1', + '2023-01-14 15:33:55', '1', '2023-01-14 15:33:55', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2115, '上传永久素材', 'mp:material:upload-permanent', 3, 2, 2113, '', '', '', NULL, 0, b'1', b'1', b'1', '1', + '2023-01-14 15:34:14', '1', '2023-01-14 15:34:14', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2116, '删除素材', 'mp:material:delete', 3, 3, 2113, '', '', '', NULL, 0, b'1', b'1', b'1', '1', + '2023-01-14 15:35:37', '1', '2023-01-14 15:35:37', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2117, '上传图文图片', 'mp:material:upload-news-image', 3, 4, 2113, '', '', '', NULL, 0, b'1', b'1', b'1', '1', + '2023-01-14 15:36:31', '1', '2023-01-14 15:36:31', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2118, '查询素材', 'mp:material:query', 3, 5, 2113, '', '', '', NULL, 0, b'1', b'1', b'1', '1', + '2023-01-14 15:39:22', '1', '2023-01-14 15:39:22', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2119, '菜单管理', '', 2, 6, 2084, 'menu', 'ep:menu', 'mp/menu/index', 'MpMenu', 0, b'1', b'1', b'1', '1', + '2023-01-14 17:43:54', '1', '2024-02-29 12:42:56', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2120, '自动回复', '', 2, 7, 2084, 'auto-reply', 'fa-solid:republican', 'mp/autoReply/index', 'MpAutoReply', 0, + b'1', b'1', b'1', '1', '2023-01-15 22:13:09', '1', '2024-02-29 12:43:10', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2121, '查询回复', 'mp:auto-reply:query', 3, 0, 2120, '', '', '', NULL, 0, b'1', b'1', b'1', '1', + '2023-01-16 22:28:41', '1', '2023-01-16 22:28:41', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2122, '新增回复', 'mp:auto-reply:create', 3, 1, 2120, '', '', '', NULL, 0, b'1', b'1', b'1', '1', + '2023-01-16 22:28:54', '1', '2023-01-16 22:28:54', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2123, '修改回复', 'mp:auto-reply:update', 3, 2, 2120, '', '', '', NULL, 0, b'1', b'1', b'1', '1', + '2023-01-16 22:29:05', '1', '2023-01-16 22:29:05', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2124, '删除回复', 'mp:auto-reply:delete', 3, 3, 2120, '', '', '', NULL, 0, b'1', b'1', b'1', '1', + '2023-01-16 22:29:34', '1', '2023-01-16 22:29:34', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2125, '查询菜单', 'mp:menu:query', 3, 0, 2119, '', '', '', NULL, 0, b'1', b'1', b'1', '1', + '2023-01-17 23:05:41', '1', '2023-01-17 23:05:41', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2126, '保存菜单', 'mp:menu:save', 3, 1, 2119, '', '', '', NULL, 0, b'1', b'1', b'1', '1', '2023-01-17 23:06:01', + '1', '2023-01-17 23:06:01', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2127, '删除菜单', 'mp:menu:delete', 3, 2, 2119, '', '', '', NULL, 0, b'1', b'1', b'1', '1', + '2023-01-17 23:06:16', '1', '2023-01-17 23:06:16', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2128, '查询消息', 'mp:message:query', 3, 0, 2103, '', '', '', NULL, 0, b'1', b'1', b'1', '1', + '2023-01-17 23:07:14', '1', '2023-01-17 23:07:14', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2129, '发送消息', 'mp:message:send', 3, 1, 2103, '', '', '', NULL, 0, b'1', b'1', b'1', '1', + '2023-01-17 23:07:26', '1', '2023-01-17 23:07:26', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2130, '邮箱管理', '', 2, 2, 2739, 'mail', 'fa-solid:mail-bulk', NULL, NULL, 0, b'1', b'1', b'1', '1', + '2023-01-25 17:27:44', '1', '2024-04-22 23:56:08', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2131, '邮箱账号', '', 2, 0, 2130, 'mail-account', 'fa:universal-access', 'system/mail/account/index', + 'SystemMailAccount', 0, b'1', b'1', b'1', '', '2023-01-25 09:33:48', '1', '2024-02-29 08:48:16', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2132, '账号查询', 'system:mail-account:query', 3, 1, 2131, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2023-01-25 09:33:48', '', '2023-01-25 09:33:48', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2133, '账号创建', 'system:mail-account:create', 3, 2, 2131, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2023-01-25 09:33:48', '', '2023-01-25 09:33:48', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2134, '账号更新', 'system:mail-account:update', 3, 3, 2131, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2023-01-25 09:33:48', '', '2023-01-25 09:33:48', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2135, '账号删除', 'system:mail-account:delete', 3, 4, 2131, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2023-01-25 09:33:48', '', '2023-01-25 09:33:48', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2136, '邮件模版', '', 2, 0, 2130, 'mail-template', 'fa:tag', 'system/mail/template/index', 'SystemMailTemplate', + 0, b'1', b'1', b'1', '', '2023-01-25 12:05:31', '1', '2024-02-29 08:48:41', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2137, '模版查询', 'system:mail-template:query', 3, 1, 2136, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2023-01-25 12:05:31', '', '2023-01-25 12:05:31', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2138, '模版创建', 'system:mail-template:create', 3, 2, 2136, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2023-01-25 12:05:31', '', '2023-01-25 12:05:31', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2139, '模版更新', 'system:mail-template:update', 3, 3, 2136, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2023-01-25 12:05:31', '', '2023-01-25 12:05:31', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2140, '模版删除', 'system:mail-template:delete', 3, 4, 2136, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2023-01-25 12:05:31', '', '2023-01-25 12:05:31', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2141, '邮件记录', '', 2, 0, 2130, 'mail-log', 'fa:edit', 'system/mail/log/index', 'SystemMailLog', 0, b'1', + b'1', b'1', '', '2023-01-26 02:16:50', '1', '2024-02-29 08:48:51', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2142, '日志查询', 'system:mail-log:query', 3, 1, 2141, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2023-01-26 02:16:50', '', '2023-01-26 02:16:50', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2143, '发送测试邮件', 'system:mail-template:send-mail', 3, 5, 2136, '', '', '', NULL, 0, b'1', b'1', b'1', '1', + '2023-01-26 23:29:15', '1', '2023-01-26 23:29:15', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2144, '站内信管理', '', 1, 3, 2739, 'notify', 'ep:message-box', NULL, NULL, 0, b'1', b'1', b'1', '1', + '2023-01-28 10:25:18', '1', '2024-04-22 23:56:12', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2145, '模板管理', '', 2, 0, 2144, 'notify-template', 'fa:archive', 'system/notify/template/index', + 'SystemNotifyTemplate', 0, b'1', b'1', b'1', '', '2023-01-28 02:26:42', '1', '2024-02-29 08:49:14', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2146, '站内信模板查询', 'system:notify-template:query', 3, 1, 2145, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2023-01-28 02:26:42', '', '2023-01-28 02:26:42', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2147, '站内信模板创建', 'system:notify-template:create', 3, 2, 2145, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2023-01-28 02:26:42', '', '2023-01-28 02:26:42', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2148, '站内信模板更新', 'system:notify-template:update', 3, 3, 2145, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2023-01-28 02:26:42', '', '2023-01-28 02:26:42', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2149, '站内信模板删除', 'system:notify-template:delete', 3, 4, 2145, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2023-01-28 02:26:42', '', '2023-01-28 02:26:42', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2150, '发送测试站内信', 'system:notify-template:send-notify', 3, 5, 2145, '', '', '', NULL, 0, b'1', b'1', b'1', + '1', '2023-01-28 10:54:43', '1', '2023-01-28 10:54:43', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2151, '消息记录', '', 2, 0, 2144, 'notify-message', 'fa:edit', 'system/notify/message/index', + 'SystemNotifyMessage', 0, b'1', b'1', b'1', '', '2023-01-28 04:28:22', '1', '2024-02-29 08:49:22', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2152, '站内信消息查询', 'system:notify-message:query', 3, 1, 2151, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2023-01-28 04:28:22', '', '2023-01-28 04:28:22', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2153, '大屏设计器', '', 2, 2, 1281, 'go-view', 'fa:area-chart', 'report/goview/index', 'JimuReport', 0, b'1', + b'1', b'1', '1', '2023-02-07 00:03:19', '1', '2024-02-29 12:34:02', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2154, '创建项目', 'report:go-view-project:create', 3, 1, 2153, '', '', '', NULL, 0, b'1', b'1', b'1', '1', + '2023-02-07 19:25:14', '1', '2023-02-07 19:25:14', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2155, '更新项目', 'report:go-view-project:update', 3, 2, 2153, '', '', '', '', 0, b'1', b'1', b'1', '1', + '2023-02-07 19:25:34', '1', '2024-04-24 20:01:18', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2156, '查询项目', 'report:go-view-project:query', 3, 0, 2153, '', '', '', NULL, 0, b'1', b'1', b'1', '1', + '2023-02-07 19:25:53', '1', '2023-02-07 19:25:53', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2157, '使用 SQL 查询数据', 'report:go-view-data:get-by-sql', 3, 3, 2153, '', '', '', NULL, 0, b'1', b'1', b'1', + '1', '2023-02-07 19:26:15', '1', '2023-02-07 19:26:15', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2158, '使用 HTTP 查询数据', 'report:go-view-data:get-by-http', 3, 4, 2153, '', '', '', NULL, 0, b'1', b'1', + b'1', '1', '2023-02-07 19:26:35', '1', '2023-02-07 19:26:35', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2159, 'Boot 开发文档', '', 1, 1, 0, 'https://doc.iocoder.cn/', 'ep:document', NULL, NULL, 0, b'1', b'1', b'1', + '1', '2023-02-10 22:46:28', '1', '2024-07-28 11:36:48', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2160, 'Cloud 开发文档', '', 1, 2, 0, 'https://cloud.iocoder.cn', 'ep:document-copy', NULL, NULL, 0, b'1', b'1', + b'1', '1', '2023-02-10 22:47:07', '1', '2023-12-02 21:32:29', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2161, '接入示例', '', 1, 99, 1117, 'demo', 'fa-solid:dragon', 'pay/demo/index', NULL, 0, b'1', b'1', b'1', '', + '2023-02-11 14:21:42', '1', '2024-01-18 23:50:00', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2162, '商品导出', 'product:spu:export', 3, 5, 2014, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2022-07-30 14:22:58', '', '2022-07-30 14:22:58', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2164, '配送管理', '', 1, 3, 2072, 'delivery', 'ep:shopping-cart', '', '', 0, b'1', b'1', b'1', '1', + '2023-05-18 09:18:02', '1', '2023-09-28 10:58:09', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2165, '快递发货', '', 1, 0, 2164, 'express', 'ep:bicycle', '', '', 0, b'1', b'1', b'1', '1', + '2023-05-18 09:22:06', '1', '2023-08-30 21:02:49', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2166, '门店自提', '', 1, 1, 2164, 'pick-up-store', 'ep:add-location', '', '', 0, b'1', b'1', b'1', '1', + '2023-05-18 09:23:14', '1', '2023-08-30 21:03:21', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2167, '快递公司', '', 2, 0, 2165, 'express', 'ep:compass', 'mall/trade/delivery/express/index', 'Express', 0, + b'1', b'1', b'1', '1', '2023-05-18 09:27:21', '1', '2024-11-29 11:20:54', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2168, '快递公司查询', 'trade:delivery:express:query', 3, 1, 2167, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2023-05-18 09:37:53', '', '2023-05-18 09:37:53', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2169, '快递公司创建', 'trade:delivery:express:create', 3, 2, 2167, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2023-05-18 09:37:53', '', '2023-05-18 09:37:53', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2170, '快递公司更新', 'trade:delivery:express:update', 3, 3, 2167, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2023-05-18 09:37:53', '', '2023-05-18 09:37:53', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2171, '快递公司删除', 'trade:delivery:express:delete', 3, 4, 2167, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2023-05-18 09:37:53', '', '2023-05-18 09:37:53', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2172, '快递公司导出', 'trade:delivery:express:export', 3, 5, 2167, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2023-05-18 09:37:53', '', '2023-05-18 09:37:53', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2173, '运费模版', 'trade:delivery:express-template:query', 2, 1, 2165, 'express-template', 'ep:coordinate', + 'mall/trade/delivery/expressTemplate/index', 'ExpressTemplate', 0, b'1', b'1', b'1', '1', '2023-05-20 06:48:10', + '1', '2023-08-30 21:03:13', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2174, '快递运费模板查询', 'trade:delivery:express-template:query', 3, 1, 2173, '', '', '', NULL, 0, b'1', b'1', + b'1', '', '2023-05-20 06:49:53', '', '2023-05-20 06:49:53', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2175, '快递运费模板创建', 'trade:delivery:express-template:create', 3, 2, 2173, '', '', '', NULL, 0, b'1', b'1', + b'1', '', '2023-05-20 06:49:53', '', '2023-05-20 06:49:53', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2176, '快递运费模板更新', 'trade:delivery:express-template:update', 3, 3, 2173, '', '', '', NULL, 0, b'1', b'1', + b'1', '', '2023-05-20 06:49:53', '', '2023-05-20 06:49:53', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2177, '快递运费模板删除', 'trade:delivery:express-template:delete', 3, 4, 2173, '', '', '', NULL, 0, b'1', b'1', + b'1', '', '2023-05-20 06:49:53', '', '2023-05-20 06:49:53', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2178, '快递运费模板导出', 'trade:delivery:express-template:export', 3, 5, 2173, '', '', '', NULL, 0, b'1', b'1', + b'1', '', '2023-05-20 06:49:53', '', '2023-05-20 06:49:53', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2179, '门店管理', '', 2, 1, 2166, 'pick-up-store', 'ep:basketball', 'mall/trade/delivery/pickUpStore/index', + 'PickUpStore', 0, b'1', b'1', b'1', '1', '2023-05-25 10:50:00', '1', '2023-08-30 21:03:28', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2180, '自提门店查询', 'trade:delivery:pick-up-store:query', 3, 1, 2179, '', '', '', NULL, 0, b'1', b'1', b'1', + '', '2023-05-25 10:53:29', '', '2023-05-25 10:53:29', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2181, '自提门店创建', 'trade:delivery:pick-up-store:create', 3, 2, 2179, '', '', '', NULL, 0, b'1', b'1', b'1', + '', '2023-05-25 10:53:29', '', '2023-05-25 10:53:29', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2182, '自提门店更新', 'trade:delivery:pick-up-store:update', 3, 3, 2179, '', '', '', NULL, 0, b'1', b'1', b'1', + '', '2023-05-25 10:53:29', '', '2023-05-25 10:53:29', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2183, '自提门店删除', 'trade:delivery:pick-up-store:delete', 3, 4, 2179, '', '', '', NULL, 0, b'1', b'1', b'1', + '', '2023-05-25 10:53:29', '', '2023-05-25 10:53:29', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2184, '自提门店导出', 'trade:delivery:pick-up-store:export', 3, 5, 2179, '', '', '', NULL, 0, b'1', b'1', b'1', + '', '2023-05-25 10:53:29', '', '2023-05-25 10:53:29', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2209, '秒杀活动', '', 2, 3, 2030, 'seckill', 'ep:place', '', '', 0, b'1', b'1', b'1', '1', + '2023-06-24 17:39:13', '1', '2023-06-24 18:55:15', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2262, '会员中心', '', 1, 55, 0, '/member', 'ep:bicycle', NULL, NULL, 0, b'1', b'1', b'1', '1', + '2023-06-10 00:42:03', '1', '2023-08-20 09:23:56', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2275, '会员配置', '', 2, 0, 2262, 'config', 'fa:archive', 'member/config/index', 'MemberConfig', 0, b'1', b'1', + b'1', '', '2023-06-10 02:07:44', '1', '2023-10-01 23:41:29', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2276, '会员配置查询', 'member:config:query', 3, 1, 2275, '', '', '', '', 0, b'1', b'1', b'1', '', + '2023-06-10 02:07:44', '1', '2024-04-24 19:48:58', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2277, '会员配置保存', 'member:config:save', 3, 2, 2275, '', '', '', '', 0, b'1', b'1', b'1', '', + '2023-06-10 02:07:44', '1', '2024-04-24 19:49:28', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2281, '签到配置', '', 2, 2, 2300, 'config', 'ep:calendar', 'member/signin/config/index', 'SignInConfig', 0, + b'1', b'1', b'1', '', '2023-06-10 03:26:12', '1', '2023-08-20 19:25:51', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2282, '积分签到规则查询', 'point:sign-in-config:query', 3, 1, 2281, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2023-06-10 03:26:12', '', '2023-06-10 03:26:12', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2283, '积分签到规则创建', 'point:sign-in-config:create', 3, 2, 2281, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2023-06-10 03:26:12', '', '2023-06-10 03:26:12', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2284, '积分签到规则更新', 'point:sign-in-config:update', 3, 3, 2281, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2023-06-10 03:26:12', '', '2023-06-10 03:26:12', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2285, '积分签到规则删除', 'point:sign-in-config:delete', 3, 4, 2281, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2023-06-10 03:26:12', '', '2023-06-10 03:26:12', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2287, '会员积分', '', 2, 10, 2262, 'record', 'fa:asterisk', 'member/point/record/index', 'PointRecord', 0, b'1', + b'1', b'1', '', '2023-06-10 04:18:50', '1', '2023-10-01 23:42:11', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2288, '用户积分记录查询', 'point:record:query', 3, 1, 2287, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2023-06-10 04:18:50', '', '2023-06-10 04:18:50', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2293, '签到记录', '', 2, 3, 2300, 'record', 'ep:chicken', 'member/signin/record/index', 'SignInRecord', 0, b'1', + b'1', b'1', '', '2023-06-10 04:48:22', '1', '2023-08-20 19:26:02', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2294, '用户签到积分查询', 'point:sign-in-record:query', 3, 1, 2293, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2023-06-10 04:48:22', '', '2023-06-10 04:48:22', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2297, '用户签到积分删除', 'point:sign-in-record:delete', 3, 4, 2293, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2023-06-10 04:48:22', '', '2023-06-10 04:48:22', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2300, '会员签到', '', 1, 11, 2262, 'signin', 'ep:alarm-clock', '', '', 0, b'1', b'1', b'1', '1', + '2023-06-27 22:49:53', '1', '2023-08-20 09:23:48', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2301, '回调通知', '', 2, 5, 1117, 'notify', 'ep:mute-notification', 'pay/notify/index', 'PayNotify', 0, b'1', + b'1', b'1', '', '2023-07-20 04:41:32', '1', '2024-01-18 23:56:48', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2302, '支付通知查询', 'pay:notify:query', 3, 1, 2301, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2023-07-20 04:41:32', '', '2023-07-20 04:41:32', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2303, '拼团活动', '', 2, 3, 2030, 'combination', 'fa:group', '', '', 0, b'1', b'1', b'1', '1', + '2023-08-12 17:19:54', '1', '2023-08-12 17:20:05', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2304, '拼团商品', '', 2, 1, 2303, 'acitivity', 'ep:apple', 'mall/promotion/combination/activity/index', + 'PromotionCombinationActivity', 0, b'1', b'1', b'1', '1', '2023-08-12 17:22:03', '1', '2023-08-12 17:22:29', + b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2305, '拼团活动查询', 'promotion:combination-activity:query', 3, 1, 2304, '', '', '', '', 0, b'1', b'1', b'1', + '1', '2023-08-12 17:54:32', '1', '2023-11-24 11:57:40', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2306, '拼团活动创建', 'promotion:combination-activity:create', 3, 2, 2304, '', '', '', '', 0, b'1', b'1', b'1', + '1', '2023-08-12 17:54:49', '1', '2023-08-12 17:54:49', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2307, '拼团活动更新', 'promotion:combination-activity:update', 3, 3, 2304, '', '', '', '', 0, b'1', b'1', b'1', + '1', '2023-08-12 17:55:04', '1', '2023-08-12 17:55:04', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2308, '拼团活动删除', 'promotion:combination-activity:delete', 3, 4, 2304, '', '', '', '', 0, b'1', b'1', b'1', + '1', '2023-08-12 17:55:23', '1', '2023-08-12 17:55:23', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2309, '拼团活动关闭', 'promotion:combination-activity:close', 3, 5, 2304, '', '', '', '', 0, b'1', b'1', b'1', + '1', '2023-08-12 17:55:37', '1', '2023-10-06 10:51:57', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2310, '砍价活动', '', 2, 4, 2030, 'bargain', 'ep:box', '', '', 0, b'1', b'1', b'1', '1', '2023-08-13 00:27:25', + '1', '2023-08-13 00:27:25', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2311, '砍价商品', '', 2, 1, 2310, 'activity', 'ep:burger', 'mall/promotion/bargain/activity/index', + 'PromotionBargainActivity', 0, b'1', b'1', b'1', '1', '2023-08-13 00:28:49', '1', '2023-10-05 01:16:23', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2312, '砍价活动查询', 'promotion:bargain-activity:query', 3, 1, 2311, '', '', '', '', 0, b'1', b'1', b'1', '1', + '2023-08-13 00:32:30', '1', '2023-08-13 00:32:30', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2313, '砍价活动创建', 'promotion:bargain-activity:create', 3, 2, 2311, '', '', '', '', 0, b'1', b'1', b'1', '1', + '2023-08-13 00:32:44', '1', '2023-08-13 00:32:44', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2314, '砍价活动更新', 'promotion:bargain-activity:update', 3, 3, 2311, '', '', '', '', 0, b'1', b'1', b'1', '1', + '2023-08-13 00:32:55', '1', '2023-08-13 00:32:55', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2315, '砍价活动删除', 'promotion:bargain-activity:delete', 3, 4, 2311, '', '', '', '', 0, b'1', b'1', b'1', '1', + '2023-08-13 00:34:50', '1', '2023-08-13 00:34:50', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2316, '砍价活动关闭', 'promotion:bargain-activity:close', 3, 5, 2311, '', '', '', '', 0, b'1', b'1', b'1', '1', + '2023-08-13 00:35:02', '1', '2023-08-13 00:35:02', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2317, '会员管理', '', 2, 0, 2262, 'user', 'ep:avatar', 'member/user/index', 'MemberUser', 0, b'1', b'1', b'1', + '', '2023-08-19 04:12:15', '1', '2023-08-24 00:50:55', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2318, '会员用户查询', 'member:user:query', 3, 1, 2317, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2023-08-19 04:12:15', '', '2023-08-19 04:12:15', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2319, '会员用户更新', 'member:user:update', 3, 3, 2317, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2023-08-19 04:12:15', '', '2023-08-19 04:12:15', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2320, '会员标签', '', 2, 1, 2262, 'tag', 'ep:collection-tag', 'member/tag/index', 'MemberTag', 0, b'1', b'1', + b'1', '', '2023-08-20 01:03:08', '1', '2023-08-20 09:23:19', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2321, '会员标签查询', 'member:tag:query', 3, 1, 2320, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2023-08-20 01:03:08', '', '2023-08-20 01:03:08', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2322, '会员标签创建', 'member:tag:create', 3, 2, 2320, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2023-08-20 01:03:08', '', '2023-08-20 01:03:08', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2323, '会员标签更新', 'member:tag:update', 3, 3, 2320, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2023-08-20 01:03:08', '', '2023-08-20 01:03:08', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2324, '会员标签删除', 'member:tag:delete', 3, 4, 2320, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2023-08-20 01:03:08', '', '2023-08-20 01:03:08', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2325, '会员等级', '', 2, 2, 2262, 'level', 'fa:level-up', 'member/level/index', 'MemberLevel', 0, b'1', b'1', + b'1', '', '2023-08-22 12:41:01', '1', '2023-08-22 21:47:00', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2326, '会员等级查询', 'member:level:query', 3, 1, 2325, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2023-08-22 12:41:02', '', '2023-08-22 12:41:02', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2327, '会员等级创建', 'member:level:create', 3, 2, 2325, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2023-08-22 12:41:02', '', '2023-08-22 12:41:02', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2328, '会员等级更新', 'member:level:update', 3, 3, 2325, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2023-08-22 12:41:02', '', '2023-08-22 12:41:02', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2329, '会员等级删除', 'member:level:delete', 3, 4, 2325, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2023-08-22 12:41:02', '', '2023-08-22 12:41:02', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2330, '会员分组', '', 2, 3, 2262, 'group', 'fa:group', 'member/group/index', 'MemberGroup', 0, b'1', b'1', b'1', + '', '2023-08-22 13:50:06', '1', '2023-10-01 23:42:01', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2331, '用户分组查询', 'member:group:query', 3, 1, 2330, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2023-08-22 13:50:06', '', '2023-08-22 13:50:06', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2332, '用户分组创建', 'member:group:create', 3, 2, 2330, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2023-08-22 13:50:06', '', '2023-08-22 13:50:06', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2333, '用户分组更新', 'member:group:update', 3, 3, 2330, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2023-08-22 13:50:06', '', '2023-08-22 13:50:06', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2334, '用户分组删除', 'member:group:delete', 3, 4, 2330, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2023-08-22 13:50:06', '', '2023-08-22 13:50:06', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2335, '用户等级修改', 'member:user:update-level', 3, 5, 2317, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2023-08-23 16:49:05', '', '2023-08-23 16:50:48', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2336, '商品评论', '', 2, 5, 2000, 'comment', 'ep:comment', 'mall/product/comment/index', 'ProductComment', 0, + b'1', b'1', b'1', '1', '2023-08-26 11:03:00', '1', '2023-08-26 11:03:38', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2337, '评论查询', 'product:comment:query', 3, 1, 2336, '', '', '', '', 0, b'1', b'1', b'1', '1', + '2023-08-26 11:04:01', '1', '2023-08-26 11:04:01', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2338, '添加自评', 'product:comment:create', 3, 2, 2336, '', '', '', '', 0, b'1', b'1', b'1', '1', + '2023-08-26 11:04:23', '1', '2023-08-26 11:08:18', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2339, '商家回复', 'product:comment:update', 3, 3, 2336, '', '', '', '', 0, b'1', b'1', b'1', '1', + '2023-08-26 11:04:37', '1', '2023-08-26 11:04:37', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2340, '显隐评论', 'product:comment:update', 3, 4, 2336, '', '', '', '', 0, b'1', b'1', b'1', '1', + '2023-08-26 11:04:55', '1', '2023-08-26 11:04:55', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2341, '优惠劵发送', 'promotion:coupon:send', 3, 2, 2038, '', '', '', '', 0, b'1', b'1', b'1', '1', + '2023-09-02 00:03:14', '1', '2023-09-02 00:03:14', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2342, '交易配置', '', 2, 0, 2072, 'config', 'ep:setting', 'mall/trade/config/index', 'TradeConfig', 0, b'1', + b'1', b'1', '', '2023-09-28 02:46:22', '1', '2024-02-26 20:30:53', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2343, '交易中心配置查询', 'trade:config:query', 3, 1, 2342, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2023-09-28 02:46:22', '', '2023-09-28 02:46:22', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2344, '交易中心配置保存', 'trade:config:save', 3, 2, 2342, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2023-09-28 02:46:22', '', '2023-09-28 02:46:22', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2345, '分销管理', '', 1, 4, 2072, 'brokerage', 'fa-solid:project-diagram', '', '', 0, b'1', b'1', b'1', '', + '2023-09-28 02:46:22', '1', '2023-09-28 10:58:44', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2346, '分销用户', '', 2, 0, 2345, 'brokerage-user', 'fa-solid:user-tie', 'mall/trade/brokerage/user/index', + 'TradeBrokerageUser', 0, b'1', b'1', b'1', '', '2023-09-28 02:46:22', '1', '2024-02-26 20:33:23', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2347, '分销用户查询', 'trade:brokerage-user:query', 3, 1, 2346, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2023-09-28 02:46:22', '', '2023-09-28 02:46:22', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2348, '分销用户推广人查询', 'trade:brokerage-user:user-query', 3, 2, 2346, '', '', '', NULL, 0, b'1', b'1', + b'1', '', '2023-09-28 02:46:22', '', '2023-09-28 02:46:22', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2349, '分销用户推广订单查询', 'trade:brokerage-user:order-query', 3, 3, 2346, '', '', '', NULL, 0, b'1', b'1', + b'1', '', '2023-09-28 02:46:22', '', '2023-09-28 02:46:22', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2350, '分销用户修改推广资格', 'trade:brokerage-user:update-brokerage-enable', 3, 4, 2346, '', '', '', NULL, 0, + b'1', b'1', b'1', '', '2023-09-28 02:46:22', '', '2023-09-28 02:46:22', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2351, '修改推广员', 'trade:brokerage-user:update-bind-user', 3, 5, 2346, '', '', '', '', 0, b'1', b'1', b'1', + '', '2023-09-28 02:46:22', '1', '2024-12-01 14:33:07', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2352, '清除推广员', 'trade:brokerage-user:clear-bind-user', 3, 6, 2346, '', '', '', '', 0, b'1', b'1', b'1', '', + '2023-09-28 02:46:22', '1', '2024-12-01 14:33:14', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2353, '佣金记录', '', 2, 1, 2345, 'brokerage-record', 'fa:money', 'mall/trade/brokerage/record/index', + 'TradeBrokerageRecord', 0, b'1', b'1', b'1', '', '2023-09-28 02:46:22', '1', '2024-02-26 20:33:30', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2354, '佣金记录查询', 'trade:brokerage-record:query', 3, 1, 2353, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2023-09-28 02:46:22', '', '2023-09-28 02:46:22', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2355, '佣金提现', '', 2, 2, 2345, 'brokerage-withdraw', 'fa:credit-card', 'mall/trade/brokerage/withdraw/index', + 'TradeBrokerageWithdraw', 0, b'1', b'1', b'1', '', '2023-09-28 02:46:22', '1', '2024-02-26 20:33:35', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2356, '佣金提现查询', 'trade:brokerage-withdraw:query', 3, 1, 2355, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2023-09-28 02:46:22', '', '2023-09-28 02:46:22', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2357, '佣金提现审核', 'trade:brokerage-withdraw:audit', 3, 2, 2355, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2023-09-28 02:46:22', '', '2023-09-28 02:46:22', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2358, '统计中心', '', 1, 75, 2362, 'statistics', 'ep:data-line', '', '', 0, b'1', b'1', b'1', '', + '2023-09-30 03:22:40', '1', '2023-09-30 11:54:48', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2359, '交易统计', '', 2, 4, 2358, 'trade', 'fa-solid:credit-card', 'mall/statistics/trade/index', + 'TradeStatistics', 0, b'1', b'1', b'1', '', '2023-09-30 03:22:40', '1', '2024-02-26 20:42:00', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2360, '交易统计查询', 'statistics:trade:query', 3, 1, 2359, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2023-09-30 03:22:40', '', '2023-09-30 03:22:40', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2361, '交易统计导出', 'statistics:trade:export', 3, 2, 2359, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2023-09-30 03:22:40', '', '2023-09-30 03:22:40', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2362, '商城系统', '', 1, 59, 0, '/mall', 'ep:shop', '', '', 0, b'1', b'1', b'1', '1', '2023-09-30 11:52:02', + '1', '2023-09-30 11:52:18', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2363, '用户积分修改', 'member:user:update-point', 3, 6, 2317, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2023-10-01 14:39:43', '', '2023-10-01 14:39:43', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2364, '用户余额修改', 'pay:wallet:update-balance', 3, 7, 2317, '', '', '', '', 0, b'1', b'1', b'1', '', + '2023-10-01 14:39:43', '1', '2024-10-01 09:42:57', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2365, '优惠劵', '', 1, 2, 2030, 'coupon', 'fa-solid:disease', '', '', 0, b'1', b'1', b'1', '1', + '2023-10-03 12:39:15', '1', '2023-10-05 00:16:07', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2366, '砍价记录', '', 2, 2, 2310, 'record', 'ep:list', 'mall/promotion/bargain/record/index', + 'PromotionBargainRecord', 0, b'1', b'1', b'1', '', '2023-10-05 02:49:06', '1', '2023-10-05 10:50:38', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2367, '砍价记录查询', 'promotion:bargain-record:query', 3, 1, 2366, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2023-10-05 02:49:06', '', '2023-10-05 02:49:06', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2368, '助力记录查询', 'promotion:bargain-help:query', 3, 2, 2366, '', '', '', '', 0, b'1', b'1', b'1', '1', + '2023-10-05 12:27:49', '1', '2023-10-05 12:27:49', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2369, '拼团记录', 'promotion:combination-record:query', 2, 2, 2303, 'record', 'ep:avatar', + 'mall/promotion/combination/record/index.vue', 'PromotionCombinationRecord', 0, b'1', b'1', b'1', '1', + '2023-10-08 07:10:22', '1', '2023-10-08 07:34:11', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2374, '会员统计', '', 2, 2, 2358, 'member', 'ep:avatar', 'mall/statistics/member/index', 'MemberStatistics', 0, + b'1', b'1', b'1', '', '2023-10-11 04:39:24', '1', '2024-02-26 20:41:46', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2375, '会员统计查询', 'statistics:member:query', 3, 1, 2374, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2023-10-11 04:39:24', '', '2023-10-11 04:39:24', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2376, '订单核销', 'trade:order:pick-up', 3, 10, 2076, '', '', '', '', 0, b'1', b'1', b'1', '1', + '2023-10-14 17:11:58', '1', '2023-10-14 17:11:58', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2377, '文章分类', '', 2, 0, 2387, 'article/category', 'fa:certificate', 'mall/promotion/article/category/index', + 'ArticleCategory', 0, b'1', b'1', b'1', '', '2023-10-16 01:26:18', '1', '2023-10-16 09:38:26', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2378, '分类查询', 'promotion:article-category:query', 3, 1, 2377, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2023-10-16 01:26:18', '', '2023-10-16 01:26:18', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2379, '分类创建', 'promotion:article-category:create', 3, 2, 2377, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2023-10-16 01:26:18', '', '2023-10-16 01:26:18', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2380, '分类更新', 'promotion:article-category:update', 3, 3, 2377, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2023-10-16 01:26:18', '', '2023-10-16 01:26:18', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2381, '分类删除', 'promotion:article-category:delete', 3, 4, 2377, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2023-10-16 01:26:18', '', '2023-10-16 01:26:18', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2382, '文章列表', '', 2, 2, 2387, 'article', 'ep:connection', 'mall/promotion/article/index', 'Article', 0, + b'1', b'1', b'1', '', '2023-10-16 01:26:18', '1', '2023-10-16 09:41:19', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2383, '文章管理查询', 'promotion:article:query', 3, 1, 2382, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2023-10-16 01:26:18', '', '2023-10-16 01:26:18', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2384, '文章管理创建', 'promotion:article:create', 3, 2, 2382, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2023-10-16 01:26:18', '', '2023-10-16 01:26:18', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2385, '文章管理更新', 'promotion:article:update', 3, 3, 2382, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2023-10-16 01:26:18', '', '2023-10-16 01:26:18', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2386, '文章管理删除', 'promotion:article:delete', 3, 4, 2382, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2023-10-16 01:26:18', '', '2023-10-16 01:26:18', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2387, '内容管理', '', 1, 1, 2030, 'content', 'ep:collection', '', '', 0, b'1', b'1', b'1', '1', + '2023-10-16 09:37:31', '1', '2023-10-16 09:37:31', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2388, '商城首页', '', 2, 1, 2362, 'home', 'ep:home-filled', 'mall/home/index', 'MallHome', 0, b'1', b'1', b'1', + '', '2023-10-16 12:10:33', '', '2023-10-16 12:10:33', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2389, '核销订单', '', 2, 2, 2166, 'pick-up-order', 'ep:list', 'mall/trade/delivery/pickUpOrder/index', + 'PickUpOrder', 0, b'1', b'1', b'1', '', '2023-10-19 16:09:51', '', '2023-10-19 16:09:51', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2390, '优惠活动', '', 1, 99, 2030, 'youhui', 'ep:aim', '', '', 0, b'1', b'1', b'1', '1', '2023-10-21 19:23:49', + '1', '2023-10-21 19:23:49', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2391, '客户管理', '', 2, 10, 2397, 'customer', 'fa:address-book-o', 'crm/customer/index', 'CrmCustomer', 0, + b'1', b'1', b'1', '', '2023-10-29 09:04:21', '1', '2024-02-17 17:13:32', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2392, '客户查询', 'crm:customer:query', 3, 1, 2391, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2023-10-29 09:04:21', '', '2023-10-29 09:04:21', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2393, '客户创建', 'crm:customer:create', 3, 2, 2391, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2023-10-29 09:04:21', '', '2023-10-29 09:04:21', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2394, '客户更新', 'crm:customer:update', 3, 3, 2391, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2023-10-29 09:04:21', '', '2023-10-29 09:04:21', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2395, '客户删除', 'crm:customer:delete', 3, 4, 2391, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2023-10-29 09:04:21', '', '2023-10-29 09:04:21', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2396, '客户导出', 'crm:customer:export', 3, 5, 2391, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2023-10-29 09:04:21', '', '2023-10-29 09:04:21', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2397, 'CRM 系统', '', 1, 200, 0, '/crm', 'ep:avatar', '', '', 0, b'1', b'1', b'1', '1', '2023-10-29 17:08:30', + '1', '2024-02-04 15:37:31', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2398, '合同管理', '', 2, 50, 2397, 'contract', 'ep:notebook', 'crm/contract/index', 'CrmContract', 0, b'1', + b'1', b'1', '', '2023-10-29 10:50:41', '1', '2024-02-17 17:15:09', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2399, '合同查询', 'crm:contract:query', 3, 1, 2398, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2023-10-29 10:50:41', '', '2023-10-29 10:50:41', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2400, '合同创建', 'crm:contract:create', 3, 2, 2398, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2023-10-29 10:50:41', '', '2023-10-29 10:50:41', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2401, '合同更新', 'crm:contract:update', 3, 3, 2398, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2023-10-29 10:50:41', '', '2023-10-29 10:50:41', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2402, '合同删除', 'crm:contract:delete', 3, 4, 2398, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2023-10-29 10:50:41', '', '2023-10-29 10:50:41', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2403, '合同导出', 'crm:contract:export', 3, 5, 2398, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2023-10-29 10:50:41', '', '2023-10-29 10:50:41', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2404, '线索管理', '', 2, 8, 2397, 'clue', 'fa:pagelines', 'crm/clue/index', 'CrmClue', 0, b'1', b'1', b'1', '', + '2023-10-29 11:06:29', '1', '2024-02-17 17:15:41', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2405, '线索查询', 'crm:clue:query', 3, 1, 2404, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2023-10-29 11:06:29', '', '2023-10-29 11:06:29', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2406, '线索创建', 'crm:clue:create', 3, 2, 2404, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2023-10-29 11:06:29', '', '2023-10-29 11:06:29', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2407, '线索更新', 'crm:clue:update', 3, 3, 2404, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2023-10-29 11:06:29', '', '2023-10-29 11:06:29', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2408, '线索删除', 'crm:clue:delete', 3, 4, 2404, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2023-10-29 11:06:29', '', '2023-10-29 11:06:29', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2409, '线索导出', 'crm:clue:export', 3, 5, 2404, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2023-10-29 11:06:29', '', '2023-10-29 11:06:29', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2410, '商机管理', '', 2, 40, 2397, 'business', 'fa:bus', 'crm/business/index', 'CrmBusiness', 0, b'1', b'1', + b'1', '', '2023-10-29 11:12:35', '1', '2024-02-17 17:14:55', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2411, '商机查询', 'crm:business:query', 3, 1, 2410, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2023-10-29 11:12:35', '', '2023-10-29 11:12:35', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2412, '商机创建', 'crm:business:create', 3, 2, 2410, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2023-10-29 11:12:35', '', '2023-10-29 11:12:35', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2413, '商机更新', 'crm:business:update', 3, 3, 2410, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2023-10-29 11:12:35', '', '2023-10-29 11:12:35', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2414, '商机删除', 'crm:business:delete', 3, 4, 2410, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2023-10-29 11:12:35', '', '2023-10-29 11:12:35', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2415, '商机导出', 'crm:business:export', 3, 5, 2410, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2023-10-29 11:12:35', '', '2023-10-29 11:12:35', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2416, '联系人管理', '', 2, 20, 2397, 'contact', 'fa:address-book-o', 'crm/contact/index', 'CrmContact', 0, b'1', + b'1', b'1', '', '2023-10-29 11:14:56', '1', '2024-02-17 17:13:49', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2417, '联系人查询', 'crm:contact:query', 3, 1, 2416, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2023-10-29 11:14:56', '', '2023-10-29 11:14:56', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2418, '联系人创建', 'crm:contact:create', 3, 2, 2416, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2023-10-29 11:14:56', '', '2023-10-29 11:14:56', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2419, '联系人更新', 'crm:contact:update', 3, 3, 2416, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2023-10-29 11:14:56', '', '2023-10-29 11:14:56', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2420, '联系人删除', 'crm:contact:delete', 3, 4, 2416, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2023-10-29 11:14:56', '', '2023-10-29 11:14:56', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2421, '联系人导出', 'crm:contact:export', 3, 5, 2416, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2023-10-29 11:14:56', '', '2023-10-29 11:14:56', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2422, '回款管理', '', 2, 60, 2397, 'receivable', 'ep:money', 'crm/receivable/index', 'CrmReceivable', 0, b'1', + b'1', b'1', '', '2023-10-29 11:18:09', '1', '2024-02-17 17:16:18', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2423, '回款管理查询', 'crm:receivable:query', 3, 1, 2422, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2023-10-29 11:18:09', '', '2023-10-29 11:18:09', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2424, '回款管理创建', 'crm:receivable:create', 3, 2, 2422, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2023-10-29 11:18:09', '', '2023-10-29 11:18:09', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2425, '回款管理更新', 'crm:receivable:update', 3, 3, 2422, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2023-10-29 11:18:09', '', '2023-10-29 11:18:09', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2426, '回款管理删除', 'crm:receivable:delete', 3, 4, 2422, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2023-10-29 11:18:09', '', '2023-10-29 11:18:09', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2427, '回款管理导出', 'crm:receivable:export', 3, 5, 2422, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2023-10-29 11:18:09', '', '2023-10-29 11:18:09', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2428, '回款计划', '', 2, 61, 2397, 'receivable-plan', 'fa:money', 'crm/receivable/plan/index', + 'CrmReceivablePlan', 0, b'1', b'1', b'1', '', '2023-10-29 11:18:09', '1', '2024-02-17 17:16:11', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2429, '回款计划查询', 'crm:receivable-plan:query', 3, 1, 2428, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2023-10-29 11:18:09', '', '2023-10-29 11:18:09', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2430, '回款计划创建', 'crm:receivable-plan:create', 3, 2, 2428, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2023-10-29 11:18:09', '', '2023-10-29 11:18:09', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2431, '回款计划更新', 'crm:receivable-plan:update', 3, 3, 2428, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2023-10-29 11:18:09', '', '2023-10-29 11:18:09', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2432, '回款计划删除', 'crm:receivable-plan:delete', 3, 4, 2428, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2023-10-29 11:18:09', '', '2023-10-29 11:18:09', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2433, '回款计划导出', 'crm:receivable-plan:export', 3, 5, 2428, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2023-10-29 11:18:09', '', '2023-10-29 11:18:09', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2435, '商城装修', '', 2, 20, 2030, 'diy-template', 'fa6-solid:brush', 'mall/promotion/diy/template/index', + 'DiyTemplate', 0, b'1', b'1', b'1', '', '2023-10-29 14:19:25', '', '2023-10-29 14:19:25', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2436, '装修模板', '', 2, 1, 2435, 'diy-template', 'fa6-solid:brush', 'mall/promotion/diy/template/index', + 'DiyTemplate', 0, b'1', b'1', b'1', '', '2023-10-29 14:19:25', '', '2023-10-29 14:19:25', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2437, '装修模板查询', 'promotion:diy-template:query', 3, 1, 2436, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2023-10-29 14:19:25', '', '2023-10-29 14:19:25', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2438, '装修模板创建', 'promotion:diy-template:create', 3, 2, 2436, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2023-10-29 14:19:25', '', '2023-10-29 14:19:25', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2439, '装修模板更新', 'promotion:diy-template:update', 3, 3, 2436, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2023-10-29 14:19:25', '', '2023-10-29 14:19:25', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2440, '装修模板删除', 'promotion:diy-template:delete', 3, 4, 2436, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2023-10-29 14:19:25', '', '2023-10-29 14:19:25', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2441, '装修模板使用', 'promotion:diy-template:use', 3, 5, 2436, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2023-10-29 14:19:25', '', '2023-10-29 14:19:25', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2442, '装修页面', '', 2, 2, 2435, 'diy-page', 'foundation:page-edit', 'mall/promotion/diy/page/index', + 'DiyPage', 0, b'1', b'1', b'1', '', '2023-10-29 14:19:25', '', '2023-10-29 14:19:25', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2443, '装修页面查询', 'promotion:diy-page:query', 3, 1, 2442, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2023-10-29 14:19:25', '', '2023-10-29 14:19:25', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2444, '装修页面创建', 'promotion:diy-page:create', 3, 2, 2442, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2023-10-29 14:19:26', '', '2023-10-29 14:19:26', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2445, '装修页面更新', 'promotion:diy-page:update', 3, 3, 2442, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2023-10-29 14:19:26', '', '2023-10-29 14:19:26', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2446, '装修页面删除', 'promotion:diy-page:delete', 3, 4, 2442, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2023-10-29 14:19:26', '', '2023-10-29 14:19:26', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2447, '三方登录', '', 1, 10, 1, 'social', 'fa:rocket', '', '', 0, b'1', b'1', b'1', '1', '2023-11-04 12:12:01', + '1', '2024-02-29 01:14:05', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2448, '三方应用', '', 2, 1, 2447, 'client', 'ep:set-up', 'system/social/client/index.vue', 'SocialClient', 0, + b'1', b'1', b'1', '1', '2023-11-04 12:17:19', '1', '2024-05-04 19:09:54', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2449, '三方应用查询', 'system:social-client:query', 3, 1, 2448, '', '', '', '', 0, b'1', b'1', b'1', '1', + '2023-11-04 12:43:12', '1', '2023-11-04 12:43:33', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2450, '三方应用创建', 'system:social-client:create', 3, 2, 2448, '', '', '', '', 0, b'1', b'1', b'1', '1', + '2023-11-04 12:43:58', '1', '2023-11-04 12:43:58', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2451, '三方应用更新', 'system:social-client:update', 3, 3, 2448, '', '', '', '', 0, b'1', b'1', b'1', '1', + '2023-11-04 12:44:27', '1', '2023-11-04 12:44:27', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2452, '三方应用删除', 'system:social-client:delete', 3, 4, 2448, '', '', '', '', 0, b'1', b'1', b'1', '1', + '2023-11-04 12:44:43', '1', '2023-11-04 12:44:43', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2453, '三方用户', 'system:social-user:query', 2, 2, 2447, 'user', 'ep:avatar', 'system/social/user/index.vue', + 'SocialUser', 0, b'1', b'1', b'1', '1', '2023-11-04 14:01:05', '1', '2023-11-04 14:01:05', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2472, '主子表(内嵌)', '', 2, 12, 1070, 'demo03-inner', 'fa:power-off', 'infra/demo/demo03/inner/index', + 'Demo03StudentInner', 0, b'1', b'1', b'1', '', '2023-11-13 04:39:51', '1', '2023-11-16 23:53:46', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2478, '单表(增删改查)', '', 2, 1, 1070, 'demo01-contact', 'ep:bicycle', 'infra/demo/demo01/index', + 'Demo01Contact', 0, b'1', b'1', b'1', '', '2023-11-15 14:42:30', '1', '2023-11-16 20:34:40', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2479, '示例联系人查询', 'infra:demo01-contact:query', 3, 1, 2478, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2023-11-15 14:42:30', '', '2023-11-15 14:42:30', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2480, '示例联系人创建', 'infra:demo01-contact:create', 3, 2, 2478, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2023-11-15 14:42:30', '', '2023-11-15 14:42:30', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2481, '示例联系人更新', 'infra:demo01-contact:update', 3, 3, 2478, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2023-11-15 14:42:30', '', '2023-11-15 14:42:30', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2482, '示例联系人删除', 'infra:demo01-contact:delete', 3, 4, 2478, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2023-11-15 14:42:30', '', '2023-11-15 14:42:30', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2483, '示例联系人导出', 'infra:demo01-contact:export', 3, 5, 2478, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2023-11-15 14:42:30', '', '2023-11-15 14:42:30', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2484, '树表(增删改查)', '', 2, 2, 1070, 'demo02-category', 'fa:tree', 'infra/demo/demo02/index', + 'Demo02Category', 0, b'1', b'1', b'1', '', '2023-11-16 12:18:27', '1', '2023-11-16 20:35:01', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2485, '示例分类查询', 'infra:demo02-category:query', 3, 1, 2484, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2023-11-16 12:18:27', '', '2023-11-16 12:18:27', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2486, '示例分类创建', 'infra:demo02-category:create', 3, 2, 2484, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2023-11-16 12:18:27', '', '2023-11-16 12:18:27', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2487, '示例分类更新', 'infra:demo02-category:update', 3, 3, 2484, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2023-11-16 12:18:27', '', '2023-11-16 12:18:27', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2488, '示例分类删除', 'infra:demo02-category:delete', 3, 4, 2484, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2023-11-16 12:18:27', '', '2023-11-16 12:18:27', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2489, '示例分类导出', 'infra:demo02-category:export', 3, 5, 2484, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2023-11-16 12:18:27', '', '2023-11-16 12:18:27', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2490, '主子表(标准)', '', 2, 10, 1070, 'demo03-normal', 'fa:battery-3', 'infra/demo/demo03/normal/index', + 'Demo03StudentNormal', 0, b'1', b'1', b'1', '', '2023-11-16 12:53:37', '1', '2023-11-16 23:10:03', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2491, '学生查询', 'infra:demo03-student:query', 3, 1, 2490, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2023-11-16 12:53:37', '', '2023-11-16 12:53:37', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2492, '学生创建', 'infra:demo03-student:create', 3, 2, 2490, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2023-11-16 12:53:37', '', '2023-11-16 12:53:37', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2493, '学生更新', 'infra:demo03-student:update', 3, 3, 2490, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2023-11-16 12:53:37', '', '2023-11-16 12:53:37', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2494, '学生删除', 'infra:demo03-student:delete', 3, 4, 2490, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2023-11-16 12:53:37', '', '2023-11-16 12:53:37', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2495, '学生导出', 'infra:demo03-student:export', 3, 5, 2490, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2023-11-16 12:53:37', '', '2023-11-16 12:53:37', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2497, '主子表(ERP)', '', 2, 11, 1070, 'demo03-erp', 'ep:calendar', 'infra/demo/demo03/erp/index', + 'Demo03StudentERP', 0, b'1', b'1', b'1', '', '2023-11-16 15:50:59', '1', '2023-11-17 13:19:56', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2516, '客户公海配置', '', 2, 0, 2524, 'customer-pool-config', 'ep:data-analysis', + 'crm/customer/poolConfig/index', 'CrmCustomerPoolConfig', 0, b'1', b'1', b'1', '', '2023-11-18 13:33:31', '1', + '2024-01-03 19:52:06', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2517, '客户公海配置保存', 'crm:customer-pool-config:update', 3, 1, 2516, '', '', '', NULL, 0, b'1', b'1', b'1', + '', '2023-11-18 13:33:31', '', '2023-11-18 13:33:31', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2518, '客户限制配置', '', 2, 1, 2524, 'customer-limit-config', 'ep:avatar', 'crm/customer/limitConfig/index', + 'CrmCustomerLimitConfig', 0, b'1', b'1', b'1', '', '2023-11-18 13:33:53', '1', '2024-02-24 16:43:33', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2519, '客户限制配置查询', 'crm:customer-limit-config:query', 3, 1, 2518, '', '', '', NULL, 0, b'1', b'1', b'1', + '', '2023-11-18 13:33:53', '', '2023-11-18 13:33:53', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2520, '客户限制配置创建', 'crm:customer-limit-config:create', 3, 2, 2518, '', '', '', NULL, 0, b'1', b'1', b'1', + '', '2023-11-18 13:33:53', '', '2023-11-18 13:33:53', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2521, '客户限制配置更新', 'crm:customer-limit-config:update', 3, 3, 2518, '', '', '', NULL, 0, b'1', b'1', b'1', + '', '2023-11-18 13:33:53', '', '2023-11-18 13:33:53', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2522, '客户限制配置删除', 'crm:customer-limit-config:delete', 3, 4, 2518, '', '', '', NULL, 0, b'1', b'1', b'1', + '', '2023-11-18 13:33:53', '', '2023-11-18 13:33:53', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2523, '客户限制配置导出', 'crm:customer-limit-config:export', 3, 5, 2518, '', '', '', NULL, 0, b'1', b'1', b'1', + '', '2023-11-18 13:33:53', '', '2023-11-18 13:33:53', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2524, '系统配置', '', 1, 999, 2397, 'config', 'ep:connection', '', '', 0, b'1', b'1', b'1', '1', + '2023-11-18 21:58:00', '1', '2024-02-17 17:14:34', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2525, 'WebSocket', '', 2, 5, 2, 'websocket', 'ep:connection', 'infra/webSocket/index', 'InfraWebSocket', 0, + b'1', b'1', b'1', '1', '2023-11-23 19:41:55', '1', '2024-04-23 00:02:00', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2526, '产品管理', '', 2, 80, 2397, 'product', 'fa:product-hunt', 'crm/product/index', 'CrmProduct', 0, b'1', + b'1', b'1', '1', '2023-12-05 22:45:26', '1', '2024-02-20 20:36:20', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2527, '产品查询', 'crm:product:query', 3, 1, 2526, '', '', '', '', 0, b'1', b'1', b'1', '1', + '2023-12-05 22:47:16', '1', '2023-12-05 22:47:16', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2528, '产品创建', 'crm:product:create', 3, 2, 2526, '', '', '', '', 0, b'1', b'1', b'1', '1', + '2023-12-05 22:47:41', '1', '2023-12-05 22:47:48', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2529, '产品更新', 'crm:product:update', 3, 3, 2526, '', '', '', '', 0, b'1', b'1', b'1', '1', + '2023-12-05 22:48:03', '1', '2023-12-05 22:48:03', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2530, '产品删除', 'crm:product:delete', 3, 4, 2526, '', '', '', '', 0, b'1', b'1', b'1', '1', + '2023-12-05 22:48:17', '1', '2023-12-05 22:48:17', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2531, '产品导出', 'crm:product:export', 3, 5, 2526, '', '', '', '', 0, b'1', b'1', b'1', '1', + '2023-12-05 22:48:29', '1', '2023-12-05 22:48:29', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2532, '产品分类配置', '', 2, 3, 2524, 'product/category', 'fa-solid:window-restore', + 'crm/product/category/index', 'CrmProductCategory', 0, b'1', b'1', b'1', '1', '2023-12-06 12:52:36', '1', + '2023-12-06 12:52:51', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2533, '产品分类查询', 'crm:product-category:query', 3, 1, 2532, '', '', '', '', 0, b'1', b'1', b'1', '1', + '2023-12-06 12:53:23', '1', '2023-12-06 12:53:23', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2534, '产品分类创建', 'crm:product-category:create', 3, 2, 2532, '', '', '', '', 0, b'1', b'1', b'1', '1', + '2023-12-06 12:53:41', '1', '2023-12-06 12:53:41', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2535, '产品分类更新', 'crm:product-category:update', 3, 3, 2532, '', '', '', '', 0, b'1', b'1', b'1', '1', + '2023-12-06 12:53:59', '1', '2023-12-06 12:53:59', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2536, '产品分类删除', 'crm:product-category:delete', 3, 4, 2532, '', '', '', '', 0, b'1', b'1', b'1', '1', + '2023-12-06 12:54:14', '1', '2023-12-06 12:54:14', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2543, '关联商机', 'crm:contact:create-business', 3, 10, 2416, '', '', '', '', 0, b'1', b'1', b'1', '1', + '2024-01-02 17:28:25', '1', '2024-01-02 17:28:25', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2544, '取关商机', 'crm:contact:delete-business', 3, 11, 2416, '', '', '', '', 0, b'1', b'1', b'1', '1', + '2024-01-02 17:28:43', '1', '2024-01-02 17:28:51', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2545, '商品统计', '', 2, 3, 2358, 'product', 'fa:product-hunt', 'mall/statistics/product/index', + 'ProductStatistics', 0, b'1', b'1', b'1', '', '2023-12-15 18:54:28', '1', '2024-02-26 20:41:52', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2546, '客户公海', '', 2, 30, 2397, 'customer/pool', 'fa-solid:swimming-pool', 'crm/customer/pool/index', + 'CrmCustomerPool', 0, b'1', b'1', b'1', '1', '2024-01-15 21:29:34', '1', '2024-02-17 17:14:18', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2547, '订单查询', 'trade:order:query', 3, 1, 2076, '', '', '', '', 0, b'1', b'1', b'1', '1', + '2024-01-16 08:52:00', '1', '2024-01-16 08:52:00', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2548, '订单更新', 'trade:order:update', 3, 2, 2076, '', '', '', '', 0, b'1', b'1', b'1', '1', + '2024-01-16 08:52:21', '1', '2024-01-16 08:52:21', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2549, '支付&退款案例', '', 2, 1, 2161, 'order', 'fa:paypal', 'pay/demo/order/index', '', 0, b'1', b'1', b'1', + '1', '2024-01-18 23:45:00', '1', '2024-01-18 23:47:21', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2550, '转账案例', '', 2, 2, 2161, 'transfer', 'fa:transgender-alt', 'pay/demo/transfer/index', '', 0, b'1', + b'1', b'1', '1', '2024-01-18 23:51:16', '1', '2024-01-18 23:51:16', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2551, '钱包管理', '', 1, 4, 1117, 'wallet', 'ep:wallet', '', '', 0, b'1', b'1', b'1', '', '2023-12-29 02:32:54', + '1', '2024-02-29 08:58:54', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2552, '充值套餐', '', 2, 2, 2551, 'wallet-recharge-package', 'fa:leaf', 'pay/wallet/rechargePackage/index', + 'WalletRechargePackage', 0, b'1', b'1', b'1', '', '2023-12-29 02:32:54', '', '2023-12-29 02:32:54', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2553, '钱包充值套餐查询', 'pay:wallet-recharge-package:query', 3, 1, 2552, '', '', '', NULL, 0, b'1', b'1', + b'1', '', '2023-12-29 02:32:54', '', '2023-12-29 02:32:54', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2554, '钱包充值套餐创建', 'pay:wallet-recharge-package:create', 3, 2, 2552, '', '', '', NULL, 0, b'1', b'1', + b'1', '', '2023-12-29 02:32:54', '', '2023-12-29 02:32:54', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2555, '钱包充值套餐更新', 'pay:wallet-recharge-package:update', 3, 3, 2552, '', '', '', NULL, 0, b'1', b'1', + b'1', '', '2023-12-29 02:32:54', '', '2023-12-29 02:32:54', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2556, '钱包充值套餐删除', 'pay:wallet-recharge-package:delete', 3, 4, 2552, '', '', '', NULL, 0, b'1', b'1', + b'1', '', '2023-12-29 02:32:54', '', '2023-12-29 02:32:54', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2557, '钱包余额', '', 2, 1, 2551, 'wallet-balance', 'fa:leaf', 'pay/wallet/balance/index', 'WalletBalance', 0, + b'1', b'1', b'1', '', '2023-12-29 02:32:54', '', '2023-12-29 02:32:54', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2558, '钱包余额查询', 'pay:wallet:query', 3, 1, 2557, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2023-12-29 02:32:54', '', '2023-12-29 02:32:54', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2559, '转账订单', '', 2, 3, 1117, 'transfer', 'ep:credit-card', 'pay/transfer/index', 'PayTransfer', 0, b'1', + b'1', b'1', '', '2023-12-29 02:32:54', '', '2023-12-29 02:32:54', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2560, '数据统计', '', 1, 200, 2397, 'statistics', 'ep:data-line', '', '', 0, b'1', b'1', b'1', '1', + '2024-01-26 22:50:35', '1', '2024-02-24 20:10:07', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2561, '排行榜', 'crm:statistics-rank:query', 2, 1, 2560, 'ranking', 'fa:area-chart', + 'crm/statistics/rank/index', 'CrmStatisticsRank', 0, b'1', b'1', b'1', '1', '2024-01-26 22:52:09', '1', + '2024-04-24 19:39:11', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2562, '客户导入', 'crm:customer:import', 3, 6, 2391, '', '', '', '', 0, b'1', b'1', b'1', '1', + '2024-02-01 13:09:00', '1', '2024-02-01 13:09:05', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2563, 'ERP 系统', '', 1, 300, 0, '/erp', 'fa-solid:store', '', '', 0, b'1', b'1', b'1', '1', + '2024-02-04 15:37:25', '1', '2024-02-04 15:37:25', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2564, '产品管理', '', 1, 40, 2563, 'product', 'fa:product-hunt', '', '', 0, b'1', b'1', b'1', '1', + '2024-02-04 15:38:43', '1', '2024-02-04 15:38:43', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2565, '产品信息', '', 2, 0, 2564, 'product', 'fa-solid:apple-alt', 'erp/product/product/index', 'ErpProduct', 0, + b'1', b'1', b'1', '', '2024-02-04 07:52:15', '1', '2024-02-05 14:42:11', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2566, '产品查询', 'erp:product:query', 3, 1, 2565, '', '', '', '', 0, b'1', b'1', b'1', '', + '2024-02-04 07:52:15', '1', '2024-02-04 17:21:57', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2567, '产品创建', 'erp:product:create', 3, 2, 2565, '', '', '', '', 0, b'1', b'1', b'1', '', + '2024-02-04 07:52:15', '1', '2024-02-04 17:22:12', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2568, '产品更新', 'erp:product:update', 3, 3, 2565, '', '', '', '', 0, b'1', b'1', b'1', '', + '2024-02-04 07:52:15', '1', '2024-02-04 17:22:16', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2569, '产品删除', 'erp:product:delete', 3, 4, 2565, '', '', '', '', 0, b'1', b'1', b'1', '', + '2024-02-04 07:52:15', '1', '2024-02-04 17:22:22', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2570, '产品导出', 'erp:product:export', 3, 5, 2565, '', '', '', '', 0, b'1', b'1', b'1', '', + '2024-02-04 07:52:15', '1', '2024-02-04 17:22:26', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2571, '产品分类', '', 2, 1, 2564, 'product-category', 'fa:certificate', 'erp/product/category/index', + 'ErpProductCategory', 0, b'1', b'1', b'1', '', '2024-02-04 09:21:04', '1', '2024-02-04 17:24:58', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2572, '分类查询', 'erp:product-category:query', 3, 1, 2571, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2024-02-04 09:21:04', '', '2024-02-04 09:21:04', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2573, '分类创建', 'erp:product-category:create', 3, 2, 2571, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2024-02-04 09:21:04', '', '2024-02-04 09:21:04', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2574, '分类更新', 'erp:product-category:update', 3, 3, 2571, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2024-02-04 09:21:04', '', '2024-02-04 09:21:04', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2575, '分类删除', 'erp:product-category:delete', 3, 4, 2571, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2024-02-04 09:21:04', '', '2024-02-04 09:21:04', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2576, '分类导出', 'erp:product-category:export', 3, 5, 2571, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2024-02-04 09:21:04', '', '2024-02-04 09:21:04', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2577, '产品单位', '', 2, 2, 2564, 'unit', 'ep:opportunity', 'erp/product/unit/index', 'ErpProductUnit', 0, b'1', + b'1', b'1', '', '2024-02-04 11:54:08', '1', '2024-02-04 19:54:37', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2578, '单位查询', 'erp:product-unit:query', 3, 1, 2577, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2024-02-04 11:54:08', '', '2024-02-04 11:54:08', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2579, '单位创建', 'erp:product-unit:create', 3, 2, 2577, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2024-02-04 11:54:08', '', '2024-02-04 11:54:08', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2580, '单位更新', 'erp:product-unit:update', 3, 3, 2577, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2024-02-04 11:54:08', '', '2024-02-04 11:54:08', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2581, '单位删除', 'erp:product-unit:delete', 3, 4, 2577, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2024-02-04 11:54:08', '', '2024-02-04 11:54:08', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2582, '单位导出', 'erp:product-unit:export', 3, 5, 2577, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2024-02-04 11:54:08', '', '2024-02-04 11:54:08', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2583, '库存管理', '', 1, 30, 2563, 'stock', 'fa:window-restore', '', '', 0, b'1', b'1', b'1', '1', + '2024-02-05 00:29:37', '1', '2024-02-05 00:29:37', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2584, '仓库信息', '', 2, 0, 2583, 'warehouse', 'ep:house', 'erp/stock/warehouse/index', 'ErpWarehouse', 0, b'1', + b'1', b'1', '', '2024-02-04 17:12:09', '1', '2024-02-05 01:12:53', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2585, '仓库查询', 'erp:warehouse:query', 3, 1, 2584, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2024-02-04 17:12:09', '', '2024-02-04 17:12:09', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2586, '仓库创建', 'erp:warehouse:create', 3, 2, 2584, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2024-02-04 17:12:09', '', '2024-02-04 17:12:09', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2587, '仓库更新', 'erp:warehouse:update', 3, 3, 2584, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2024-02-04 17:12:09', '', '2024-02-04 17:12:09', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2588, '仓库删除', 'erp:warehouse:delete', 3, 4, 2584, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2024-02-04 17:12:09', '', '2024-02-04 17:12:09', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2589, '仓库导出', 'erp:warehouse:export', 3, 5, 2584, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2024-02-04 17:12:09', '', '2024-02-04 17:12:09', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2590, '产品库存', '', 2, 1, 2583, 'stock', 'ep:coffee', 'erp/stock/stock/index', 'ErpStock', 0, b'1', b'1', + b'1', '', '2024-02-05 06:40:50', '1', '2024-02-05 14:42:44', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2591, '库存查询', 'erp:stock:query', 3, 1, 2590, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2024-02-05 06:40:50', '', '2024-02-05 06:40:50', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2592, '库存导出', 'erp:stock:export', 3, 5, 2590, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2024-02-05 06:40:50', '', '2024-02-05 06:40:50', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2593, '出入库明细', '', 2, 2, 2583, 'record', 'fa-solid:blog', 'erp/stock/record/index', 'ErpStockRecord', 0, + b'1', b'1', b'1', '', '2024-02-05 10:27:21', '1', '2024-02-06 17:26:11', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2594, '库存明细查询', 'erp:stock-record:query', 3, 1, 2593, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2024-02-05 10:27:21', '', '2024-02-05 10:27:21', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2595, '库存明细导出', 'erp:stock-record:export', 3, 5, 2593, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2024-02-05 10:27:21', '', '2024-02-05 10:27:21', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2596, '其它入库', '', 2, 3, 2583, 'in', 'ep:zoom-in', 'erp/stock/in/index', 'ErpStockIn', 0, b'1', b'1', b'1', + '', '2024-02-05 16:08:56', '1', '2024-02-07 19:06:51', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2597, '其它入库单查询', 'erp:stock-in:query', 3, 1, 2596, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2024-02-05 16:08:56', '', '2024-02-05 16:08:56', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2598, '其它入库单创建', 'erp:stock-in:create', 3, 2, 2596, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2024-02-05 16:08:56', '', '2024-02-05 16:08:56', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2599, '其它入库单更新', 'erp:stock-in:update', 3, 3, 2596, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2024-02-05 16:08:56', '', '2024-02-05 16:08:56', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2600, '其它入库单删除', 'erp:stock-in:delete', 3, 4, 2596, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2024-02-05 16:08:56', '', '2024-02-05 16:08:56', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2601, '其它入库单导出', 'erp:stock-in:export', 3, 5, 2596, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2024-02-05 16:08:56', '', '2024-02-05 16:08:56', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2602, '采购管理', '', 1, 10, 2563, 'purchase', 'fa:buysellads', '', '', 0, b'1', b'1', b'1', '1', + '2024-02-06 16:01:01', '1', '2024-02-06 16:01:23', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2603, '供应商信息', '', 2, 4, 2602, 'supplier', 'fa:superpowers', 'erp/purchase/supplier/index', 'ErpSupplier', + 0, b'1', b'1', b'1', '', '2024-02-06 08:21:55', '1', '2024-02-06 16:22:25', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2604, '供应商查询', 'erp:supplier:query', 3, 1, 2603, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2024-02-06 08:21:55', '', '2024-02-06 08:21:55', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2605, '供应商创建', 'erp:supplier:create', 3, 2, 2603, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2024-02-06 08:21:55', '', '2024-02-06 08:21:55', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2606, '供应商更新', 'erp:supplier:update', 3, 3, 2603, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2024-02-06 08:21:55', '', '2024-02-06 08:21:55', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2607, '供应商删除', 'erp:supplier:delete', 3, 4, 2603, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2024-02-06 08:21:55', '', '2024-02-06 08:21:55', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2608, '供应商导出', 'erp:supplier:export', 3, 5, 2603, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2024-02-06 08:21:55', '', '2024-02-06 08:21:55', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2609, '其它入库单审批', 'erp:stock-in:update-status', 3, 6, 2596, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2024-02-05 16:08:56', '', '2024-02-05 16:08:56', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2610, '其它出库', '', 2, 4, 2583, 'out', 'ep:zoom-out', 'erp/stock/out/index', 'ErpStockOut', 0, b'1', b'1', + b'1', '', '2024-02-05 16:08:56', '1', '2024-02-07 19:06:55', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2611, '其它出库单查询', 'erp:stock-out:query', 3, 1, 2610, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2024-02-05 16:08:56', '', '2024-02-07 06:43:39', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2612, '其它出库单创建', 'erp:stock-out:create', 3, 2, 2610, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2024-02-05 16:08:56', '', '2024-02-07 06:43:42', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2613, '其它出库单更新', 'erp:stock-out:update', 3, 3, 2610, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2024-02-05 16:08:56', '', '2024-02-07 06:43:44', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2614, '其它出库单删除', 'erp:stock-out:delete', 3, 4, 2610, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2024-02-05 16:08:56', '', '2024-02-07 06:43:56', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2615, '其它出库单导出', 'erp:stock-out:export', 3, 5, 2610, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2024-02-05 16:08:56', '', '2024-02-07 06:43:57', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2616, '其它出库单审批', 'erp:stock-out:update-status', 3, 6, 2610, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2024-02-05 16:08:56', '', '2024-02-07 06:43:58', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2617, '销售管理', '', 1, 20, 2563, 'sale', 'fa:sellsy', '', '', 0, b'1', b'1', b'1', '1', '2024-02-07 15:12:32', + '1', '2024-02-07 15:12:32', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2618, '客户信息', '', 2, 4, 2617, 'customer', 'ep:avatar', 'erp/sale/customer/index', 'ErpCustomer', 0, b'1', + b'1', b'1', '', '2024-02-07 07:21:45', '1', '2024-02-07 15:22:25', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2619, '客户查询', 'erp:customer:query', 3, 1, 2618, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2024-02-07 07:21:45', '', '2024-02-07 07:21:45', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2620, '客户创建', 'erp:customer:create', 3, 2, 2618, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2024-02-07 07:21:45', '', '2024-02-07 07:21:45', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2621, '客户更新', 'erp:customer:update', 3, 3, 2618, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2024-02-07 07:21:45', '', '2024-02-07 07:21:45', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2622, '客户删除', 'erp:customer:delete', 3, 4, 2618, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2024-02-07 07:21:45', '', '2024-02-07 07:21:45', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2623, '客户导出', 'erp:customer:export', 3, 5, 2618, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2024-02-07 07:21:45', '', '2024-02-07 07:21:45', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2624, '库存调拨', '', 2, 5, 2583, 'move', 'ep:folder-remove', 'erp/stock/move/index', 'ErpStockMove', 0, b'1', + b'1', b'1', '', '2024-02-05 16:08:56', '1', '2024-02-16 18:53:55', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2625, '库存调度单查询', 'erp:stock-move:query', 3, 1, 2624, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2024-02-05 16:08:56', '', '2024-02-07 11:12:49', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2626, '库存调度单创建', 'erp:stock-move:create', 3, 2, 2624, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2024-02-05 16:08:56', '', '2024-02-07 11:12:52', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2627, '库存调度单更新', 'erp:stock-move:update', 3, 3, 2624, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2024-02-05 16:08:56', '', '2024-02-07 11:12:55', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2628, '库存调度单删除', 'erp:stock-move:delete', 3, 4, 2624, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2024-02-05 16:08:56', '', '2024-02-07 11:12:57', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2629, '库存调度单导出', 'erp:stock-move:export', 3, 5, 2624, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2024-02-05 16:08:56', '', '2024-02-07 11:12:59', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2630, '库存调度单审批', 'erp:stock-move:update-status', 3, 6, 2624, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2024-02-05 16:08:56', '', '2024-02-07 11:13:03', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2631, '库存盘点', '', 2, 6, 2583, 'check', 'ep:circle-check-filled', 'erp/stock/check/index', 'ErpStockCheck', + 0, b'1', b'1', b'1', '', '2024-02-05 16:08:56', '1', '2024-02-08 08:31:09', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2632, '库存盘点单查询', 'erp:stock-check:query', 3, 1, 2631, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2024-02-05 16:08:56', '', '2024-02-07 11:12:49', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2633, '库存盘点单创建', 'erp:stock-check:create', 3, 2, 2631, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2024-02-05 16:08:56', '', '2024-02-07 11:12:52', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2634, '库存盘点单更新', 'erp:stock-check:update', 3, 3, 2631, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2024-02-05 16:08:56', '', '2024-02-07 11:12:55', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2635, '库存盘点单删除', 'erp:stock-check:delete', 3, 4, 2631, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2024-02-05 16:08:56', '', '2024-02-07 11:12:57', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2636, '库存盘点单导出', 'erp:stock-check:export', 3, 5, 2631, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2024-02-05 16:08:56', '', '2024-02-07 11:12:59', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2637, '库存盘点单审批', 'erp:stock-check:update-status', 3, 6, 2631, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2024-02-05 16:08:56', '', '2024-02-07 11:13:03', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2638, '销售订单', '', 2, 1, 2617, 'order', 'fa:first-order', 'erp/sale/order/index', 'ErpSaleOrder', 0, b'1', + b'1', b'1', '', '2024-02-05 16:08:56', '1', '2024-02-10 21:59:20', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2639, '销售订单查询', 'erp:sale-order:query', 3, 1, 2638, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2024-02-05 16:08:56', '', '2024-02-07 11:12:49', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2640, '销售订单创建', 'erp:sale-order:create', 3, 2, 2638, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2024-02-05 16:08:56', '', '2024-02-07 11:12:52', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2641, '销售订单更新', 'erp:sale-order:update', 3, 3, 2638, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2024-02-05 16:08:56', '', '2024-02-07 11:12:55', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2642, '销售订单删除', 'erp:sale-order:delete', 3, 4, 2638, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2024-02-05 16:08:56', '', '2024-02-07 11:12:57', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2643, '销售订单导出', 'erp:sale-order:export', 3, 5, 2638, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2024-02-05 16:08:56', '', '2024-02-07 11:12:59', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2644, '销售订单审批', 'erp:sale-order:update-status', 3, 6, 2638, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2024-02-05 16:08:56', '', '2024-02-07 11:13:03', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2645, '财务管理', '', 1, 50, 2563, 'finance', 'ep:money', '', '', 0, b'1', b'1', b'1', '1', + '2024-02-10 08:05:58', '1', '2024-02-10 08:06:07', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2646, '结算账户', '', 2, 10, 2645, 'account', 'fa:universal-access', 'erp/finance/account/index', 'ErpAccount', + 0, b'1', b'1', b'1', '', '2024-02-10 00:15:07', '1', '2024-02-14 08:24:31', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2647, '结算账户查询', 'erp:account:query', 3, 1, 2646, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2024-02-10 00:15:07', '', '2024-02-10 00:15:07', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2648, '结算账户创建', 'erp:account:create', 3, 2, 2646, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2024-02-10 00:15:07', '', '2024-02-10 00:15:07', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2649, '结算账户更新', 'erp:account:update', 3, 3, 2646, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2024-02-10 00:15:07', '', '2024-02-10 00:15:07', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2650, '结算账户删除', 'erp:account:delete', 3, 4, 2646, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2024-02-10 00:15:07', '', '2024-02-10 00:15:07', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2651, '结算账户导出', 'erp:account:export', 3, 5, 2646, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2024-02-10 00:15:07', '', '2024-02-10 00:15:07', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2652, '销售出库', '', 2, 2, 2617, 'out', 'ep:sold-out', 'erp/sale/out/index', 'ErpSaleOut', 0, b'1', b'1', b'1', + '', '2024-02-05 16:08:56', '1', '2024-02-10 22:02:07', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2653, '销售出库查询', 'erp:sale-out:query', 3, 1, 2652, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2024-02-05 16:08:56', '', '2024-02-07 11:12:49', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2654, '销售出库创建', 'erp:sale-out:create', 3, 2, 2652, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2024-02-05 16:08:56', '', '2024-02-07 11:12:52', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2655, '销售出库更新', 'erp:sale-out:update', 3, 3, 2652, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2024-02-05 16:08:56', '', '2024-02-07 11:12:55', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2656, '销售出库删除', 'erp:sale-out:delete', 3, 4, 2652, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2024-02-05 16:08:56', '', '2024-02-07 11:12:57', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2657, '销售出库导出', 'erp:sale-out:export', 3, 5, 2652, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2024-02-05 16:08:56', '', '2024-02-07 11:12:59', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2658, '销售出库审批', 'erp:sale-out:update-status', 3, 6, 2652, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2024-02-05 16:08:56', '', '2024-02-07 11:13:03', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2659, '销售退货', '', 2, 3, 2617, 'return', 'fa-solid:bone', 'erp/sale/return/index', 'ErpSaleReturn', 0, b'1', + b'1', b'1', '', '2024-02-05 16:08:56', '1', '2024-02-12 06:12:58', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2660, '销售退货查询', 'erp:sale-return:query', 3, 1, 2659, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2024-02-05 16:08:56', '', '2024-02-07 11:12:49', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2661, '销售退货创建', 'erp:sale-return:create', 3, 2, 2659, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2024-02-05 16:08:56', '', '2024-02-07 11:12:52', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2662, '销售退货更新', 'erp:sale-return:update', 3, 3, 2659, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2024-02-05 16:08:56', '', '2024-02-07 11:12:55', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2663, '销售退货删除', 'erp:sale-return:delete', 3, 4, 2659, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2024-02-05 16:08:56', '', '2024-02-07 11:12:57', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2664, '销售退货导出', 'erp:sale-return:export', 3, 5, 2659, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2024-02-05 16:08:56', '', '2024-02-07 11:12:59', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2665, '销售退货审批', 'erp:sale-return:update-status', 3, 6, 2659, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2024-02-05 16:08:56', '', '2024-02-07 11:13:03', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2666, '采购订单', '', 2, 1, 2602, 'order', 'fa-solid:border-all', 'erp/purchase/order/index', + 'ErpPurchaseOrder', 0, b'1', b'1', b'1', '', '2024-02-05 16:08:56', '1', '2024-02-12 08:51:49', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2667, '采购订单查询', 'erp:purchase-order:query', 3, 1, 2666, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2024-02-05 16:08:56', '', '2024-02-12 00:45:17', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2668, '采购订单创建', 'erp:purchase-order:create', 3, 2, 2666, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2024-02-05 16:08:56', '', '2024-02-12 00:44:54', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2669, '采购订单更新', 'erp:purchase-order:update', 3, 3, 2666, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2024-02-05 16:08:56', '', '2024-02-12 00:44:58', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2670, '采购订单删除', 'erp:purchase-order:delete', 3, 4, 2666, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2024-02-05 16:08:56', '', '2024-02-12 00:45:00', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2671, '采购订单导出', 'erp:purchase-order:export', 3, 5, 2666, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2024-02-05 16:08:56', '', '2024-02-12 00:45:05', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2672, '采购订单审批', 'erp:purchase-order:update-status', 3, 6, 2666, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2024-02-05 16:08:56', '', '2024-02-12 00:45:08', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2673, '采购入库', '', 2, 2, 2602, 'in', 'fa-solid:gopuram', 'erp/purchase/in/index', 'ErpPurchaseIn', 0, b'1', + b'1', b'1', '', '2024-02-05 16:08:56', '1', '2024-02-12 11:19:27', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2674, '采购入库查询', 'erp:purchase-in:query', 3, 1, 2673, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2024-02-05 16:08:56', '', '2024-02-12 00:45:17', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2675, '采购入库创建', 'erp:purchase-in:create', 3, 2, 2673, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2024-02-05 16:08:56', '', '2024-02-12 00:44:54', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2676, '采购入库更新', 'erp:purchase-in:update', 3, 3, 2673, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2024-02-05 16:08:56', '', '2024-02-12 00:44:58', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2677, '采购入库删除', 'erp:purchase-in:delete', 3, 4, 2673, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2024-02-05 16:08:56', '', '2024-02-12 00:45:00', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2678, '采购入库导出', 'erp:purchase-in:export', 3, 5, 2673, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2024-02-05 16:08:56', '', '2024-02-12 00:45:05', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2679, '采购入库审批', 'erp:purchase-in:update-status', 3, 6, 2673, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2024-02-05 16:08:56', '', '2024-02-12 00:45:08', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2680, '采购退货', '', 2, 3, 2602, 'return', 'ep:minus', 'erp/purchase/return/index', 'ErpPurchaseReturn', 0, + b'1', b'1', b'1', '', '2024-02-05 16:08:56', '1', '2024-02-12 20:51:02', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2681, '采购退货查询', 'erp:purchase-return:query', 3, 1, 2680, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2024-02-05 16:08:56', '', '2024-02-12 00:45:17', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2682, '采购退货创建', 'erp:purchase-return:create', 3, 2, 2680, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2024-02-05 16:08:56', '', '2024-02-12 00:44:54', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2683, '采购退货更新', 'erp:purchase-return:update', 3, 3, 2680, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2024-02-05 16:08:56', '', '2024-02-12 00:44:58', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2684, '采购退货删除', 'erp:purchase-return:delete', 3, 4, 2680, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2024-02-05 16:08:56', '', '2024-02-12 00:45:00', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2685, '采购退货导出', 'erp:purchase-return:export', 3, 5, 2680, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2024-02-05 16:08:56', '', '2024-02-12 00:45:05', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2686, '采购退货审批', 'erp:purchase-return:update-status', 3, 6, 2680, '', '', '', NULL, 0, b'1', b'1', b'1', + '', '2024-02-05 16:08:56', '', '2024-02-12 00:45:08', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2687, '付款单', '', 2, 1, 2645, 'payment', 'ep:caret-right', 'erp/finance/payment/index', 'ErpFinancePayment', + 0, b'1', b'1', b'1', '', '2024-02-05 16:08:56', '1', '2024-02-14 08:24:23', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2688, '付款单查询', 'erp:finance-payment:query', 3, 1, 2687, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2024-02-05 16:08:56', '', '2024-02-12 00:45:17', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2689, '付款单创建', 'erp:finance-payment:create', 3, 2, 2687, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2024-02-05 16:08:56', '', '2024-02-12 00:44:54', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2690, '付款单更新', 'erp:finance-payment:update', 3, 3, 2687, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2024-02-05 16:08:56', '', '2024-02-12 00:44:58', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2691, '付款单删除', 'erp:finance-payment:delete', 3, 4, 2687, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2024-02-05 16:08:56', '', '2024-02-12 00:45:00', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2692, '付款单导出', 'erp:finance-payment:export', 3, 5, 2687, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2024-02-05 16:08:56', '', '2024-02-12 00:45:05', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2693, '付款单审批', 'erp:finance-payment:update-status', 3, 6, 2687, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2024-02-05 16:08:56', '', '2024-02-12 00:45:08', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2694, '收款单', '', 2, 2, 2645, 'receipt', 'ep:expand', 'erp/finance/receipt/index', 'ErpFinanceReceipt', 0, + b'1', b'1', b'1', '', '2024-02-05 16:08:56', '1', '2024-02-15 19:35:45', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2695, '收款单查询', 'erp:finance-receipt:query', 3, 1, 2694, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2024-02-05 16:08:56', '', '2024-02-12 00:45:17', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2696, '收款单创建', 'erp:finance-receipt:create', 3, 2, 2694, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2024-02-05 16:08:56', '', '2024-02-12 00:44:54', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2697, '收款单更新', 'erp:finance-receipt:update', 3, 3, 2694, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2024-02-05 16:08:56', '', '2024-02-12 00:44:58', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2698, '收款单删除', 'erp:finance-receipt:delete', 3, 4, 2694, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2024-02-05 16:08:56', '', '2024-02-12 00:45:00', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2699, '收款单导出', 'erp:finance-receipt:export', 3, 5, 2694, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2024-02-05 16:08:56', '', '2024-02-12 00:45:05', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2700, '收款单审批', 'erp:finance-receipt:update-status', 3, 6, 2694, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2024-02-05 16:08:56', '', '2024-02-12 00:45:08', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2701, '待办事项', '', 2, 0, 2397, 'backlog', 'fa-solid:tasks', 'crm/backlog/index', 'CrmBacklog', 0, b'1', b'1', + b'1', '1', '2024-02-17 17:17:11', '1', '2024-02-17 17:17:11', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2702, 'ERP 首页', 'erp:statistics:query', 2, 0, 2563, 'home', 'ep:home-filled', 'erp/home/index.vue', 'ErpHome', + 0, b'1', b'1', b'1', '1', '2024-02-18 16:49:40', '1', '2024-02-26 21:12:18', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2703, '商机状态配置', '', 2, 4, 2524, 'business-status', 'fa-solid:charging-station', + 'crm/business/status/index', 'CrmBusinessStatus', 0, b'1', b'1', b'1', '1', '2024-02-21 20:15:17', '1', + '2024-02-21 20:15:17', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2704, '商机状态查询', 'crm:business-status:query', 3, 1, 2703, '', '', '', '', 0, b'1', b'1', b'1', '1', + '2024-02-21 20:35:36', '1', '2024-02-21 20:36:06', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2705, '商机状态创建', 'crm:business-status:create', 3, 2, 2703, '', '', '', '', 0, b'1', b'1', b'1', '1', + '2024-02-21 20:35:57', '1', '2024-02-21 20:35:57', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2706, '商机状态更新', 'crm:business-status:update', 3, 3, 2703, '', '', '', '', 0, b'1', b'1', b'1', '1', + '2024-02-21 20:36:21', '1', '2024-02-21 20:36:21', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2707, '商机状态删除', 'crm:business-status:delete', 3, 4, 2703, '', '', '', '', 0, b'1', b'1', b'1', '1', + '2024-02-21 20:36:36', '1', '2024-02-21 20:36:36', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2708, '合同配置', '', 2, 5, 2524, 'contract-config', 'ep:connection', 'crm/contract/config/index', + 'CrmContractConfig', 0, b'1', b'1', b'1', '1', '2024-02-24 16:44:40', '1', '2024-02-24 16:44:48', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2709, '客户公海配置查询', 'crm:customer-pool-config:query', 3, 2, 2516, '', '', '', '', 0, b'1', b'1', b'1', + '1', '2024-02-24 16:45:19', '1', '2024-02-24 16:45:28', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2710, '合同配置更新', 'crm:contract-config:update', 3, 1, 2708, '', '', '', '', 0, b'1', b'1', b'1', '1', + '2024-02-24 16:45:56', '1', '2024-02-24 16:45:56', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2711, '合同配置查询', 'crm:contract-config:query', 3, 2, 2708, '', '', '', '', 0, b'1', b'1', b'1', '1', + '2024-02-24 16:46:16', '1', '2024-02-24 16:46:16', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2712, '客户分析', 'crm:statistics-customer:query', 2, 0, 2560, 'customer', 'ep:avatar', + 'crm/statistics/customer/index.vue', 'CrmStatisticsCustomer', 0, b'1', b'1', b'1', '1', '2024-03-09 16:43:56', + '1', '2024-05-04 20:38:50', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2713, '抄送我的', 'bpm:process-instance-cc:query', 2, 30, 1200, 'copy', 'ep:copy-document', + 'bpm/task/copy/index', 'BpmProcessInstanceCopy', 0, b'1', b'1', b'1', '1', '2024-03-17 21:50:23', '1', + '2024-04-24 19:55:12', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2714, '流程分类', '', 2, 3, 1186, 'category', 'fa:object-ungroup', 'bpm/category/index', 'BpmCategory', 0, b'1', + b'1', b'1', '', '2024-03-08 02:00:51', '1', '2024-03-21 23:51:18', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2715, '分类查询', 'bpm:category:query', 3, 1, 2714, '', '', '', '', 0, b'1', b'1', b'1', '', + '2024-03-08 02:00:51', '1', '2024-03-19 14:36:25', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2716, '分类创建', 'bpm:category:create', 3, 2, 2714, '', '', '', '', 0, b'1', b'1', b'1', '', + '2024-03-08 02:00:51', '1', '2024-03-19 14:36:31', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2717, '分类更新', 'bpm:category:update', 3, 3, 2714, '', '', '', '', 0, b'1', b'1', b'1', '', + '2024-03-08 02:00:51', '1', '2024-03-19 14:36:35', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2718, '分类删除', 'bpm:category:delete', 3, 4, 2714, '', '', '', '', 0, b'1', b'1', b'1', '', + '2024-03-08 02:00:51', '1', '2024-03-19 14:36:41', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2720, '发起流程', '', 2, 0, 1200, 'create', 'fa-solid:grin-stars', 'bpm/processInstance/create/index', + 'BpmProcessInstanceCreate', 0, b'1', b'0', b'1', '1', '2024-03-19 19:46:05', '1', '2024-03-23 19:03:42', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2721, '流程实例', '', 2, 10, 1186, 'process-instance/manager', 'fa:square', 'bpm/processInstance/manager/index', + 'BpmProcessInstanceManager', 0, b'1', b'1', b'1', '1', '2024-03-21 23:57:30', '1', '2024-03-21 23:57:30', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2722, '流程实例的查询(管理员)', 'bpm:process-instance:manager-query', 3, 1, 2721, '', '', '', '', 0, b'1', b'1', + b'1', '1', '2024-03-22 08:18:27', '1', '2024-03-22 08:19:05', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2723, '流程实例的取消(管理员)', 'bpm:process-instance:cancel-by-admin', 3, 2, 2721, '', '', '', '', 0, b'1', + b'1', b'1', '1', '2024-03-22 08:19:25', '1', '2024-03-22 08:19:25', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2724, '流程任务', '', 2, 11, 1186, 'process-tasnk', 'ep:collection-tag', 'bpm/task/manager/index', + 'BpmManagerTask', 0, b'1', b'1', b'1', '1', '2024-03-22 08:43:22', '1', '2024-03-22 08:43:27', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2725, '流程任务的查询(管理员)', 'bpm:task:mananger-query', 3, 1, 2724, '', '', '', '', 0, b'1', b'1', b'1', '1', + '2024-03-22 08:43:49', '1', '2024-03-22 08:43:49', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2726, '流程监听器', '', 2, 5, 1186, 'process-listener', 'fa:assistive-listening-systems', + 'bpm/processListener/index', 'BpmProcessListener', 0, b'1', b'1', b'1', '', '2024-03-09 16:05:34', '1', + '2024-03-23 13:13:38', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2727, '流程监听器查询', 'bpm:process-listener:query', 3, 1, 2726, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2024-03-09 16:05:34', '', '2024-03-09 16:05:34', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2728, '流程监听器创建', 'bpm:process-listener:create', 3, 2, 2726, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2024-03-09 16:05:34', '', '2024-03-09 16:05:34', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2729, '流程监听器更新', 'bpm:process-listener:update', 3, 3, 2726, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2024-03-09 16:05:34', '', '2024-03-09 16:05:34', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2730, '流程监听器删除', 'bpm:process-listener:delete', 3, 4, 2726, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2024-03-09 16:05:34', '', '2024-03-09 16:05:34', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2731, '流程表达式', '', 2, 6, 1186, 'process-expression', 'fa:wpexplorer', 'bpm/processExpression/index', + 'BpmProcessExpression', 0, b'1', b'1', b'1', '', '2024-03-09 22:35:08', '1', '2024-03-23 19:43:05', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2732, '流程表达式查询', 'bpm:process-expression:query', 3, 1, 2731, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2024-03-09 22:35:08', '', '2024-03-09 22:35:08', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2733, '流程表达式创建', 'bpm:process-expression:create', 3, 2, 2731, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2024-03-09 22:35:08', '', '2024-03-09 22:35:08', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2734, '流程表达式更新', 'bpm:process-expression:update', 3, 3, 2731, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2024-03-09 22:35:08', '', '2024-03-09 22:35:08', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2735, '流程表达式删除', 'bpm:process-expression:delete', 3, 4, 2731, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2024-03-09 22:35:08', '', '2024-03-09 22:35:08', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2736, '员工业绩', 'crm:statistics-performance:query', 2, 3, 2560, 'performance', 'ep:dish-dot', + 'crm/statistics/performance/index', 'CrmStatisticsPerformance', 0, b'1', b'1', b'1', '1', '2024-04-05 13:49:20', + '1', '2024-04-24 19:42:43', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2737, '客户画像', 'crm:statistics-portrait:query', 2, 4, 2560, 'portrait', 'ep:picture', + 'crm/statistics/portrait/index', 'CrmStatisticsPortrait', 0, b'1', b'1', b'1', '1', '2024-04-05 13:57:40', '1', + '2024-04-24 19:42:24', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2738, '销售漏斗', 'crm:statistics-funnel:query', 2, 5, 2560, 'funnel', 'ep:grape', + 'crm/statistics/funnel/index', 'CrmStatisticsFunnel', 0, b'1', b'1', b'1', '1', '2024-04-13 10:53:26', '1', + '2024-04-24 19:39:33', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2739, '消息中心', '', 1, 7, 1, 'messages', 'ep:chat-dot-round', '', '', 0, b'1', b'1', b'1', '1', + '2024-04-22 23:54:30', '1', '2024-04-23 09:36:35', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2740, '监控中心', '', 1, 10, 2, 'monitors', 'ep:monitor', '', '', 0, b'1', b'1', b'1', '1', + '2024-04-23 00:04:44', '1', '2024-04-23 00:04:44', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2741, '领取公海客户', 'crm:customer:receive', 3, 1, 2546, '', '', '', '', 0, b'1', b'1', b'1', '1', + '2024-04-24 19:47:45', '1', '2024-04-24 19:47:45', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2742, '分配公海客户', 'crm:customer:distribute', 3, 2, 2546, '', '', '', '', 0, b'1', b'1', b'1', '1', + '2024-04-24 19:48:05', '1', '2024-04-24 19:48:05', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2743, '商品统计查询', 'statistics:product:query', 3, 1, 2545, '', '', '', '', 0, b'1', b'1', b'1', '1', + '2024-04-24 19:50:05', '1', '2024-04-24 19:50:05', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2744, '商品统计导出', 'statistics:product:export', 3, 2, 2545, '', '', '', '', 0, b'1', b'1', b'1', '1', + '2024-04-24 19:50:26', '1', '2024-04-24 19:50:26', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2745, '支付渠道查询', 'pay:channel:query', 3, 10, 1126, '', '', '', '', 0, b'1', b'1', b'1', '1', + '2024-04-24 19:53:01', '1', '2024-04-24 19:53:01', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2746, '支付渠道创建', 'pay:channel:create', 3, 11, 1126, '', '', '', '', 0, b'1', b'1', b'1', '1', + '2024-04-24 19:53:18', '1', '2024-04-24 19:53:18', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2747, '支付渠道更新', 'pay:channel:update', 3, 12, 1126, '', '', '', '', 0, b'1', b'1', b'1', '1', + '2024-04-24 19:53:32', '1', '2024-04-24 19:53:58', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2748, '支付渠道删除', 'pay:channel:delete', 3, 13, 1126, '', '', '', '', 0, b'1', b'1', b'1', '1', + '2024-04-24 19:54:34', '1', '2024-04-24 19:54:34', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2749, '商品收藏查询', 'product:favorite:query', 3, 10, 2014, '', '', '', '', 0, b'1', b'1', b'1', '1', + '2024-04-24 19:55:47', '1', '2024-04-24 19:55:47', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2750, '商品浏览查询', 'product:browse-history:query', 3, 20, 2014, '', '', '', '', 0, b'1', b'1', b'1', '1', + '2024-04-24 19:57:43', '1', '2024-04-24 19:57:43', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2751, '售后同意', 'trade:after-sale:agree', 3, 2, 2073, '', '', '', '', 0, b'1', b'1', b'1', '1', + '2024-04-24 19:58:40', '1', '2024-04-24 19:58:40', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2752, '售后不同意', 'trade:after-sale:disagree', 3, 3, 2073, '', '', '', '', 0, b'1', b'1', b'1', '1', + '2024-04-24 19:59:03', '1', '2024-04-24 19:59:03', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2753, '售后确认退货', 'trade:after-sale:receive', 3, 4, 2073, '', '', '', '', 0, b'1', b'1', b'1', '1', + '2024-04-24 20:00:07', '1', '2024-04-24 20:00:07', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2754, '售后确认退款', 'trade:after-sale:refund', 3, 5, 2073, '', '', '', '', 0, b'1', b'1', b'1', '1', + '2024-04-24 20:00:24', '1', '2024-04-24 20:00:24', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2755, '删除项目', 'report:go-view-project:delete', 3, 2, 2153, '', '', '', '', 0, b'1', b'1', b'1', '1', + '2024-04-24 20:01:37', '1', '2024-04-24 20:01:37', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2756, '会员等级记录查询', 'member:level-record:query', 3, 10, 2325, '', '', '', '', 0, b'1', b'1', b'1', '1', + '2024-04-24 20:02:32', '1', '2024-04-24 20:02:32', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2757, '会员经验记录查询', 'member:experience-record:query', 3, 11, 2325, '', '', '', '', 0, b'1', b'1', b'1', + '1', '2024-04-24 20:02:51', '1', '2024-04-24 20:02:51', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2758, 'AI 大模型', '', 1, 400, 0, '/ai', 'fa:apple', '', '', 0, b'1', b'1', b'1', '1', '2024-05-07 15:07:56', + '1', '2024-05-25 12:36:12', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2759, 'AI 对话', '', 2, 1, 2758, 'chat', 'ep:message', 'ai/chat/index/index.vue', 'AiChat', 0, b'1', b'1', b'1', + '1', '2024-05-07 15:09:14', '1', '2024-07-07 17:15:36', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2760, '控制台', '', 1, 100, 2758, 'console', 'ep:setting', '', '', 0, b'1', b'1', b'1', '1', + '2024-05-09 22:39:09', '1', '2024-05-24 23:34:21', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2761, 'API 密钥', '', 2, 0, 2760, 'api-key', 'ep:key', 'ai/model/apiKey/index.vue', 'AiApiKey', 0, b'1', b'1', + b'1', '', '2024-05-09 14:52:56', '1', '2024-05-10 22:44:08', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2762, 'API 密钥查询', 'ai:api-key:query', 3, 1, 2761, '', '', '', '', 0, b'1', b'1', b'1', '', + '2024-05-09 14:52:56', '1', '2024-05-13 20:36:32', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2763, 'API 密钥创建', 'ai:api-key:create', 3, 2, 2761, '', '', '', '', 0, b'1', b'1', b'1', '', + '2024-05-09 14:52:56', '1', '2024-05-13 20:36:26', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2764, 'API 密钥更新', 'ai:api-key:update', 3, 3, 2761, '', '', '', '', 0, b'1', b'1', b'1', '', + '2024-05-09 14:52:56', '1', '2024-05-13 20:36:42', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2765, 'API 密钥删除', 'ai:api-key:delete', 3, 4, 2761, '', '', '', '', 0, b'1', b'1', b'1', '', + '2024-05-09 14:52:56', '1', '2024-05-13 20:36:48', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2767, '聊天模型', '', 2, 0, 2760, 'chat-model', 'fa-solid:abacus', 'ai/model/chatModel/index.vue', + 'AiChatModel', 0, b'1', b'1', b'1', '', '2024-05-10 14:42:48', '1', '2024-05-10 22:44:16', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2768, '聊天模型查询', 'ai:chat-model:query', 3, 1, 2767, '', '', '', '', 0, b'1', b'1', b'1', '', + '2024-05-10 14:42:48', '1', '2024-05-13 20:37:02', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2769, '聊天模型创建', 'ai:chat-model:create', 3, 2, 2767, '', '', '', '', 0, b'1', b'1', b'1', '', + '2024-05-10 14:42:48', '1', '2024-05-13 20:37:12', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2770, '聊天模型更新', 'ai:chat-model:update', 3, 3, 2767, '', '', '', '', 0, b'1', b'1', b'1', '', + '2024-05-10 14:42:48', '1', '2024-05-13 20:37:18', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2771, '聊天模型删除', 'ai:chat-model:delete', 3, 4, 2767, '', '', '', '', 0, b'1', b'1', b'1', '', + '2024-05-10 14:42:48', '1', '2024-05-13 20:37:23', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2773, '聊天角色', '', 2, 0, 2760, 'chat-role', 'fa:user-secret', 'ai/model/chatRole/index.vue', 'AiChatRole', 0, + b'1', b'1', b'1', '', '2024-05-13 12:39:28', '1', '2024-05-13 20:41:45', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2774, '聊天角色查询', 'ai:chat-role:query', 3, 1, 2773, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2024-05-13 12:39:28', '', '2024-05-13 12:39:28', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2775, '聊天角色创建', 'ai:chat-role:create', 3, 2, 2773, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2024-05-13 12:39:28', '', '2024-05-13 12:39:28', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2776, '聊天角色更新', 'ai:chat-role:update', 3, 3, 2773, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2024-05-13 12:39:28', '', '2024-05-13 12:39:28', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2777, '聊天角色删除', 'ai:chat-role:delete', 3, 4, 2773, '', '', '', '', 0, b'1', b'1', b'1', '1', + '2024-05-13 21:43:38', '1', '2024-05-13 21:43:38', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2778, '聊天管理', '', 2, 10, 2760, 'chat-conversation', 'ep:chat-square', 'ai/chat/manager/index.vue', + 'AiChatManager', 0, b'1', b'1', b'1', '', '2024-05-24 15:39:18', '1', '2024-06-26 21:36:56', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2779, '会话查询', 'ai:chat-conversation:query', 3, 1, 2778, '', '', '', '', 0, b'1', b'1', b'1', '', + '2024-05-24 15:39:18', '1', '2024-05-25 08:38:30', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2780, '会话删除', 'ai:chat-conversation:delete', 3, 2, 2778, '', '', '', '', 0, b'1', b'1', b'1', '', + '2024-05-24 15:39:18', '1', '2024-05-25 08:38:40', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2781, '消息查询', 'ai:chat-message:query', 3, 11, 2778, '', '', '', '', 0, b'1', b'1', b'1', '1', + '2024-05-25 08:38:56', '1', '2024-05-25 08:38:56', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2782, '消息删除', 'ai:chat-message:delete', 3, 12, 2778, '', '', '', '', 0, b'1', b'1', b'1', '1', + '2024-05-25 08:39:10', '1', '2024-05-25 08:39:10', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2783, 'AI 绘画', '', 2, 2, 2758, 'image', 'ep:picture-rounded', 'ai/image/index/index.vue', 'AiImage', 0, b'1', + b'1', b'1', '1', '2024-05-26 11:45:17', '1', '2024-07-07 17:18:59', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2784, '绘画管理', '', 2, 11, 2760, 'image', 'fa:file-image-o', 'ai/image/manager/index.vue', 'AiImageManager', + 0, b'1', b'1', b'1', '', '2024-06-26 13:32:31', '1', '2024-06-26 21:37:13', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2785, '绘画查询', 'ai:image:query', 3, 1, 2784, '', '', '', '', 0, b'1', b'1', b'1', '', '2024-06-26 13:32:31', + '1', '2024-06-26 22:21:57', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2786, '绘画删除', 'ai:image:delete', 3, 4, 2784, '', '', '', '', 0, b'1', b'1', b'1', '', '2024-06-26 13:32:31', + '1', '2024-06-26 22:22:08', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2787, '绘图更新', 'ai:image:update', 3, 2, 2784, '', '', '', '', 0, b'1', b'1', b'1', '1', + '2024-06-26 22:47:56', '1', '2024-08-31 09:21:35', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2788, '音乐管理', '', 2, 12, 2760, 'music', 'fa:music', 'ai/music/manager/index.vue', 'AiMusicManager', 0, b'1', + b'1', b'1', '', '2024-06-27 15:03:33', '1', '2024-06-27 23:04:19', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2789, '音乐查询', 'ai:music:query', 3, 1, 2788, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2024-06-27 15:03:33', '', '2024-06-27 15:03:33', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2790, '音乐更新', 'ai:music:update', 3, 3, 2788, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2024-06-27 15:03:33', '', '2024-06-27 15:03:33', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2791, '音乐删除', 'ai:music:delete', 3, 4, 2788, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2024-06-27 15:03:33', '', '2024-06-27 15:03:33', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2792, 'AI 写作', '', 2, 3, 2758, 'write', 'fa-solid:book-reader', 'ai/write/index/index.vue', 'AiWrite', 0, + b'1', b'1', b'1', '1', '2024-07-08 09:26:44', '1', '2024-07-16 13:03:06', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2793, '写作管理', '', 2, 13, 2760, 'write', 'fa:bookmark-o', 'ai/write/manager/index.vue', 'AiWriteManager', 0, + b'1', b'1', b'1', '', '2024-07-10 13:24:34', '1', '2024-07-10 21:31:59', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2794, 'AI 写作查询', 'ai:write:query', 3, 1, 2793, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2024-07-10 13:24:34', '', '2024-07-10 13:24:34', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2795, 'AI 写作删除', 'ai:write:delete', 3, 4, 2793, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2024-07-10 13:24:34', '', '2024-07-10 13:24:34', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2796, 'AI 音乐', '', 2, 4, 2758, 'music', 'fa:music', 'ai/music/index/index.vue', 'AiMusic', 0, b'1', b'1', + b'1', '1', '2024-07-17 09:21:12', '1', '2024-07-29 21:11:52', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2797, '客服中心', '', 2, 100, 2362, 'kefu', 'fa-solid:user-alt', 'mall/promotion/kefu/index', 'KeFu', 0, b'1', + b'1', b'1', '1', '2024-07-17 23:49:05', '1', '2024-07-17 23:49:16', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2798, 'AI 思维导图', '', 2, 5, 2758, 'mind-map', 'fa:sitemap', 'ai/mindmap/index/index.vue', 'AiMindMap', 0, + b'1', b'1', b'1', '1', '2024-07-29 21:31:59', '1', '2024-07-29 21:33:20', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2799, '导图管理', '', 2, 14, 2760, 'mind-map', 'fa:map', 'ai/mindmap/manager/index', 'AiMindMapManager', 0, + b'1', b'1', b'1', '', '2024-08-10 09:15:09', '1', '2024-08-10 17:24:28', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2800, '思维导图查询', 'ai:mind-map:query', 3, 1, 2799, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2024-08-10 09:15:09', '', '2024-08-10 09:15:09', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2801, '思维导图删除', 'ai:mind-map:delete', 3, 4, 2799, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2024-08-10 09:15:09', '', '2024-08-10 09:15:09', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2802, '会话查询', 'promotion:kefu-conversation:query', 3, 1, 2797, '', '', '', '', 0, b'1', b'1', b'1', '1', + '2024-08-31 09:17:52', '1', '2024-08-31 09:18:52', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2803, '会话更新', 'promotion:kefu-conversation:update', 3, 2, 2797, '', '', '', '', 0, b'1', b'1', b'1', '1', + '2024-08-31 09:18:15', '1', '2024-08-31 09:19:29', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2804, '消息查询', 'promotion:kefu-message:query', 3, 10, 2797, '', '', '', '', 0, b'1', b'1', b'1', '1', + '2024-08-31 09:18:42', '1', '2024-08-31 09:18:42', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2805, '会话删除', 'promotion:kefu-conversation:delete', 3, 3, 2797, '', '', '', '', 0, b'1', b'1', b'1', '1', + '2024-08-31 09:19:51', '1', '2024-08-31 09:20:32', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2806, '消息发送', 'promotion:kefu-message:send', 3, 12, 2797, '', '', '', '', 0, b'1', b'1', b'1', '1', + '2024-08-31 09:20:06', '1', '2024-08-31 09:20:06', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2807, '消息更新', 'promotion:kefu-message:update', 3, 11, 2797, '', '', '', '', 0, b'1', b'1', b'1', '1', + '2024-08-31 09:20:22', '1', '2024-08-31 09:20:22', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2808, '积分商城', '', 2, 5, 2030, 'point-activity', 'ep:bowl', 'mall/promotion/point/activity/index', + 'PointActivity', 0, b'1', b'1', b'1', '', '2024-09-21 05:36:42', '1', '2024-09-23 09:14:43', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2809, '积分商城活动查询', 'promotion:point-activity:query', 3, 1, 2808, '', '', '', '', 0, b'1', b'1', b'1', '', + '2024-09-21 05:36:42', '1', '2024-09-22 14:49:05', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2810, '积分商城活动创建', 'promotion:point-activity:create', 3, 2, 2808, '', '', '', '', 0, b'1', b'1', b'1', + '', '2024-09-21 05:36:42', '1', '2024-09-22 14:49:08', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2811, '积分商城活动更新', 'promotion:point-activity:update', 3, 3, 2808, '', '', '', '', 0, b'1', b'1', b'1', + '', '2024-09-21 05:36:42', '1', '2024-09-22 14:49:10', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2812, '积分商城活动删除', 'promotion:point-activity:delete', 3, 4, 2808, '', '', '', '', 0, b'1', b'1', b'1', + '', '2024-09-21 05:36:42', '1', '2024-09-22 14:49:12', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2813, '积分商城活动导出', 'promotion:point-activity:export', 3, 5, 2808, '', '', '', '', 0, b'1', b'1', b'1', + '', '2024-09-21 05:36:42', '1', '2024-09-22 14:49:27', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2892, 'IOT 物联网', '', 1, 500, 0, '/iot', 'fa-solid:hdd', '', '', 0, b'1', b'1', b'1', '1', + '2024-08-10 09:55:29', '1', '2024-08-10 09:55:29', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2893, '设备接入', '', 1, 1, 2892, 'device', 'ep:platform', '', '', 0, b'1', b'1', b'1', '1', + '2024-08-10 09:57:56', '1', '2024-10-20 18:57:43', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2894, '产品管理', '', 2, 0, 2893, 'product', '', 'iot/product/index', 'IoTProduct', 0, b'1', b'1', b'1', '', + '2024-08-10 02:38:02', '1', '2024-09-16 19:50:42', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2895, '产品查询', 'iot:product:query', 3, 1, 2894, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2024-08-10 02:38:02', '', '2024-08-10 02:38:02', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2896, '产品创建', 'iot:product:create', 3, 2, 2894, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2024-08-10 02:38:02', '', '2024-08-10 02:38:02', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2897, '产品更新', 'iot:product:update', 3, 3, 2894, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2024-08-10 02:38:02', '', '2024-08-10 02:38:02', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2898, '产品删除', 'iot:product:delete', 3, 4, 2894, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2024-08-10 02:38:02', '', '2024-08-10 02:38:02', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2899, '产品导出', 'iot:product:export', 3, 5, 2894, '', '', '', NULL, 0, b'1', b'1', b'1', '', + '2024-08-10 02:38:02', '', '2024-08-10 02:38:02', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2900, '设备管理', '', 2, 0, 2893, 'device', '', 'iot/device/index', 'IoTDevice', 0, b'1', b'1', b'1', '', + '2024-09-16 18:48:19', '1', '2024-09-16 19:50:53', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2901, '设备查询', 'iot:device:query', 3, 1, 2900, '', '', '', '', 0, b'1', b'1', b'1', '', + '2024-09-16 18:48:19', '1', '2024-09-16 19:37:00', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2902, '设备创建', 'iot:device:create', 3, 2, 2900, '', '', '', '', 0, b'1', b'1', b'1', '', + '2024-09-16 18:48:19', '1', '2024-09-16 19:37:09', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2903, '设备更新', 'iot:device:update', 3, 3, 2900, '', '', '', '', 0, b'1', b'1', b'1', '', + '2024-09-16 18:48:19', '1', '2024-09-16 19:37:18', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2904, '设备删除', 'iot:device:delete', 3, 4, 2900, '', '', '', '', 0, b'1', b'1', b'1', '', + '2024-09-16 18:48:19', '1', '2024-09-16 19:37:42', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2905, '设备导出', 'iot:device:export', 3, 5, 2900, '', '', '', '', 0, b'1', b'1', b'1', '', + '2024-09-16 18:48:19', '1', '2024-09-16 19:37:49', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2906, 'IoT 产品物模型管理', '', 1, 0, 2893, 'think-model-function', '', '', '', 0, b'0', b'1', b'1', '', + '2024-09-25 22:12:09', '1', '2024-09-29 20:52:12', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2907, 'IoT 产品物模型查询', 'iot:think-model-function:query', 3, 1, 2906, '', '', '', NULL, 0, b'1', b'1', b'1', + '', '2024-09-25 22:12:09', '', '2024-09-25 22:12:09', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2908, 'IoT 产品物模型创建', 'iot:think-model-function:create', 3, 2, 2906, '', '', '', NULL, 0, b'1', b'1', + b'1', '', '2024-09-25 22:12:09', '', '2024-09-25 22:12:09', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2909, 'IoT 产品物模型更新', 'iot:think-model-function:update', 3, 3, 2906, '', '', '', NULL, 0, b'1', b'1', + b'1', '', '2024-09-25 22:12:09', '', '2024-09-25 22:12:09', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2910, 'IoT 产品物模型删除', 'iot:think-model-function:delete', 3, 4, 2906, '', '', '', NULL, 0, b'1', b'1', + b'1', '', '2024-09-25 22:12:09', '', '2024-09-25 22:12:09', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2911, 'IoT 产品物模型导出', 'iot:think-model-function:export', 3, 5, 2906, '', '', '', NULL, 0, b'1', b'1', + b'1', '', '2024-09-25 22:12:09', '', '2024-09-25 22:12:09', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, + `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`) +VALUES (2912, '创建推广员', 'trade:brokerage-user:create', 3, 7, 2346, '', '', '', '', 0, b'1', b'1', b'1', '1', + '2024-12-01 14:32:39', '1', '2024-12-01 14:32:39', b'0'); +COMMIT; + +-- ---------------------------- +-- Table structure for system_notice +-- ---------------------------- +DROP TABLE IF EXISTS `system_notice`; +CREATE TABLE `system_notice` +( + `id` bigint NOT NULL AUTO_INCREMENT COMMENT '公告ID', + `title` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '公告标题', + `content` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '公告内容', + `type` tinyint NOT NULL COMMENT '公告类型(1通知 2公告)', + `status` tinyint NOT NULL DEFAULT 0 COMMENT '公告状态(0正常 1关闭)', + `creator` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '创建者', + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `updater` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '更新者', + `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除', + `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 5 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '通知公告表'; + +-- ---------------------------- +-- Records of system_notice +-- ---------------------------- +BEGIN; +INSERT INTO `system_notice` (`id`, `title`, `content`, `type`, `status`, `creator`, `create_time`, `updater`, + `update_time`, `deleted`, `tenant_id`) +VALUES (1, '芋道的公众', '

新版本内容133

', 1, 0, 'admin', '2021-01-05 17:03:48', '1', '2022-05-04 21:00:20', b'0', + 1); +INSERT INTO `system_notice` (`id`, `title`, `content`, `type`, `status`, `creator`, `create_time`, `updater`, + `update_time`, `deleted`, `tenant_id`) +VALUES (2, '维护通知:2018-07-01 系统凌晨维护', + '

\"\"11112222\"image\"

', + 2, 1, 'admin', '2021-01-05 17:03:48', '1', '2024-09-24 20:48:09', b'0', 1); +INSERT INTO `system_notice` (`id`, `title`, `content`, `type`, `status`, `creator`, `create_time`, `updater`, + `update_time`, `deleted`, `tenant_id`) +VALUES (4, '我是测试标题', '

哈哈哈哈123

', 1, 0, '110', '2022-02-22 01:01:25', '110', '2022-02-22 01:01:46', b'0', + 121); +COMMIT; + +-- ---------------------------- +-- Table structure for system_notify_message +-- ---------------------------- +DROP TABLE IF EXISTS `system_notify_message`; +CREATE TABLE `system_notify_message` +( + `id` bigint NOT NULL AUTO_INCREMENT COMMENT '用户ID', + `user_id` bigint NOT NULL COMMENT '用户id', + `user_type` tinyint NOT NULL COMMENT '用户类型', + `template_id` bigint NOT NULL COMMENT '模版编号', + `template_code` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '模板编码', + `template_nickname` varchar(63) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '模版发送人名称', + `template_content` varchar(1024) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '模版内容', + `template_type` int NOT NULL COMMENT '模版类型', + `template_params` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '模版参数', + `read_status` bit(1) NOT NULL COMMENT '是否已读', + `read_time` datetime NULL DEFAULT NULL COMMENT '阅读时间', + `creator` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '创建者', + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `updater` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '更新者', + `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除', + `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 11 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '站内信消息表'; + +-- ---------------------------- +-- Records of system_notify_message +-- ---------------------------- +BEGIN; +INSERT INTO `system_notify_message` (`id`, `user_id`, `user_type`, `template_id`, `template_code`, `template_nickname`, + `template_content`, `template_type`, `template_params`, `read_status`, `read_time`, + `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) +VALUES (2, 1, 2, 1, 'test', '123', '我是 1,我开始 2 了', 1, '{\"name\":\"1\",\"what\":\"2\"}', b'1', + '2023-02-10 00:47:04', '1', '2023-01-28 11:44:08', '1', '2023-02-10 00:47:04', b'0', 1); +INSERT INTO `system_notify_message` (`id`, `user_id`, `user_type`, `template_id`, `template_code`, `template_nickname`, + `template_content`, `template_type`, `template_params`, `read_status`, `read_time`, + `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) +VALUES (3, 1, 2, 1, 'test', '123', '我是 1,我开始 2 了', 1, '{\"name\":\"1\",\"what\":\"2\"}', b'1', + '2023-02-10 00:47:04', '1', '2023-01-28 11:45:04', '1', '2023-02-10 00:47:04', b'0', 1); +INSERT INTO `system_notify_message` (`id`, `user_id`, `user_type`, `template_id`, `template_code`, `template_nickname`, + `template_content`, `template_type`, `template_params`, `read_status`, `read_time`, + `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) +VALUES (4, 103, 2, 2, 'register', '系统消息', '你好,欢迎 哈哈 加入大家庭!', 2, '{\"name\":\"哈哈\"}', b'0', NULL, '1', + '2023-01-28 21:02:20', '1', '2023-01-28 21:02:20', b'0', 1); +INSERT INTO `system_notify_message` (`id`, `user_id`, `user_type`, `template_id`, `template_code`, `template_nickname`, + `template_content`, `template_type`, `template_params`, `read_status`, `read_time`, + `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) +VALUES (5, 1, 2, 1, 'test', '123', '我是 芋艿,我开始 写代码 了', 1, '{\"name\":\"芋艿\",\"what\":\"写代码\"}', b'1', + '2023-02-10 00:47:04', '1', '2023-01-28 22:21:42', '1', '2023-02-10 00:47:04', b'0', 1); +INSERT INTO `system_notify_message` (`id`, `user_id`, `user_type`, `template_id`, `template_code`, `template_nickname`, + `template_content`, `template_type`, `template_params`, `read_status`, `read_time`, + `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) +VALUES (6, 1, 2, 1, 'test', '123', '我是 芋艿,我开始 写代码 了', 1, '{\"name\":\"芋艿\",\"what\":\"写代码\"}', b'1', + '2023-01-29 10:52:06', '1', '2023-01-28 22:22:07', '1', '2023-01-29 10:52:06', b'0', 1); +INSERT INTO `system_notify_message` (`id`, `user_id`, `user_type`, `template_id`, `template_code`, `template_nickname`, + `template_content`, `template_type`, `template_params`, `read_status`, `read_time`, + `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) +VALUES (7, 1, 2, 1, 'test', '123', '我是 2,我开始 3 了', 1, '{\"name\":\"2\",\"what\":\"3\"}', b'1', + '2023-01-29 10:52:06', '1', '2023-01-28 23:45:21', '1', '2023-01-29 10:52:06', b'0', 1); +INSERT INTO `system_notify_message` (`id`, `user_id`, `user_type`, `template_id`, `template_code`, `template_nickname`, + `template_content`, `template_type`, `template_params`, `read_status`, `read_time`, + `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) +VALUES (8, 1, 2, 2, 'register', '系统消息', '你好,欢迎 123 加入大家庭!', 2, '{\"name\":\"123\"}', b'1', + '2023-01-29 10:52:06', '1', '2023-01-28 23:50:21', '1', '2023-01-29 10:52:06', b'0', 1); +INSERT INTO `system_notify_message` (`id`, `user_id`, `user_type`, `template_id`, `template_code`, `template_nickname`, + `template_content`, `template_type`, `template_params`, `read_status`, `read_time`, + `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) +VALUES (9, 247, 1, 4, 'brokerage_withdraw_audit_approve', 'system', + '您在2023-09-28 08:35:46提现¥0.09元的申请已通过审核', 2, + '{\"reason\":null,\"createTime\":\"2023-09-28 08:35:46\",\"price\":\"0.09\"}', b'0', NULL, '1', + '2023-09-28 16:36:22', '1', '2023-09-28 16:36:22', b'0', 1); +INSERT INTO `system_notify_message` (`id`, `user_id`, `user_type`, `template_id`, `template_code`, `template_nickname`, + `template_content`, `template_type`, `template_params`, `read_status`, `read_time`, + `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) +VALUES (10, 247, 1, 4, 'brokerage_withdraw_audit_approve', 'system', + '您在2023-09-30 20:59:40提现¥1.00元的申请已通过审核', 2, + '{\"reason\":null,\"createTime\":\"2023-09-30 20:59:40\",\"price\":\"1.00\"}', b'0', NULL, '1', + '2023-10-03 12:11:34', '1', '2023-10-03 12:11:34', b'0', 1); +COMMIT; + +-- ---------------------------- +-- Table structure for system_notify_template +-- ---------------------------- +DROP TABLE IF EXISTS `system_notify_template`; +CREATE TABLE `system_notify_template` +( + `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键', + `name` varchar(63) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '模板名称', + `code` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '模版编码', + `nickname` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '发送人名称', + `content` varchar(1024) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '模版内容', + `type` tinyint NOT NULL COMMENT '类型', + `params` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '参数数组', + `status` tinyint NOT NULL COMMENT '状态', + `remark` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '备注', + `creator` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '创建者', + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `updater` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '更新者', + `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 6 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '站内信模板表'; + +-- ---------------------------- +-- Records of system_notify_template +-- ---------------------------- +BEGIN; +COMMIT; + +-- ---------------------------- +-- Table structure for system_oauth2_access_token +-- ---------------------------- +DROP TABLE IF EXISTS `system_oauth2_access_token`; +CREATE TABLE `system_oauth2_access_token` +( + `id` bigint NOT NULL AUTO_INCREMENT COMMENT '编号', + `user_id` bigint NOT NULL COMMENT '用户编号', + `user_type` tinyint NOT NULL COMMENT '用户类型', + `user_info` varchar(512) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '用户信息', + `access_token` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '访问令牌', + `refresh_token` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '刷新令牌', + `client_id` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '客户端编号', + `scopes` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '授权范围', + `expires_time` datetime NOT NULL COMMENT '过期时间', + `creator` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '创建者', + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `updater` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '更新者', + `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除', + `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号', + PRIMARY KEY (`id`) USING BTREE, + INDEX `idx_access_token`(`access_token` ASC) USING BTREE, + INDEX `idx_refresh_token`(`refresh_token` ASC) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 12055 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = 'OAuth2 访问令牌'; + +-- ---------------------------- +-- Records of system_oauth2_access_token +-- ---------------------------- +BEGIN; +COMMIT; + +-- ---------------------------- +-- Table structure for system_oauth2_approve +-- ---------------------------- +DROP TABLE IF EXISTS `system_oauth2_approve`; +CREATE TABLE `system_oauth2_approve` +( + `id` bigint NOT NULL AUTO_INCREMENT COMMENT '编号', + `user_id` bigint NOT NULL COMMENT '用户编号', + `user_type` tinyint NOT NULL COMMENT '用户类型', + `client_id` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '客户端编号', + `scope` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '授权范围', + `approved` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否接受', + `expires_time` datetime NOT NULL COMMENT '过期时间', + `creator` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '创建者', + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `updater` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '更新者', + `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除', + `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 82 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = 'OAuth2 批准表'; + +-- ---------------------------- +-- Records of system_oauth2_approve +-- ---------------------------- +BEGIN; +COMMIT; + +-- ---------------------------- +-- Table structure for system_oauth2_client +-- ---------------------------- +DROP TABLE IF EXISTS `system_oauth2_client`; +CREATE TABLE `system_oauth2_client` +( + `id` bigint NOT NULL AUTO_INCREMENT COMMENT '编号', + `client_id` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '客户端编号', + `secret` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '客户端密钥', + `name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '应用名', + `logo` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '应用图标', + `description` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '应用描述', + `status` tinyint NOT NULL COMMENT '状态', + `access_token_validity_seconds` int NOT NULL COMMENT '访问令牌的有效期', + `refresh_token_validity_seconds` int NOT NULL COMMENT '刷新令牌的有效期', + `redirect_uris` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '可重定向的 URI 地址', + `authorized_grant_types` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '授权类型', + `scopes` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '授权范围', + `auto_approve_scopes` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '自动通过的授权范围', + `authorities` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '权限', + `resource_ids` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '资源', + `additional_information` varchar(4096) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '附加信息', + `creator` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '创建者', + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `updater` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '更新者', + `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 43 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = 'OAuth2 客户端表'; + +-- ---------------------------- +-- Records of system_oauth2_client +-- ---------------------------- +BEGIN; +INSERT INTO `system_oauth2_client` (`id`, `client_id`, `secret`, `name`, `logo`, `description`, `status`, + `access_token_validity_seconds`, `refresh_token_validity_seconds`, `redirect_uris`, + `authorized_grant_types`, `scopes`, `auto_approve_scopes`, `authorities`, + `resource_ids`, `additional_information`, `creator`, `create_time`, `updater`, + `update_time`, `deleted`) +VALUES (1, 'default', 'admin123', '芋道源码', 'http://test.yudao.iocoder.cn/a5e2e244368878a366b516805a4aabf1.png', + '我是描述', 0, 1800, 2592000, '[\"https://www.iocoder.cn\",\"https://doc.iocoder.cn\"]', + '[\"password\",\"authorization_code\",\"implicit\",\"refresh_token\"]', '[\"user.read\",\"user.write\"]', '[]', + '[\"user.read\",\"user.write\"]', '[]', '{}', '1', '2022-05-11 21:47:12', '1', '2024-02-22 16:31:52', b'0'); +INSERT INTO `system_oauth2_client` (`id`, `client_id`, `secret`, `name`, `logo`, `description`, `status`, + `access_token_validity_seconds`, `refresh_token_validity_seconds`, `redirect_uris`, + `authorized_grant_types`, `scopes`, `auto_approve_scopes`, `authorities`, + `resource_ids`, `additional_information`, `creator`, `create_time`, `updater`, + `update_time`, `deleted`) +VALUES (40, 'test', 'test2', 'biubiu', 'http://test.yudao.iocoder.cn/277a899d573723f1fcdfb57340f00379.png', '啦啦啦啦', + 0, 1800, 43200, '[\"https://www.iocoder.cn\"]', '[\"password\",\"authorization_code\",\"implicit\"]', + '[\"user_info\",\"projects\"]', '[\"user_info\"]', '[]', '[]', '{}', '1', '2022-05-12 00:28:20', '1', + '2023-12-02 21:01:01', b'0'); +INSERT INTO `system_oauth2_client` (`id`, `client_id`, `secret`, `name`, `logo`, `description`, `status`, + `access_token_validity_seconds`, `refresh_token_validity_seconds`, `redirect_uris`, + `authorized_grant_types`, `scopes`, `auto_approve_scopes`, `authorities`, + `resource_ids`, `additional_information`, `creator`, `create_time`, `updater`, + `update_time`, `deleted`) +VALUES (41, 'yudao-sso-demo-by-code', 'test', '基于授权码模式,如何实现 SSO 单点登录?', + 'http://test.yudao.iocoder.cn/fe4ed36596adad5120036ef61a6d0153654544d44af8dd4ad3ffe8f759933d6f.png', NULL, 0, + 1800, 43200, '[\"http://127.0.0.1:18080\"]', '[\"authorization_code\",\"refresh_token\"]', + '[\"user.read\",\"user.write\"]', '[]', '[]', '[]', NULL, '1', '2022-09-29 13:28:31', '1', + '2022-09-29 13:28:31', b'0'); +INSERT INTO `system_oauth2_client` (`id`, `client_id`, `secret`, `name`, `logo`, `description`, `status`, + `access_token_validity_seconds`, `refresh_token_validity_seconds`, `redirect_uris`, + `authorized_grant_types`, `scopes`, `auto_approve_scopes`, `authorities`, + `resource_ids`, `additional_information`, `creator`, `create_time`, `updater`, + `update_time`, `deleted`) +VALUES (42, 'yudao-sso-demo-by-password', 'test', '基于密码模式,如何实现 SSO 单点登录?', + 'http://test.yudao.iocoder.cn/604bdc695e13b3b22745be704d1f2aa8ee05c5f26f9fead6d1ca49005afbc857.jpeg', NULL, 0, + 1800, 43200, '[\"http://127.0.0.1:18080\"]', '[\"password\",\"refresh_token\"]', + '[\"user.read\",\"user.write\"]', '[]', '[]', '[]', NULL, '1', '2022-10-04 17:40:16', '1', + '2022-10-04 20:31:21', b'0'); +COMMIT; + +-- ---------------------------- +-- Table structure for system_oauth2_code +-- ---------------------------- +DROP TABLE IF EXISTS `system_oauth2_code`; +CREATE TABLE `system_oauth2_code` +( + `id` bigint NOT NULL AUTO_INCREMENT COMMENT '编号', + `user_id` bigint NOT NULL COMMENT '用户编号', + `user_type` tinyint NOT NULL COMMENT '用户类型', + `code` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '授权码', + `client_id` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '客户端编号', + `scopes` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '授权范围', + `expires_time` datetime NOT NULL COMMENT '过期时间', + `redirect_uri` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '可重定向的 URI 地址', + `state` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '状态', + `creator` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '创建者', + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `updater` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '更新者', + `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除', + `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 147 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = 'OAuth2 授权码表'; + +-- ---------------------------- +-- Records of system_oauth2_code +-- ---------------------------- +BEGIN; +COMMIT; + +-- ---------------------------- +-- Table structure for system_oauth2_refresh_token +-- ---------------------------- +DROP TABLE IF EXISTS `system_oauth2_refresh_token`; +CREATE TABLE `system_oauth2_refresh_token` +( + `id` bigint NOT NULL AUTO_INCREMENT COMMENT '编号', + `user_id` bigint NOT NULL COMMENT '用户编号', + `refresh_token` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '刷新令牌', + `user_type` tinyint NOT NULL COMMENT '用户类型', + `client_id` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '客户端编号', + `scopes` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '授权范围', + `expires_time` datetime NOT NULL COMMENT '过期时间', + `creator` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '创建者', + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `updater` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '更新者', + `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除', + `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 1711 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = 'OAuth2 刷新令牌'; + +-- ---------------------------- +-- Records of system_oauth2_refresh_token +-- ---------------------------- +BEGIN; +COMMIT; + +-- ---------------------------- +-- Table structure for system_operate_log +-- ---------------------------- +DROP TABLE IF EXISTS `system_operate_log`; +CREATE TABLE `system_operate_log` +( + `id` bigint NOT NULL AUTO_INCREMENT COMMENT '日志主键', + `trace_id` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '链路追踪编号', + `user_id` bigint NOT NULL COMMENT '用户编号', + `user_type` tinyint NOT NULL DEFAULT 0 COMMENT '用户类型', + `type` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '操作模块类型', + `sub_type` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '操作名', + `biz_id` bigint NOT NULL COMMENT '操作数据模块编号', + `action` varchar(2000) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '操作内容', + `success` bit(1) NOT NULL DEFAULT b'1' COMMENT '操作结果', + `extra` varchar(2000) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '拓展字段', + `request_method` varchar(16) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '请求方法名', + `request_url` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '请求地址', + `user_ip` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '用户 IP', + `user_agent` varchar(512) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '浏览器 UA', + `creator` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '创建者', + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `updater` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '更新者', + `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除', + `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 9064 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '操作日志记录 V2 版本'; + +-- ---------------------------- +-- Records of system_operate_log +-- ---------------------------- +BEGIN; +COMMIT; + +-- ---------------------------- +-- Table structure for system_post +-- ---------------------------- +DROP TABLE IF EXISTS `system_post`; +CREATE TABLE `system_post` +( + `id` bigint NOT NULL AUTO_INCREMENT COMMENT '岗位ID', + `code` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '岗位编码', + `name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '岗位名称', + `sort` int NOT NULL COMMENT '显示顺序', + `status` tinyint NOT NULL COMMENT '状态(0正常 1停用)', + `remark` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '备注', + `creator` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '创建者', + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `updater` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '更新者', + `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除', + `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 6 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '岗位信息表'; + +-- ---------------------------- +-- Records of system_post +-- ---------------------------- +BEGIN; +INSERT INTO `system_post` (`id`, `code`, `name`, `sort`, `status`, `remark`, `creator`, `create_time`, `updater`, + `update_time`, `deleted`, `tenant_id`) +VALUES (1, 'ceo', '董事长', 1, 0, '', 'admin', '2021-01-06 17:03:48', '1', '2023-02-11 15:19:04', b'0', 1); +INSERT INTO `system_post` (`id`, `code`, `name`, `sort`, `status`, `remark`, `creator`, `create_time`, `updater`, + `update_time`, `deleted`, `tenant_id`) +VALUES (2, 'se', '项目经理', 2, 0, '', 'admin', '2021-01-05 17:03:48', '1', '2023-11-15 09:18:20', b'0', 1); +INSERT INTO `system_post` (`id`, `code`, `name`, `sort`, `status`, `remark`, `creator`, `create_time`, `updater`, + `update_time`, `deleted`, `tenant_id`) +VALUES (4, 'user', '普通员工', 4, 0, '111', 'admin', '2021-01-05 17:03:48', '1', '2023-12-02 10:04:37', b'0', 1); +INSERT INTO `system_post` (`id`, `code`, `name`, `sort`, `status`, `remark`, `creator`, `create_time`, `updater`, + `update_time`, `deleted`, `tenant_id`) +VALUES (5, 'HR', '人力资源', 5, 0, '', '1', '2024-03-24 20:45:40', '1', '2024-03-24 20:45:40', b'0', 1); +COMMIT; + +-- ---------------------------- +-- Table structure for system_role +-- ---------------------------- +DROP TABLE IF EXISTS `system_role`; +CREATE TABLE `system_role` +( + `id` bigint NOT NULL AUTO_INCREMENT COMMENT '角色ID', + `name` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '角色名称', + `code` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '角色权限字符串', + `sort` int NOT NULL COMMENT '显示顺序', + `data_scope` tinyint NOT NULL DEFAULT 1 COMMENT '数据范围(1:全部数据权限 2:自定数据权限 3:本部门数据权限 4:本部门及以下数据权限)', + `data_scope_dept_ids` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '数据范围(指定部门数组)', + `status` tinyint NOT NULL COMMENT '角色状态(0正常 1停用)', + `type` tinyint NOT NULL COMMENT '角色类型', + `remark` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '备注', + `creator` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '创建者', + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `updater` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '更新者', + `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除', + `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 154 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '角色信息表'; + +-- ---------------------------- +-- Records of system_role +-- ---------------------------- +BEGIN; +INSERT INTO `system_role` (`id`, `name`, `code`, `sort`, `data_scope`, `data_scope_dept_ids`, `status`, `type`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) +VALUES (1, '超级管理员', 'super_admin', 1, 1, '', 0, 1, '超级管理员', 'admin', '2021-01-05 17:03:48', '', + '2022-02-22 05:08:21', b'0', 1); +INSERT INTO `system_role` (`id`, `name`, `code`, `sort`, `data_scope`, `data_scope_dept_ids`, `status`, `type`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) +VALUES (2, '普通角色', 'common', 2, 2, '', 0, 1, '普通角色', 'admin', '2021-01-05 17:03:48', '', '2022-02-22 05:08:20', + b'0', 1); +INSERT INTO `system_role` (`id`, `name`, `code`, `sort`, `data_scope`, `data_scope_dept_ids`, `status`, `type`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) +VALUES (3, 'CRM 管理员', 'crm_admin', 2, 1, '', 0, 1, 'CRM 专属角色', '1', '2024-02-24 10:51:13', '1', + '2024-02-24 02:51:32', b'0', 1); +INSERT INTO `system_role` (`id`, `name`, `code`, `sort`, `data_scope`, `data_scope_dept_ids`, `status`, `type`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) +VALUES (101, '测试账号', 'test', 0, 1, '[]', 0, 2, '', '', '2021-01-06 13:49:35', '1', '2024-08-11 10:41:10', b'0', 1); +INSERT INTO `system_role` (`id`, `name`, `code`, `sort`, `data_scope`, `data_scope_dept_ids`, `status`, `type`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) +VALUES (109, '租户管理员', 'tenant_admin', 0, 1, '', 0, 1, '系统自动生成', '1', '2022-02-22 00:56:14', '1', + '2022-02-22 00:56:14', b'0', 121); +INSERT INTO `system_role` (`id`, `name`, `code`, `sort`, `data_scope`, `data_scope_dept_ids`, `status`, `type`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) +VALUES (111, '租户管理员', 'tenant_admin', 0, 1, '', 0, 1, '系统自动生成', '1', '2022-03-07 21:37:58', '1', + '2022-03-07 21:37:58', b'0', 122); +INSERT INTO `system_role` (`id`, `name`, `code`, `sort`, `data_scope`, `data_scope_dept_ids`, `status`, `type`, + `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) +VALUES (153, '某角色', 'tt', 4, 1, '', 0, 2, '', '1', '2024-08-17 14:09:35', '1', '2024-08-17 14:09:35', b'0', 1); +COMMIT; + +-- ---------------------------- +-- Table structure for system_role_menu +-- ---------------------------- +DROP TABLE IF EXISTS `system_role_menu`; +CREATE TABLE `system_role_menu` +( + `id` bigint NOT NULL AUTO_INCREMENT COMMENT '自增编号', + `role_id` bigint NOT NULL COMMENT '角色ID', + `menu_id` bigint NOT NULL COMMENT '菜单ID', + `creator` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '创建者', + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `updater` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '更新者', + `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除', + `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 5793 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '角色和菜单关联表'; + +-- ---------------------------- +-- Records of system_role_menu +-- ---------------------------- +BEGIN; +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (263, 109, 1, '1', '2022-02-22 00:56:14', '1', '2022-02-22 00:56:14', b'0', 121); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (434, 2, 1, '1', '2022-02-22 13:09:12', '1', '2022-02-22 13:09:12', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (454, 2, 1093, '1', '2022-02-22 13:09:12', '1', '2022-02-22 13:09:12', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (455, 2, 1094, '1', '2022-02-22 13:09:12', '1', '2022-02-22 13:09:12', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (460, 2, 1100, '1', '2022-02-22 13:09:12', '1', '2022-02-22 13:09:12', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (467, 2, 1107, '1', '2022-02-22 13:09:12', '1', '2022-02-22 13:09:12', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (476, 2, 1117, '1', '2022-02-22 13:09:12', '1', '2022-02-22 13:09:12', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (477, 2, 100, '1', '2022-02-22 13:09:12', '1', '2022-02-22 13:09:12', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (478, 2, 101, '1', '2022-02-22 13:09:12', '1', '2022-02-22 13:09:12', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (479, 2, 102, '1', '2022-02-22 13:09:12', '1', '2022-02-22 13:09:12', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (480, 2, 1126, '1', '2022-02-22 13:09:12', '1', '2022-02-22 13:09:12', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (481, 2, 103, '1', '2022-02-22 13:09:12', '1', '2022-02-22 13:09:12', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (483, 2, 104, '1', '2022-02-22 13:09:12', '1', '2022-02-22 13:09:12', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (485, 2, 105, '1', '2022-02-22 13:09:12', '1', '2022-02-22 13:09:12', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (488, 2, 107, '1', '2022-02-22 13:09:12', '1', '2022-02-22 13:09:12', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (490, 2, 108, '1', '2022-02-22 13:09:12', '1', '2022-02-22 13:09:12', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (492, 2, 109, '1', '2022-02-22 13:09:12', '1', '2022-02-22 13:09:12', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (498, 2, 1138, '1', '2022-02-22 13:09:12', '1', '2022-02-22 13:09:12', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (523, 2, 1224, '1', '2022-02-22 13:09:12', '1', '2022-02-22 13:09:12', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (524, 2, 1225, '1', '2022-02-22 13:09:12', '1', '2022-02-22 13:09:12', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (541, 2, 500, '1', '2022-02-22 13:09:12', '1', '2022-02-22 13:09:12', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (543, 2, 501, '1', '2022-02-22 13:09:12', '1', '2022-02-22 13:09:12', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (675, 2, 2, '1', '2022-02-22 13:16:57', '1', '2022-02-22 13:16:57', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (689, 2, 1077, '1', '2022-02-22 13:16:57', '1', '2022-02-22 13:16:57', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (690, 2, 1078, '1', '2022-02-22 13:16:57', '1', '2022-02-22 13:16:57', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (692, 2, 1083, '1', '2022-02-22 13:16:57', '1', '2022-02-22 13:16:57', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (693, 2, 1084, '1', '2022-02-22 13:16:57', '1', '2022-02-22 13:16:57', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (699, 2, 1090, '1', '2022-02-22 13:16:57', '1', '2022-02-22 13:16:57', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (703, 2, 106, '1', '2022-02-22 13:16:57', '1', '2022-02-22 13:16:57', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (704, 2, 110, '1', '2022-02-22 13:16:57', '1', '2022-02-22 13:16:57', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (705, 2, 111, '1', '2022-02-22 13:16:57', '1', '2022-02-22 13:16:57', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (706, 2, 112, '1', '2022-02-22 13:16:57', '1', '2022-02-22 13:16:57', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (707, 2, 113, '1', '2022-02-22 13:16:57', '1', '2022-02-22 13:16:57', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (1296, 110, 1, '110', '2022-02-23 00:23:55', '110', '2022-02-23 00:23:55', b'0', 121); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (1578, 111, 1, '1', '2022-03-07 21:37:58', '1', '2022-03-07 21:37:58', b'0', 122); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (1604, 101, 1216, '1', '2022-03-19 21:45:52', '1', '2022-03-19 21:45:52', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (1605, 101, 1217, '1', '2022-03-19 21:45:52', '1', '2022-03-19 21:45:52', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (1606, 101, 1218, '1', '2022-03-19 21:45:52', '1', '2022-03-19 21:45:52', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (1607, 101, 1219, '1', '2022-03-19 21:45:52', '1', '2022-03-19 21:45:52', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (1608, 101, 1220, '1', '2022-03-19 21:45:52', '1', '2022-03-19 21:45:52', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (1609, 101, 1221, '1', '2022-03-19 21:45:52', '1', '2022-03-19 21:45:52', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (1610, 101, 5, '1', '2022-03-19 21:45:52', '1', '2022-03-19 21:45:52', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (1611, 101, 1222, '1', '2022-03-19 21:45:52', '1', '2022-03-19 21:45:52', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (1612, 101, 1118, '1', '2022-03-19 21:45:52', '1', '2022-03-19 21:45:52', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (1613, 101, 1119, '1', '2022-03-19 21:45:52', '1', '2022-03-19 21:45:52', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (1614, 101, 1120, '1', '2022-03-19 21:45:52', '1', '2022-03-19 21:45:52', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (1615, 101, 1185, '1', '2022-03-19 21:45:52', '1', '2022-03-19 21:45:52', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (1616, 101, 1186, '1', '2022-03-19 21:45:52', '1', '2022-03-19 21:45:52', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (1617, 101, 1187, '1', '2022-03-19 21:45:52', '1', '2022-03-19 21:45:52', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (1618, 101, 1188, '1', '2022-03-19 21:45:52', '1', '2022-03-19 21:45:52', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (1619, 101, 1189, '1', '2022-03-19 21:45:52', '1', '2022-03-19 21:45:52', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (1620, 101, 1190, '1', '2022-03-19 21:45:52', '1', '2022-03-19 21:45:52', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (1621, 101, 1191, '1', '2022-03-19 21:45:52', '1', '2022-03-19 21:45:52', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (1622, 101, 1192, '1', '2022-03-19 21:45:52', '1', '2022-03-19 21:45:52', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (1623, 101, 1193, '1', '2022-03-19 21:45:52', '1', '2022-03-19 21:45:52', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (1624, 101, 1194, '1', '2022-03-19 21:45:52', '1', '2022-03-19 21:45:52', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (1625, 101, 1195, '1', '2022-03-19 21:45:52', '1', '2022-03-19 21:45:52', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (1627, 101, 1197, '1', '2022-03-19 21:45:52', '1', '2022-03-19 21:45:52', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (1628, 101, 1198, '1', '2022-03-19 21:45:52', '1', '2022-03-19 21:45:52', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (1629, 101, 1199, '1', '2022-03-19 21:45:52', '1', '2022-03-19 21:45:52', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (1630, 101, 1200, '1', '2022-03-19 21:45:52', '1', '2022-03-19 21:45:52', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (1631, 101, 1201, '1', '2022-03-19 21:45:52', '1', '2022-03-19 21:45:52', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (1632, 101, 1202, '1', '2022-03-19 21:45:52', '1', '2022-03-19 21:45:52', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (1633, 101, 1207, '1', '2022-03-19 21:45:52', '1', '2022-03-19 21:45:52', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (1634, 101, 1208, '1', '2022-03-19 21:45:52', '1', '2022-03-19 21:45:52', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (1635, 101, 1209, '1', '2022-03-19 21:45:52', '1', '2022-03-19 21:45:52', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (1636, 101, 1210, '1', '2022-03-19 21:45:52', '1', '2022-03-19 21:45:52', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (1637, 101, 1211, '1', '2022-03-19 21:45:52', '1', '2022-03-19 21:45:52', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (1638, 101, 1212, '1', '2022-03-19 21:45:52', '1', '2022-03-19 21:45:52', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (1639, 101, 1213, '1', '2022-03-19 21:45:52', '1', '2022-03-19 21:45:52', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (1640, 101, 1215, '1', '2022-03-19 21:45:52', '1', '2022-03-19 21:45:52', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (1641, 101, 2, '1', '2022-04-01 22:21:24', '1', '2022-04-01 22:21:24', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (1642, 101, 1031, '1', '2022-04-01 22:21:37', '1', '2022-04-01 22:21:37', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (1643, 101, 1032, '1', '2022-04-01 22:21:37', '1', '2022-04-01 22:21:37', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (1644, 101, 1033, '1', '2022-04-01 22:21:37', '1', '2022-04-01 22:21:37', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (1645, 101, 1034, '1', '2022-04-01 22:21:37', '1', '2022-04-01 22:21:37', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (1646, 101, 1035, '1', '2022-04-01 22:21:37', '1', '2022-04-01 22:21:37', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (1647, 101, 1050, '1', '2022-04-01 22:21:37', '1', '2022-04-01 22:21:37', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (1648, 101, 1051, '1', '2022-04-01 22:21:37', '1', '2022-04-01 22:21:37', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (1649, 101, 1052, '1', '2022-04-01 22:21:37', '1', '2022-04-01 22:21:37', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (1650, 101, 1053, '1', '2022-04-01 22:21:37', '1', '2022-04-01 22:21:37', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (1651, 101, 1054, '1', '2022-04-01 22:21:37', '1', '2022-04-01 22:21:37', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (1652, 101, 1056, '1', '2022-04-01 22:21:37', '1', '2022-04-01 22:21:37', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (1653, 101, 1057, '1', '2022-04-01 22:21:37', '1', '2022-04-01 22:21:37', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (1654, 101, 1058, '1', '2022-04-01 22:21:37', '1', '2022-04-01 22:21:37', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (1655, 101, 1059, '1', '2022-04-01 22:21:37', '1', '2022-04-01 22:21:37', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (1656, 101, 1060, '1', '2022-04-01 22:21:37', '1', '2022-04-01 22:21:37', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (1657, 101, 1066, '1', '2022-04-01 22:21:37', '1', '2022-04-01 22:21:37', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (1658, 101, 1067, '1', '2022-04-01 22:21:37', '1', '2022-04-01 22:21:37', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (1659, 101, 1070, '1', '2022-04-01 22:21:37', '1', '2022-04-01 22:21:37', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (1664, 101, 1075, '1', '2022-04-01 22:21:37', '1', '2022-04-01 22:21:37', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (1666, 101, 1077, '1', '2022-04-01 22:21:37', '1', '2022-04-01 22:21:37', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (1667, 101, 1078, '1', '2022-04-01 22:21:37', '1', '2022-04-01 22:21:37', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (1668, 101, 1082, '1', '2022-04-01 22:21:37', '1', '2022-04-01 22:21:37', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (1669, 101, 1083, '1', '2022-04-01 22:21:37', '1', '2022-04-01 22:21:37', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (1670, 101, 1084, '1', '2022-04-01 22:21:37', '1', '2022-04-01 22:21:37', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (1671, 101, 1085, '1', '2022-04-01 22:21:37', '1', '2022-04-01 22:21:37', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (1672, 101, 1086, '1', '2022-04-01 22:21:37', '1', '2022-04-01 22:21:37', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (1673, 101, 1087, '1', '2022-04-01 22:21:37', '1', '2022-04-01 22:21:37', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (1674, 101, 1088, '1', '2022-04-01 22:21:37', '1', '2022-04-01 22:21:37', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (1675, 101, 1089, '1', '2022-04-01 22:21:37', '1', '2022-04-01 22:21:37', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (1679, 101, 1237, '1', '2022-04-01 22:21:37', '1', '2022-04-01 22:21:37', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (1680, 101, 1238, '1', '2022-04-01 22:21:37', '1', '2022-04-01 22:21:37', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (1681, 101, 1239, '1', '2022-04-01 22:21:37', '1', '2022-04-01 22:21:37', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (1682, 101, 1240, '1', '2022-04-01 22:21:37', '1', '2022-04-01 22:21:37', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (1683, 101, 1241, '1', '2022-04-01 22:21:37', '1', '2022-04-01 22:21:37', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (1684, 101, 1242, '1', '2022-04-01 22:21:37', '1', '2022-04-01 22:21:37', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (1685, 101, 1243, '1', '2022-04-01 22:21:37', '1', '2022-04-01 22:21:37', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (1687, 101, 106, '1', '2022-04-01 22:21:37', '1', '2022-04-01 22:21:37', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (1688, 101, 110, '1', '2022-04-01 22:21:37', '1', '2022-04-01 22:21:37', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (1689, 101, 111, '1', '2022-04-01 22:21:37', '1', '2022-04-01 22:21:37', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (1690, 101, 112, '1', '2022-04-01 22:21:37', '1', '2022-04-01 22:21:37', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (1691, 101, 113, '1', '2022-04-01 22:21:37', '1', '2022-04-01 22:21:37', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (1692, 101, 114, '1', '2022-04-01 22:21:37', '1', '2022-04-01 22:21:37', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (1693, 101, 115, '1', '2022-04-01 22:21:37', '1', '2022-04-01 22:21:37', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (1694, 101, 116, '1', '2022-04-01 22:21:37', '1', '2022-04-01 22:21:37', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (1729, 109, 100, '1', '2022-09-21 22:08:51', '1', '2022-09-21 22:08:51', b'0', 121); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (1730, 109, 101, '1', '2022-09-21 22:08:51', '1', '2022-09-21 22:08:51', b'0', 121); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (1731, 109, 1063, '1', '2022-09-21 22:08:51', '1', '2022-09-21 22:08:51', b'0', 121); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (1732, 109, 1064, '1', '2022-09-21 22:08:51', '1', '2022-09-21 22:08:51', b'0', 121); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (1733, 109, 1001, '1', '2022-09-21 22:08:51', '1', '2022-09-21 22:08:51', b'0', 121); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (1734, 109, 1065, '1', '2022-09-21 22:08:51', '1', '2022-09-21 22:08:51', b'0', 121); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (1735, 109, 1002, '1', '2022-09-21 22:08:51', '1', '2022-09-21 22:08:51', b'0', 121); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (1736, 109, 1003, '1', '2022-09-21 22:08:51', '1', '2022-09-21 22:08:51', b'0', 121); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (1737, 109, 1004, '1', '2022-09-21 22:08:51', '1', '2022-09-21 22:08:51', b'0', 121); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (1738, 109, 1005, '1', '2022-09-21 22:08:51', '1', '2022-09-21 22:08:51', b'0', 121); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (1739, 109, 1006, '1', '2022-09-21 22:08:51', '1', '2022-09-21 22:08:51', b'0', 121); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (1740, 109, 1007, '1', '2022-09-21 22:08:51', '1', '2022-09-21 22:08:51', b'0', 121); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (1741, 109, 1008, '1', '2022-09-21 22:08:51', '1', '2022-09-21 22:08:51', b'0', 121); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (1742, 109, 1009, '1', '2022-09-21 22:08:51', '1', '2022-09-21 22:08:51', b'0', 121); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (1743, 109, 1010, '1', '2022-09-21 22:08:51', '1', '2022-09-21 22:08:51', b'0', 121); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (1744, 109, 1011, '1', '2022-09-21 22:08:51', '1', '2022-09-21 22:08:51', b'0', 121); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (1745, 109, 1012, '1', '2022-09-21 22:08:51', '1', '2022-09-21 22:08:51', b'0', 121); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (1746, 111, 100, '1', '2022-09-21 22:08:52', '1', '2022-09-21 22:08:52', b'0', 122); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (1747, 111, 101, '1', '2022-09-21 22:08:52', '1', '2022-09-21 22:08:52', b'0', 122); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (1748, 111, 1063, '1', '2022-09-21 22:08:52', '1', '2022-09-21 22:08:52', b'0', 122); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (1749, 111, 1064, '1', '2022-09-21 22:08:52', '1', '2022-09-21 22:08:52', b'0', 122); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (1750, 111, 1001, '1', '2022-09-21 22:08:52', '1', '2022-09-21 22:08:52', b'0', 122); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (1751, 111, 1065, '1', '2022-09-21 22:08:52', '1', '2022-09-21 22:08:52', b'0', 122); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (1752, 111, 1002, '1', '2022-09-21 22:08:52', '1', '2022-09-21 22:08:52', b'0', 122); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (1753, 111, 1003, '1', '2022-09-21 22:08:52', '1', '2022-09-21 22:08:52', b'0', 122); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (1754, 111, 1004, '1', '2022-09-21 22:08:52', '1', '2022-09-21 22:08:52', b'0', 122); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (1755, 111, 1005, '1', '2022-09-21 22:08:52', '1', '2022-09-21 22:08:52', b'0', 122); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (1756, 111, 1006, '1', '2022-09-21 22:08:52', '1', '2022-09-21 22:08:52', b'0', 122); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (1757, 111, 1007, '1', '2022-09-21 22:08:52', '1', '2022-09-21 22:08:52', b'0', 122); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (1758, 111, 1008, '1', '2022-09-21 22:08:52', '1', '2022-09-21 22:08:52', b'0', 122); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (1759, 111, 1009, '1', '2022-09-21 22:08:52', '1', '2022-09-21 22:08:52', b'0', 122); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (1760, 111, 1010, '1', '2022-09-21 22:08:52', '1', '2022-09-21 22:08:52', b'0', 122); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (1761, 111, 1011, '1', '2022-09-21 22:08:52', '1', '2022-09-21 22:08:52', b'0', 122); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (1762, 111, 1012, '1', '2022-09-21 22:08:52', '1', '2022-09-21 22:08:52', b'0', 122); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (1763, 109, 100, '1', '2022-09-21 22:08:53', '1', '2022-09-21 22:08:53', b'0', 121); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (1764, 109, 101, '1', '2022-09-21 22:08:53', '1', '2022-09-21 22:08:53', b'0', 121); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (1765, 109, 1063, '1', '2022-09-21 22:08:53', '1', '2022-09-21 22:08:53', b'0', 121); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (1766, 109, 1064, '1', '2022-09-21 22:08:53', '1', '2022-09-21 22:08:53', b'0', 121); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (1767, 109, 1001, '1', '2022-09-21 22:08:53', '1', '2022-09-21 22:08:53', b'0', 121); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (1768, 109, 1065, '1', '2022-09-21 22:08:53', '1', '2022-09-21 22:08:53', b'0', 121); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (1769, 109, 1002, '1', '2022-09-21 22:08:53', '1', '2022-09-21 22:08:53', b'0', 121); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (1770, 109, 1003, '1', '2022-09-21 22:08:53', '1', '2022-09-21 22:08:53', b'0', 121); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (1771, 109, 1004, '1', '2022-09-21 22:08:53', '1', '2022-09-21 22:08:53', b'0', 121); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (1772, 109, 1005, '1', '2022-09-21 22:08:53', '1', '2022-09-21 22:08:53', b'0', 121); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (1773, 109, 1006, '1', '2022-09-21 22:08:53', '1', '2022-09-21 22:08:53', b'0', 121); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (1774, 109, 1007, '1', '2022-09-21 22:08:53', '1', '2022-09-21 22:08:53', b'0', 121); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (1775, 109, 1008, '1', '2022-09-21 22:08:53', '1', '2022-09-21 22:08:53', b'0', 121); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (1776, 109, 1009, '1', '2022-09-21 22:08:53', '1', '2022-09-21 22:08:53', b'0', 121); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (1777, 109, 1010, '1', '2022-09-21 22:08:53', '1', '2022-09-21 22:08:53', b'0', 121); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (1778, 109, 1011, '1', '2022-09-21 22:08:53', '1', '2022-09-21 22:08:53', b'0', 121); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (1779, 109, 1012, '1', '2022-09-21 22:08:53', '1', '2022-09-21 22:08:53', b'0', 121); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (1780, 111, 100, '1', '2022-09-21 22:08:54', '1', '2022-09-21 22:08:54', b'0', 122); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (1781, 111, 101, '1', '2022-09-21 22:08:54', '1', '2022-09-21 22:08:54', b'0', 122); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (1782, 111, 1063, '1', '2022-09-21 22:08:54', '1', '2022-09-21 22:08:54', b'0', 122); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (1783, 111, 1064, '1', '2022-09-21 22:08:54', '1', '2022-09-21 22:08:54', b'0', 122); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (1784, 111, 1001, '1', '2022-09-21 22:08:54', '1', '2022-09-21 22:08:54', b'0', 122); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (1785, 111, 1065, '1', '2022-09-21 22:08:54', '1', '2022-09-21 22:08:54', b'0', 122); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (1786, 111, 1002, '1', '2022-09-21 22:08:54', '1', '2022-09-21 22:08:54', b'0', 122); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (1787, 111, 1003, '1', '2022-09-21 22:08:54', '1', '2022-09-21 22:08:54', b'0', 122); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (1788, 111, 1004, '1', '2022-09-21 22:08:54', '1', '2022-09-21 22:08:54', b'0', 122); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (1789, 111, 1005, '1', '2022-09-21 22:08:54', '1', '2022-09-21 22:08:54', b'0', 122); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (1790, 111, 1006, '1', '2022-09-21 22:08:54', '1', '2022-09-21 22:08:54', b'0', 122); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (1791, 111, 1007, '1', '2022-09-21 22:08:54', '1', '2022-09-21 22:08:54', b'0', 122); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (1792, 111, 1008, '1', '2022-09-21 22:08:54', '1', '2022-09-21 22:08:54', b'0', 122); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (1793, 111, 1009, '1', '2022-09-21 22:08:54', '1', '2022-09-21 22:08:54', b'0', 122); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (1794, 111, 1010, '1', '2022-09-21 22:08:54', '1', '2022-09-21 22:08:54', b'0', 122); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (1795, 111, 1011, '1', '2022-09-21 22:08:54', '1', '2022-09-21 22:08:54', b'0', 122); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (1796, 111, 1012, '1', '2022-09-21 22:08:54', '1', '2022-09-21 22:08:54', b'0', 122); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (1797, 109, 100, '1', '2022-09-21 22:08:55', '1', '2022-09-21 22:08:55', b'0', 121); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (1798, 109, 101, '1', '2022-09-21 22:08:55', '1', '2022-09-21 22:08:55', b'0', 121); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (1799, 109, 1063, '1', '2022-09-21 22:08:55', '1', '2022-09-21 22:08:55', b'0', 121); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (1800, 109, 1064, '1', '2022-09-21 22:08:55', '1', '2022-09-21 22:08:55', b'0', 121); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (1801, 109, 1001, '1', '2022-09-21 22:08:55', '1', '2022-09-21 22:08:55', b'0', 121); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (1802, 109, 1065, '1', '2022-09-21 22:08:55', '1', '2022-09-21 22:08:55', b'0', 121); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (1803, 109, 1002, '1', '2022-09-21 22:08:55', '1', '2022-09-21 22:08:55', b'0', 121); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (1804, 109, 1003, '1', '2022-09-21 22:08:55', '1', '2022-09-21 22:08:55', b'0', 121); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (1805, 109, 1004, '1', '2022-09-21 22:08:55', '1', '2022-09-21 22:08:55', b'0', 121); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (1806, 109, 1005, '1', '2022-09-21 22:08:55', '1', '2022-09-21 22:08:55', b'0', 121); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (1807, 109, 1006, '1', '2022-09-21 22:08:55', '1', '2022-09-21 22:08:55', b'0', 121); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (1808, 109, 1007, '1', '2022-09-21 22:08:55', '1', '2022-09-21 22:08:55', b'0', 121); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (1809, 109, 1008, '1', '2022-09-21 22:08:55', '1', '2022-09-21 22:08:55', b'0', 121); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (1810, 109, 1009, '1', '2022-09-21 22:08:55', '1', '2022-09-21 22:08:55', b'0', 121); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (1811, 109, 1010, '1', '2022-09-21 22:08:55', '1', '2022-09-21 22:08:55', b'0', 121); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (1812, 109, 1011, '1', '2022-09-21 22:08:55', '1', '2022-09-21 22:08:55', b'0', 121); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (1813, 109, 1012, '1', '2022-09-21 22:08:55', '1', '2022-09-21 22:08:55', b'0', 121); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (1814, 111, 100, '1', '2022-09-21 22:08:56', '1', '2022-09-21 22:08:56', b'0', 122); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (1815, 111, 101, '1', '2022-09-21 22:08:56', '1', '2022-09-21 22:08:56', b'0', 122); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (1816, 111, 1063, '1', '2022-09-21 22:08:56', '1', '2022-09-21 22:08:56', b'0', 122); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (1817, 111, 1064, '1', '2022-09-21 22:08:56', '1', '2022-09-21 22:08:56', b'0', 122); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (1818, 111, 1001, '1', '2022-09-21 22:08:56', '1', '2022-09-21 22:08:56', b'0', 122); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (1819, 111, 1065, '1', '2022-09-21 22:08:56', '1', '2022-09-21 22:08:56', b'0', 122); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (1820, 111, 1002, '1', '2022-09-21 22:08:56', '1', '2022-09-21 22:08:56', b'0', 122); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (1821, 111, 1003, '1', '2022-09-21 22:08:56', '1', '2022-09-21 22:08:56', b'0', 122); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (1822, 111, 1004, '1', '2022-09-21 22:08:56', '1', '2022-09-21 22:08:56', b'0', 122); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (1823, 111, 1005, '1', '2022-09-21 22:08:56', '1', '2022-09-21 22:08:56', b'0', 122); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (1824, 111, 1006, '1', '2022-09-21 22:08:56', '1', '2022-09-21 22:08:56', b'0', 122); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (1825, 111, 1007, '1', '2022-09-21 22:08:56', '1', '2022-09-21 22:08:56', b'0', 122); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (1826, 111, 1008, '1', '2022-09-21 22:08:56', '1', '2022-09-21 22:08:56', b'0', 122); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (1827, 111, 1009, '1', '2022-09-21 22:08:56', '1', '2022-09-21 22:08:56', b'0', 122); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (1828, 111, 1010, '1', '2022-09-21 22:08:56', '1', '2022-09-21 22:08:56', b'0', 122); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (1829, 111, 1011, '1', '2022-09-21 22:08:56', '1', '2022-09-21 22:08:56', b'0', 122); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (1830, 111, 1012, '1', '2022-09-21 22:08:56', '1', '2022-09-21 22:08:56', b'0', 122); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (1831, 109, 103, '1', '2022-09-21 22:43:23', '1', '2022-09-21 22:43:23', b'0', 121); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (1832, 109, 1017, '1', '2022-09-21 22:43:23', '1', '2022-09-21 22:43:23', b'0', 121); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (1833, 109, 1018, '1', '2022-09-21 22:43:23', '1', '2022-09-21 22:43:23', b'0', 121); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (1834, 109, 1019, '1', '2022-09-21 22:43:23', '1', '2022-09-21 22:43:23', b'0', 121); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (1835, 109, 1020, '1', '2022-09-21 22:43:23', '1', '2022-09-21 22:43:23', b'0', 121); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (1836, 111, 103, '1', '2022-09-21 22:43:24', '1', '2022-09-21 22:43:24', b'0', 122); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (1837, 111, 1017, '1', '2022-09-21 22:43:24', '1', '2022-09-21 22:43:24', b'0', 122); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (1838, 111, 1018, '1', '2022-09-21 22:43:24', '1', '2022-09-21 22:43:24', b'0', 122); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (1839, 111, 1019, '1', '2022-09-21 22:43:24', '1', '2022-09-21 22:43:24', b'0', 122); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (1840, 111, 1020, '1', '2022-09-21 22:43:24', '1', '2022-09-21 22:43:24', b'0', 122); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (1841, 109, 1036, '1', '2022-09-21 22:48:13', '1', '2022-09-21 22:48:13', b'0', 121); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (1842, 109, 1037, '1', '2022-09-21 22:48:13', '1', '2022-09-21 22:48:13', b'0', 121); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (1843, 109, 1038, '1', '2022-09-21 22:48:13', '1', '2022-09-21 22:48:13', b'0', 121); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (1844, 109, 1039, '1', '2022-09-21 22:48:13', '1', '2022-09-21 22:48:13', b'0', 121); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (1845, 109, 107, '1', '2022-09-21 22:48:13', '1', '2022-09-21 22:48:13', b'0', 121); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (1846, 111, 1036, '1', '2022-09-21 22:48:13', '1', '2022-09-21 22:48:13', b'0', 122); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (1847, 111, 1037, '1', '2022-09-21 22:48:13', '1', '2022-09-21 22:48:13', b'0', 122); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (1848, 111, 1038, '1', '2022-09-21 22:48:13', '1', '2022-09-21 22:48:13', b'0', 122); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (1849, 111, 1039, '1', '2022-09-21 22:48:13', '1', '2022-09-21 22:48:13', b'0', 122); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (1850, 111, 107, '1', '2022-09-21 22:48:13', '1', '2022-09-21 22:48:13', b'0', 122); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (1991, 2, 1024, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (1992, 2, 1025, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (1993, 2, 1026, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (1994, 2, 1027, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (1995, 2, 1028, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (1996, 2, 1029, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (1997, 2, 1030, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (1998, 2, 1031, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (1999, 2, 1032, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2000, 2, 1033, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2001, 2, 1034, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2002, 2, 1035, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2003, 2, 1036, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2004, 2, 1037, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2005, 2, 1038, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2006, 2, 1039, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2007, 2, 1040, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2008, 2, 1042, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2009, 2, 1043, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2010, 2, 1045, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2011, 2, 1046, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2012, 2, 1048, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2013, 2, 1050, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2014, 2, 1051, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2015, 2, 1052, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2016, 2, 1053, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2017, 2, 1054, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2018, 2, 1056, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2019, 2, 1057, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2020, 2, 1058, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2021, 2, 2083, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2022, 2, 1059, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2023, 2, 1060, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2024, 2, 1063, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2025, 2, 1064, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2026, 2, 1065, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2027, 2, 1066, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2028, 2, 1067, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2029, 2, 1070, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2034, 2, 1075, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2036, 2, 1082, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2037, 2, 1085, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2038, 2, 1086, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2039, 2, 1087, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2040, 2, 1088, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2041, 2, 1089, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2042, 2, 1091, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2043, 2, 1092, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2044, 2, 1095, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2045, 2, 1096, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2046, 2, 1097, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2047, 2, 1098, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2048, 2, 1101, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2049, 2, 1102, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2050, 2, 1103, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2051, 2, 1104, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2052, 2, 1105, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2053, 2, 1106, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2054, 2, 1108, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2055, 2, 1109, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2061, 2, 1127, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2062, 2, 1128, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2063, 2, 1129, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2064, 2, 1130, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2066, 2, 1132, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2067, 2, 1133, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2068, 2, 1134, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2069, 2, 1135, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2070, 2, 1136, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2071, 2, 1137, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2072, 2, 114, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2073, 2, 1139, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2074, 2, 115, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2075, 2, 1140, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2076, 2, 116, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2077, 2, 1141, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2078, 2, 1142, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2079, 2, 1143, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2080, 2, 1150, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2081, 2, 1161, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2082, 2, 1162, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2083, 2, 1163, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2084, 2, 1164, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2085, 2, 1165, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2086, 2, 1166, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2087, 2, 1173, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2088, 2, 1174, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2089, 2, 1175, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2090, 2, 1176, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2091, 2, 1177, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2092, 2, 1178, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2099, 2, 1226, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2100, 2, 1227, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2101, 2, 1228, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2102, 2, 1229, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2103, 2, 1237, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2104, 2, 1238, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2105, 2, 1239, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2106, 2, 1240, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2107, 2, 1241, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2108, 2, 1242, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2109, 2, 1243, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2116, 2, 1254, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2117, 2, 1255, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2118, 2, 1256, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2119, 2, 1257, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2120, 2, 1258, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2121, 2, 1259, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2122, 2, 1260, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2123, 2, 1261, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2124, 2, 1263, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2125, 2, 1264, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2126, 2, 1265, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2127, 2, 1266, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2128, 2, 1267, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2129, 2, 1001, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2130, 2, 1002, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2131, 2, 1003, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2132, 2, 1004, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2133, 2, 1005, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2134, 2, 1006, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2135, 2, 1007, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2136, 2, 1008, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2137, 2, 1009, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2138, 2, 1010, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2139, 2, 1011, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2140, 2, 1012, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2141, 2, 1013, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2142, 2, 1014, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2143, 2, 1015, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2144, 2, 1016, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2145, 2, 1017, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2146, 2, 1018, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2147, 2, 1019, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2148, 2, 1020, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2149, 2, 1021, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2150, 2, 1022, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2151, 2, 1023, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2152, 2, 1281, '1', '2023-01-25 08:42:58', '1', '2023-01-25 08:42:58', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2153, 2, 1282, '1', '2023-01-25 08:42:58', '1', '2023-01-25 08:42:58', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2154, 2, 2000, '1', '2023-01-25 08:42:58', '1', '2023-01-25 08:42:58', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2155, 2, 2002, '1', '2023-01-25 08:42:58', '1', '2023-01-25 08:42:58', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2156, 2, 2003, '1', '2023-01-25 08:42:58', '1', '2023-01-25 08:42:58', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2157, 2, 2004, '1', '2023-01-25 08:42:58', '1', '2023-01-25 08:42:58', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2158, 2, 2005, '1', '2023-01-25 08:42:58', '1', '2023-01-25 08:42:58', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2159, 2, 2006, '1', '2023-01-25 08:42:58', '1', '2023-01-25 08:42:58', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2160, 2, 2008, '1', '2023-01-25 08:42:58', '1', '2023-01-25 08:42:58', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2161, 2, 2009, '1', '2023-01-25 08:42:58', '1', '2023-01-25 08:42:58', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2162, 2, 2010, '1', '2023-01-25 08:42:58', '1', '2023-01-25 08:42:58', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2163, 2, 2011, '1', '2023-01-25 08:42:58', '1', '2023-01-25 08:42:58', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2164, 2, 2012, '1', '2023-01-25 08:42:58', '1', '2023-01-25 08:42:58', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2170, 2, 2019, '1', '2023-01-25 08:42:58', '1', '2023-01-25 08:42:58', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2171, 2, 2020, '1', '2023-01-25 08:42:58', '1', '2023-01-25 08:42:58', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2172, 2, 2021, '1', '2023-01-25 08:42:58', '1', '2023-01-25 08:42:58', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2173, 2, 2022, '1', '2023-01-25 08:42:58', '1', '2023-01-25 08:42:58', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2174, 2, 2023, '1', '2023-01-25 08:42:58', '1', '2023-01-25 08:42:58', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2175, 2, 2025, '1', '2023-01-25 08:42:58', '1', '2023-01-25 08:42:58', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2177, 2, 2027, '1', '2023-01-25 08:42:58', '1', '2023-01-25 08:42:58', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2178, 2, 2028, '1', '2023-01-25 08:42:58', '1', '2023-01-25 08:42:58', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2179, 2, 2029, '1', '2023-01-25 08:42:58', '1', '2023-01-25 08:42:58', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2180, 2, 2014, '1', '2023-01-25 08:43:12', '1', '2023-01-25 08:43:12', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2181, 2, 2015, '1', '2023-01-25 08:43:12', '1', '2023-01-25 08:43:12', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2182, 2, 2016, '1', '2023-01-25 08:43:12', '1', '2023-01-25 08:43:12', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2183, 2, 2017, '1', '2023-01-25 08:43:12', '1', '2023-01-25 08:43:12', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2184, 2, 2018, '1', '2023-01-25 08:43:12', '1', '2023-01-25 08:43:12', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2188, 101, 1024, '1', '2023-02-09 23:49:46', '1', '2023-02-09 23:49:46', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2189, 101, 1, '1', '2023-02-09 23:49:46', '1', '2023-02-09 23:49:46', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2190, 101, 1025, '1', '2023-02-09 23:49:46', '1', '2023-02-09 23:49:46', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2191, 101, 1026, '1', '2023-02-09 23:49:46', '1', '2023-02-09 23:49:46', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2192, 101, 1027, '1', '2023-02-09 23:49:46', '1', '2023-02-09 23:49:46', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2193, 101, 1028, '1', '2023-02-09 23:49:46', '1', '2023-02-09 23:49:46', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2194, 101, 1029, '1', '2023-02-09 23:49:46', '1', '2023-02-09 23:49:46', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2195, 101, 1030, '1', '2023-02-09 23:49:46', '1', '2023-02-09 23:49:46', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2196, 101, 1036, '1', '2023-02-09 23:49:46', '1', '2023-02-09 23:49:46', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2197, 101, 1037, '1', '2023-02-09 23:49:46', '1', '2023-02-09 23:49:46', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2198, 101, 1038, '1', '2023-02-09 23:49:46', '1', '2023-02-09 23:49:46', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2199, 101, 1039, '1', '2023-02-09 23:49:46', '1', '2023-02-09 23:49:46', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2200, 101, 1040, '1', '2023-02-09 23:49:46', '1', '2023-02-09 23:49:46', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2201, 101, 1042, '1', '2023-02-09 23:49:46', '1', '2023-02-09 23:49:46', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2202, 101, 1043, '1', '2023-02-09 23:49:46', '1', '2023-02-09 23:49:46', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2203, 101, 1045, '1', '2023-02-09 23:49:46', '1', '2023-02-09 23:49:46', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2204, 101, 1046, '1', '2023-02-09 23:49:46', '1', '2023-02-09 23:49:46', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2205, 101, 1048, '1', '2023-02-09 23:49:46', '1', '2023-02-09 23:49:46', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2206, 101, 2083, '1', '2023-02-09 23:49:46', '1', '2023-02-09 23:49:46', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2207, 101, 1063, '1', '2023-02-09 23:49:46', '1', '2023-02-09 23:49:46', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2208, 101, 1064, '1', '2023-02-09 23:49:46', '1', '2023-02-09 23:49:46', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2209, 101, 1065, '1', '2023-02-09 23:49:46', '1', '2023-02-09 23:49:46', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2210, 101, 1093, '1', '2023-02-09 23:49:46', '1', '2023-02-09 23:49:46', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2211, 101, 1094, '1', '2023-02-09 23:49:46', '1', '2023-02-09 23:49:46', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2212, 101, 1095, '1', '2023-02-09 23:49:46', '1', '2023-02-09 23:49:46', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2213, 101, 1096, '1', '2023-02-09 23:49:46', '1', '2023-02-09 23:49:46', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2214, 101, 1097, '1', '2023-02-09 23:49:46', '1', '2023-02-09 23:49:46', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2215, 101, 1098, '1', '2023-02-09 23:49:46', '1', '2023-02-09 23:49:46', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2216, 101, 1100, '1', '2023-02-09 23:49:46', '1', '2023-02-09 23:49:46', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2217, 101, 1101, '1', '2023-02-09 23:49:46', '1', '2023-02-09 23:49:46', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2218, 101, 1102, '1', '2023-02-09 23:49:46', '1', '2023-02-09 23:49:46', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2219, 101, 1103, '1', '2023-02-09 23:49:46', '1', '2023-02-09 23:49:46', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2220, 101, 1104, '1', '2023-02-09 23:49:46', '1', '2023-02-09 23:49:46', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2221, 101, 1105, '1', '2023-02-09 23:49:46', '1', '2023-02-09 23:49:46', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2222, 101, 1106, '1', '2023-02-09 23:49:46', '1', '2023-02-09 23:49:46', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2223, 101, 2130, '1', '2023-02-09 23:49:46', '1', '2023-02-09 23:49:46', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2224, 101, 1107, '1', '2023-02-09 23:49:46', '1', '2023-02-09 23:49:46', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2225, 101, 2131, '1', '2023-02-09 23:49:46', '1', '2023-02-09 23:49:46', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2226, 101, 1108, '1', '2023-02-09 23:49:46', '1', '2023-02-09 23:49:46', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2227, 101, 2132, '1', '2023-02-09 23:49:46', '1', '2023-02-09 23:49:46', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2228, 101, 1109, '1', '2023-02-09 23:49:46', '1', '2023-02-09 23:49:46', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2229, 101, 2133, '1', '2023-02-09 23:49:46', '1', '2023-02-09 23:49:46', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2230, 101, 2134, '1', '2023-02-09 23:49:46', '1', '2023-02-09 23:49:46', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2232, 101, 2135, '1', '2023-02-09 23:49:46', '1', '2023-02-09 23:49:46', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2234, 101, 2136, '1', '2023-02-09 23:49:46', '1', '2023-02-09 23:49:46', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2236, 101, 2137, '1', '2023-02-09 23:49:46', '1', '2023-02-09 23:49:46', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2238, 101, 2138, '1', '2023-02-09 23:49:46', '1', '2023-02-09 23:49:46', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2240, 101, 2139, '1', '2023-02-09 23:49:46', '1', '2023-02-09 23:49:46', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2242, 101, 2140, '1', '2023-02-09 23:49:46', '1', '2023-02-09 23:49:46', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2243, 101, 2141, '1', '2023-02-09 23:49:46', '1', '2023-02-09 23:49:46', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2244, 101, 2142, '1', '2023-02-09 23:49:46', '1', '2023-02-09 23:49:46', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2245, 101, 2143, '1', '2023-02-09 23:49:46', '1', '2023-02-09 23:49:46', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2246, 101, 2144, '1', '2023-02-09 23:49:46', '1', '2023-02-09 23:49:46', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2247, 101, 2145, '1', '2023-02-09 23:49:46', '1', '2023-02-09 23:49:46', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2248, 101, 2146, '1', '2023-02-09 23:49:46', '1', '2023-02-09 23:49:46', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2249, 101, 2147, '1', '2023-02-09 23:49:46', '1', '2023-02-09 23:49:46', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2250, 101, 100, '1', '2023-02-09 23:49:46', '1', '2023-02-09 23:49:46', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2251, 101, 2148, '1', '2023-02-09 23:49:46', '1', '2023-02-09 23:49:46', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2252, 101, 101, '1', '2023-02-09 23:49:46', '1', '2023-02-09 23:49:46', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2253, 101, 2149, '1', '2023-02-09 23:49:46', '1', '2023-02-09 23:49:46', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2254, 101, 102, '1', '2023-02-09 23:49:46', '1', '2023-02-09 23:49:46', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2255, 101, 2150, '1', '2023-02-09 23:49:46', '1', '2023-02-09 23:49:46', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2256, 101, 103, '1', '2023-02-09 23:49:46', '1', '2023-02-09 23:49:46', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2257, 101, 2151, '1', '2023-02-09 23:49:46', '1', '2023-02-09 23:49:46', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2258, 101, 104, '1', '2023-02-09 23:49:46', '1', '2023-02-09 23:49:46', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2259, 101, 2152, '1', '2023-02-09 23:49:46', '1', '2023-02-09 23:49:46', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2260, 101, 105, '1', '2023-02-09 23:49:46', '1', '2023-02-09 23:49:46', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2261, 101, 107, '1', '2023-02-09 23:49:46', '1', '2023-02-09 23:49:46', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2262, 101, 108, '1', '2023-02-09 23:49:46', '1', '2023-02-09 23:49:46', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2263, 101, 109, '1', '2023-02-09 23:49:46', '1', '2023-02-09 23:49:46', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2264, 101, 1138, '1', '2023-02-09 23:49:46', '1', '2023-02-09 23:49:46', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2265, 101, 1139, '1', '2023-02-09 23:49:46', '1', '2023-02-09 23:49:46', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2266, 101, 1140, '1', '2023-02-09 23:49:46', '1', '2023-02-09 23:49:46', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2267, 101, 1141, '1', '2023-02-09 23:49:46', '1', '2023-02-09 23:49:46', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2268, 101, 1142, '1', '2023-02-09 23:49:46', '1', '2023-02-09 23:49:46', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2269, 101, 1143, '1', '2023-02-09 23:49:46', '1', '2023-02-09 23:49:46', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2270, 101, 1224, '1', '2023-02-09 23:49:46', '1', '2023-02-09 23:49:46', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2271, 101, 1225, '1', '2023-02-09 23:49:46', '1', '2023-02-09 23:49:46', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2272, 101, 1226, '1', '2023-02-09 23:49:46', '1', '2023-02-09 23:49:46', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2273, 101, 1227, '1', '2023-02-09 23:49:46', '1', '2023-02-09 23:49:46', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2274, 101, 1228, '1', '2023-02-09 23:49:46', '1', '2023-02-09 23:49:46', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2275, 101, 1229, '1', '2023-02-09 23:49:46', '1', '2023-02-09 23:49:46', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2282, 101, 1261, '1', '2023-02-09 23:49:46', '1', '2023-02-09 23:49:46', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2283, 101, 1263, '1', '2023-02-09 23:49:46', '1', '2023-02-09 23:49:46', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2284, 101, 1264, '1', '2023-02-09 23:49:46', '1', '2023-02-09 23:49:46', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2285, 101, 1265, '1', '2023-02-09 23:49:46', '1', '2023-02-09 23:49:46', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2286, 101, 1266, '1', '2023-02-09 23:49:46', '1', '2023-02-09 23:49:46', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2287, 101, 1267, '1', '2023-02-09 23:49:46', '1', '2023-02-09 23:49:46', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2288, 101, 1001, '1', '2023-02-09 23:49:46', '1', '2023-02-09 23:49:46', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2289, 101, 1002, '1', '2023-02-09 23:49:46', '1', '2023-02-09 23:49:46', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2290, 101, 1003, '1', '2023-02-09 23:49:46', '1', '2023-02-09 23:49:46', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2291, 101, 1004, '1', '2023-02-09 23:49:46', '1', '2023-02-09 23:49:46', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2292, 101, 1005, '1', '2023-02-09 23:49:46', '1', '2023-02-09 23:49:46', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2293, 101, 1006, '1', '2023-02-09 23:49:46', '1', '2023-02-09 23:49:46', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2294, 101, 1007, '1', '2023-02-09 23:49:46', '1', '2023-02-09 23:49:46', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2295, 101, 1008, '1', '2023-02-09 23:49:46', '1', '2023-02-09 23:49:46', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2296, 101, 1009, '1', '2023-02-09 23:49:46', '1', '2023-02-09 23:49:46', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2297, 101, 1010, '1', '2023-02-09 23:49:46', '1', '2023-02-09 23:49:46', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2298, 101, 1011, '1', '2023-02-09 23:49:46', '1', '2023-02-09 23:49:46', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2299, 101, 1012, '1', '2023-02-09 23:49:46', '1', '2023-02-09 23:49:46', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2300, 101, 500, '1', '2023-02-09 23:49:46', '1', '2023-02-09 23:49:46', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2301, 101, 1013, '1', '2023-02-09 23:49:46', '1', '2023-02-09 23:49:46', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2302, 101, 501, '1', '2023-02-09 23:49:46', '1', '2023-02-09 23:49:46', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2303, 101, 1014, '1', '2023-02-09 23:49:46', '1', '2023-02-09 23:49:46', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2304, 101, 1015, '1', '2023-02-09 23:49:46', '1', '2023-02-09 23:49:46', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2305, 101, 1016, '1', '2023-02-09 23:49:46', '1', '2023-02-09 23:49:46', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2306, 101, 1017, '1', '2023-02-09 23:49:46', '1', '2023-02-09 23:49:46', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2307, 101, 1018, '1', '2023-02-09 23:49:46', '1', '2023-02-09 23:49:46', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2308, 101, 1019, '1', '2023-02-09 23:49:46', '1', '2023-02-09 23:49:46', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2309, 101, 1020, '1', '2023-02-09 23:49:46', '1', '2023-02-09 23:49:46', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2310, 101, 1021, '1', '2023-02-09 23:49:46', '1', '2023-02-09 23:49:46', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2311, 101, 1022, '1', '2023-02-09 23:49:46', '1', '2023-02-09 23:49:46', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2312, 101, 1023, '1', '2023-02-09 23:49:46', '1', '2023-02-09 23:49:46', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2929, 109, 1224, '1', '2023-12-02 23:19:40', '1', '2023-12-02 23:19:40', b'0', 121); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2930, 109, 1225, '1', '2023-12-02 23:19:40', '1', '2023-12-02 23:19:40', b'0', 121); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2931, 109, 1226, '1', '2023-12-02 23:19:40', '1', '2023-12-02 23:19:40', b'0', 121); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2932, 109, 1227, '1', '2023-12-02 23:19:40', '1', '2023-12-02 23:19:40', b'0', 121); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2933, 109, 1228, '1', '2023-12-02 23:19:40', '1', '2023-12-02 23:19:40', b'0', 121); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2934, 109, 1229, '1', '2023-12-02 23:19:40', '1', '2023-12-02 23:19:40', b'0', 121); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2935, 109, 1138, '1', '2023-12-02 23:19:40', '1', '2023-12-02 23:19:40', b'0', 121); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2936, 109, 1139, '1', '2023-12-02 23:19:40', '1', '2023-12-02 23:19:40', b'0', 121); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2937, 109, 1140, '1', '2023-12-02 23:19:40', '1', '2023-12-02 23:19:40', b'0', 121); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2938, 109, 1141, '1', '2023-12-02 23:19:40', '1', '2023-12-02 23:19:40', b'0', 121); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2939, 109, 1142, '1', '2023-12-02 23:19:40', '1', '2023-12-02 23:19:40', b'0', 121); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2940, 109, 1143, '1', '2023-12-02 23:19:40', '1', '2023-12-02 23:19:40', b'0', 121); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2941, 111, 1224, '1', '2023-12-02 23:19:40', '1', '2023-12-02 23:19:40', b'0', 122); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2942, 111, 1225, '1', '2023-12-02 23:19:40', '1', '2023-12-02 23:19:40', b'0', 122); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2943, 111, 1226, '1', '2023-12-02 23:19:40', '1', '2023-12-02 23:19:40', b'0', 122); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2944, 111, 1227, '1', '2023-12-02 23:19:40', '1', '2023-12-02 23:19:40', b'0', 122); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2945, 111, 1228, '1', '2023-12-02 23:19:40', '1', '2023-12-02 23:19:40', b'0', 122); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2946, 111, 1229, '1', '2023-12-02 23:19:40', '1', '2023-12-02 23:19:40', b'0', 122); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2947, 111, 1138, '1', '2023-12-02 23:19:40', '1', '2023-12-02 23:19:40', b'0', 122); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2948, 111, 1139, '1', '2023-12-02 23:19:40', '1', '2023-12-02 23:19:40', b'0', 122); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2949, 111, 1140, '1', '2023-12-02 23:19:40', '1', '2023-12-02 23:19:40', b'0', 122); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2950, 111, 1141, '1', '2023-12-02 23:19:40', '1', '2023-12-02 23:19:40', b'0', 122); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2951, 111, 1142, '1', '2023-12-02 23:19:40', '1', '2023-12-02 23:19:40', b'0', 122); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2952, 111, 1143, '1', '2023-12-02 23:19:40', '1', '2023-12-02 23:19:40', b'0', 122); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2993, 109, 2, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', b'0', 121); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2994, 109, 1031, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', b'0', 121); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2995, 109, 1032, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', b'0', 121); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2996, 109, 1033, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', b'0', 121); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2997, 109, 1034, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', b'0', 121); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2998, 109, 1035, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', b'0', 121); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2999, 109, 1050, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', b'0', 121); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (3000, 109, 1051, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', b'0', 121); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (3001, 109, 1052, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', b'0', 121); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (3002, 109, 1053, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', b'0', 121); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (3003, 109, 1054, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', b'0', 121); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (3004, 109, 1056, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', b'0', 121); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (3005, 109, 1057, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', b'0', 121); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (3006, 109, 1058, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', b'0', 121); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (3007, 109, 1059, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', b'0', 121); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (3008, 109, 1060, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', b'0', 121); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (3009, 109, 1066, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', b'0', 121); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (3010, 109, 1067, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', b'0', 121); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (3011, 109, 1070, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', b'0', 121); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (3012, 109, 1075, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', b'0', 121); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (3014, 109, 1077, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', b'0', 121); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (3015, 109, 1078, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', b'0', 121); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (3016, 109, 1082, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', b'0', 121); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (3017, 109, 1083, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', b'0', 121); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (3018, 109, 1084, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', b'0', 121); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (3019, 109, 1085, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', b'0', 121); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (3020, 109, 1086, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', b'0', 121); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (3021, 109, 1087, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', b'0', 121); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (3022, 109, 1088, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', b'0', 121); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (3023, 109, 1089, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', b'0', 121); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (3024, 109, 1090, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', b'0', 121); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (3025, 109, 1091, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', b'0', 121); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (3026, 109, 1092, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', b'0', 121); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (3027, 109, 106, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', b'0', 121); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (3028, 109, 110, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', b'0', 121); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (3029, 109, 111, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', b'0', 121); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (3030, 109, 112, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', b'0', 121); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (3031, 109, 113, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', b'0', 121); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (3032, 109, 114, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', b'0', 121); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (3033, 109, 115, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', b'0', 121); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (3034, 109, 116, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', b'0', 121); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (3035, 109, 2472, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', b'0', 121); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (3036, 109, 2478, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', b'0', 121); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (3037, 109, 2479, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', b'0', 121); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (3038, 109, 2480, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', b'0', 121); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (3039, 109, 2481, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', b'0', 121); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (3040, 109, 2482, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', b'0', 121); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (3041, 109, 2483, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', b'0', 121); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (3042, 109, 2484, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', b'0', 121); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (3043, 109, 2485, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', b'0', 121); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (3044, 109, 2486, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', b'0', 121); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (3045, 109, 2487, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', b'0', 121); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (3046, 109, 2488, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', b'0', 121); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (3047, 109, 2489, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', b'0', 121); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (3048, 109, 2490, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', b'0', 121); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (3049, 109, 2491, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', b'0', 121); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (3050, 109, 2492, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', b'0', 121); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (3051, 109, 2493, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', b'0', 121); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (3052, 109, 2494, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', b'0', 121); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (3053, 109, 2495, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', b'0', 121); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (3054, 109, 2497, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', b'0', 121); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (3055, 109, 1237, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', b'0', 121); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (3056, 109, 1238, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', b'0', 121); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (3057, 109, 1239, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', b'0', 121); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (3058, 109, 1240, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', b'0', 121); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (3059, 109, 1241, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', b'0', 121); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (3060, 109, 1242, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', b'0', 121); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (3061, 109, 1243, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', b'0', 121); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (3062, 109, 2525, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', b'0', 121); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (3063, 109, 1255, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', b'0', 121); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (3064, 109, 1256, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', b'0', 121); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (3065, 109, 1257, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', b'0', 121); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (3066, 109, 1258, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', b'0', 121); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (3067, 109, 1259, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', b'0', 121); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (3068, 109, 1260, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', b'0', 121); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (3069, 111, 2, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', b'0', 122); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (3070, 111, 1031, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', b'0', 122); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (3071, 111, 1032, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', b'0', 122); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (3072, 111, 1033, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', b'0', 122); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (3073, 111, 1034, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', b'0', 122); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (3074, 111, 1035, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', b'0', 122); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (3075, 111, 1050, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', b'0', 122); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (3076, 111, 1051, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', b'0', 122); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (3077, 111, 1052, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', b'0', 122); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (3078, 111, 1053, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', b'0', 122); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (3079, 111, 1054, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', b'0', 122); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (3080, 111, 1056, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', b'0', 122); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (3081, 111, 1057, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', b'0', 122); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (3082, 111, 1058, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', b'0', 122); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (3083, 111, 1059, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', b'0', 122); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (3084, 111, 1060, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', b'0', 122); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (3085, 111, 1066, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', b'0', 122); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (3086, 111, 1067, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', b'0', 122); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (3087, 111, 1070, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', b'0', 122); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (3088, 111, 1075, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', b'0', 122); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (3090, 111, 1077, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', b'0', 122); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (3091, 111, 1078, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', b'0', 122); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (3092, 111, 1082, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', b'0', 122); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (3093, 111, 1083, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', b'0', 122); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (3094, 111, 1084, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', b'0', 122); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (3095, 111, 1085, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', b'0', 122); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (3096, 111, 1086, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', b'0', 122); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (3097, 111, 1087, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', b'0', 122); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (3098, 111, 1088, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', b'0', 122); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (3099, 111, 1089, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', b'0', 122); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (3100, 111, 1090, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', b'0', 122); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (3101, 111, 1091, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', b'0', 122); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (3102, 111, 1092, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', b'0', 122); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (3103, 111, 106, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', b'0', 122); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (3104, 111, 110, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', b'0', 122); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (3105, 111, 111, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', b'0', 122); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (3106, 111, 112, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', b'0', 122); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (3107, 111, 113, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', b'0', 122); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (3108, 111, 114, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', b'0', 122); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (3109, 111, 115, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', b'0', 122); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (3110, 111, 116, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', b'0', 122); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (3111, 111, 2472, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', b'0', 122); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (3112, 111, 2478, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', b'0', 122); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (3113, 111, 2479, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', b'0', 122); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (3114, 111, 2480, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', b'0', 122); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (3115, 111, 2481, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', b'0', 122); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (3116, 111, 2482, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', b'0', 122); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (3117, 111, 2483, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', b'0', 122); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (3118, 111, 2484, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', b'0', 122); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (3119, 111, 2485, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', b'0', 122); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (3120, 111, 2486, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', b'0', 122); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (3121, 111, 2487, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', b'0', 122); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (3122, 111, 2488, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', b'0', 122); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (3123, 111, 2489, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', b'0', 122); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (3124, 111, 2490, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', b'0', 122); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (3125, 111, 2491, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', b'0', 122); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (3126, 111, 2492, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', b'0', 122); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (3127, 111, 2493, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', b'0', 122); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (3128, 111, 2494, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', b'0', 122); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (3129, 111, 2495, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', b'0', 122); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (3130, 111, 2497, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', b'0', 122); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (3131, 111, 1237, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', b'0', 122); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (3132, 111, 1238, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', b'0', 122); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (3133, 111, 1239, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', b'0', 122); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (3134, 111, 1240, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', b'0', 122); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (3135, 111, 1241, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', b'0', 122); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (3136, 111, 1242, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', b'0', 122); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (3137, 111, 1243, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', b'0', 122); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (3138, 111, 2525, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', b'0', 122); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (3139, 111, 1255, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', b'0', 122); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (3140, 111, 1256, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', b'0', 122); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (3141, 111, 1257, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', b'0', 122); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (3142, 111, 1258, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', b'0', 122); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (3143, 111, 1259, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', b'0', 122); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (3144, 111, 1260, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', b'0', 122); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (3221, 109, 102, '1', '2023-12-30 11:42:36', '1', '2023-12-30 11:42:36', b'0', 121); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (3222, 109, 1013, '1', '2023-12-30 11:42:36', '1', '2023-12-30 11:42:36', b'0', 121); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (3223, 109, 1014, '1', '2023-12-30 11:42:36', '1', '2023-12-30 11:42:36', b'0', 121); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (3224, 109, 1015, '1', '2023-12-30 11:42:36', '1', '2023-12-30 11:42:36', b'0', 121); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (3225, 109, 1016, '1', '2023-12-30 11:42:36', '1', '2023-12-30 11:42:36', b'0', 121); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (3226, 111, 102, '1', '2023-12-30 11:42:36', '1', '2023-12-30 11:42:36', b'0', 122); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (3227, 111, 1013, '1', '2023-12-30 11:42:36', '1', '2023-12-30 11:42:36', b'0', 122); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (3228, 111, 1014, '1', '2023-12-30 11:42:36', '1', '2023-12-30 11:42:36', b'0', 122); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (3229, 111, 1015, '1', '2023-12-30 11:42:36', '1', '2023-12-30 11:42:36', b'0', 122); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (3230, 111, 1016, '1', '2023-12-30 11:42:36', '1', '2023-12-30 11:42:36', b'0', 122); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (4163, 109, 5, '1', '2024-03-30 17:53:17', '1', '2024-03-30 17:53:17', b'0', 121); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (4164, 109, 1118, '1', '2024-03-30 17:53:17', '1', '2024-03-30 17:53:17', b'0', 121); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (4165, 109, 1119, '1', '2024-03-30 17:53:17', '1', '2024-03-30 17:53:17', b'0', 121); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (4166, 109, 1120, '1', '2024-03-30 17:53:17', '1', '2024-03-30 17:53:17', b'0', 121); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (4167, 109, 2713, '1', '2024-03-30 17:53:17', '1', '2024-03-30 17:53:17', b'0', 121); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (4168, 109, 2714, '1', '2024-03-30 17:53:17', '1', '2024-03-30 17:53:17', b'0', 121); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (4169, 109, 2715, '1', '2024-03-30 17:53:17', '1', '2024-03-30 17:53:17', b'0', 121); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (4170, 109, 2716, '1', '2024-03-30 17:53:17', '1', '2024-03-30 17:53:17', b'0', 121); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (4171, 109, 2717, '1', '2024-03-30 17:53:17', '1', '2024-03-30 17:53:17', b'0', 121); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (4172, 109, 2718, '1', '2024-03-30 17:53:17', '1', '2024-03-30 17:53:17', b'0', 121); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (4173, 109, 2720, '1', '2024-03-30 17:53:17', '1', '2024-03-30 17:53:17', b'0', 121); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (4174, 109, 1185, '1', '2024-03-30 17:53:17', '1', '2024-03-30 17:53:17', b'0', 121); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (4175, 109, 2721, '1', '2024-03-30 17:53:17', '1', '2024-03-30 17:53:17', b'0', 121); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (4176, 109, 1186, '1', '2024-03-30 17:53:17', '1', '2024-03-30 17:53:17', b'0', 121); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (4177, 109, 2722, '1', '2024-03-30 17:53:17', '1', '2024-03-30 17:53:17', b'0', 121); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (4178, 109, 1187, '1', '2024-03-30 17:53:17', '1', '2024-03-30 17:53:17', b'0', 121); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (4179, 109, 2723, '1', '2024-03-30 17:53:17', '1', '2024-03-30 17:53:17', b'0', 121); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (4180, 109, 1188, '1', '2024-03-30 17:53:17', '1', '2024-03-30 17:53:17', b'0', 121); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (4181, 109, 2724, '1', '2024-03-30 17:53:17', '1', '2024-03-30 17:53:17', b'0', 121); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (4182, 109, 1189, '1', '2024-03-30 17:53:17', '1', '2024-03-30 17:53:17', b'0', 121); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (4183, 109, 2725, '1', '2024-03-30 17:53:17', '1', '2024-03-30 17:53:17', b'0', 121); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (4184, 109, 1190, '1', '2024-03-30 17:53:17', '1', '2024-03-30 17:53:17', b'0', 121); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (4185, 109, 2726, '1', '2024-03-30 17:53:17', '1', '2024-03-30 17:53:17', b'0', 121); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (4186, 109, 1191, '1', '2024-03-30 17:53:17', '1', '2024-03-30 17:53:17', b'0', 121); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (4187, 109, 2727, '1', '2024-03-30 17:53:17', '1', '2024-03-30 17:53:17', b'0', 121); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (4188, 109, 1192, '1', '2024-03-30 17:53:17', '1', '2024-03-30 17:53:17', b'0', 121); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (4189, 109, 2728, '1', '2024-03-30 17:53:17', '1', '2024-03-30 17:53:17', b'0', 121); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (4190, 109, 1193, '1', '2024-03-30 17:53:17', '1', '2024-03-30 17:53:17', b'0', 121); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (4191, 109, 2729, '1', '2024-03-30 17:53:17', '1', '2024-03-30 17:53:17', b'0', 121); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (4192, 109, 1194, '1', '2024-03-30 17:53:17', '1', '2024-03-30 17:53:17', b'0', 121); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (4193, 109, 2730, '1', '2024-03-30 17:53:17', '1', '2024-03-30 17:53:17', b'0', 121); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (4194, 109, 1195, '1', '2024-03-30 17:53:17', '1', '2024-03-30 17:53:17', b'0', 121); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (4195, 109, 2731, '1', '2024-03-30 17:53:17', '1', '2024-03-30 17:53:17', b'0', 121); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (4196, 109, 1196, '1', '2024-03-30 17:53:17', '1', '2024-03-30 17:53:17', b'0', 121); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (4197, 109, 2732, '1', '2024-03-30 17:53:17', '1', '2024-03-30 17:53:17', b'0', 121); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (4198, 109, 1197, '1', '2024-03-30 17:53:17', '1', '2024-03-30 17:53:17', b'0', 121); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (4199, 109, 2733, '1', '2024-03-30 17:53:17', '1', '2024-03-30 17:53:17', b'0', 121); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (4200, 109, 1198, '1', '2024-03-30 17:53:17', '1', '2024-03-30 17:53:17', b'0', 121); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (4201, 109, 2734, '1', '2024-03-30 17:53:17', '1', '2024-03-30 17:53:17', b'0', 121); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (4202, 109, 1199, '1', '2024-03-30 17:53:17', '1', '2024-03-30 17:53:17', b'0', 121); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (4203, 109, 2735, '1', '2024-03-30 17:53:17', '1', '2024-03-30 17:53:17', b'0', 121); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (4204, 109, 1200, '1', '2024-03-30 17:53:17', '1', '2024-03-30 17:53:17', b'0', 121); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (4205, 109, 1201, '1', '2024-03-30 17:53:17', '1', '2024-03-30 17:53:17', b'0', 121); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (4206, 109, 1202, '1', '2024-03-30 17:53:17', '1', '2024-03-30 17:53:17', b'0', 121); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (4207, 109, 1207, '1', '2024-03-30 17:53:17', '1', '2024-03-30 17:53:17', b'0', 121); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (4208, 109, 1208, '1', '2024-03-30 17:53:17', '1', '2024-03-30 17:53:17', b'0', 121); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (4209, 109, 1209, '1', '2024-03-30 17:53:17', '1', '2024-03-30 17:53:17', b'0', 121); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (4210, 109, 1210, '1', '2024-03-30 17:53:17', '1', '2024-03-30 17:53:17', b'0', 121); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (4211, 109, 1211, '1', '2024-03-30 17:53:17', '1', '2024-03-30 17:53:17', b'0', 121); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (4212, 109, 1212, '1', '2024-03-30 17:53:17', '1', '2024-03-30 17:53:17', b'0', 121); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (4213, 109, 1213, '1', '2024-03-30 17:53:17', '1', '2024-03-30 17:53:17', b'0', 121); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (4214, 109, 1215, '1', '2024-03-30 17:53:17', '1', '2024-03-30 17:53:17', b'0', 121); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (4215, 109, 1216, '1', '2024-03-30 17:53:17', '1', '2024-03-30 17:53:17', b'0', 121); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (4216, 109, 1217, '1', '2024-03-30 17:53:17', '1', '2024-03-30 17:53:17', b'0', 121); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (4217, 109, 1218, '1', '2024-03-30 17:53:17', '1', '2024-03-30 17:53:17', b'0', 121); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (4218, 109, 1219, '1', '2024-03-30 17:53:17', '1', '2024-03-30 17:53:17', b'0', 121); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (4219, 109, 1220, '1', '2024-03-30 17:53:17', '1', '2024-03-30 17:53:17', b'0', 121); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (4220, 109, 1221, '1', '2024-03-30 17:53:17', '1', '2024-03-30 17:53:17', b'0', 121); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (4221, 109, 1222, '1', '2024-03-30 17:53:17', '1', '2024-03-30 17:53:17', b'0', 121); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (4222, 111, 5, '1', '2024-03-30 17:53:18', '1', '2024-03-30 17:53:18', b'0', 122); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (4223, 111, 1118, '1', '2024-03-30 17:53:18', '1', '2024-03-30 17:53:18', b'0', 122); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (4224, 111, 1119, '1', '2024-03-30 17:53:18', '1', '2024-03-30 17:53:18', b'0', 122); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (4225, 111, 1120, '1', '2024-03-30 17:53:18', '1', '2024-03-30 17:53:18', b'0', 122); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (4226, 111, 2713, '1', '2024-03-30 17:53:18', '1', '2024-03-30 17:53:18', b'0', 122); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (4227, 111, 2714, '1', '2024-03-30 17:53:18', '1', '2024-03-30 17:53:18', b'0', 122); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (4228, 111, 2715, '1', '2024-03-30 17:53:18', '1', '2024-03-30 17:53:18', b'0', 122); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (4229, 111, 2716, '1', '2024-03-30 17:53:18', '1', '2024-03-30 17:53:18', b'0', 122); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (4230, 111, 2717, '1', '2024-03-30 17:53:18', '1', '2024-03-30 17:53:18', b'0', 122); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (4231, 111, 2718, '1', '2024-03-30 17:53:18', '1', '2024-03-30 17:53:18', b'0', 122); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (4232, 111, 2720, '1', '2024-03-30 17:53:18', '1', '2024-03-30 17:53:18', b'0', 122); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (4233, 111, 1185, '1', '2024-03-30 17:53:18', '1', '2024-03-30 17:53:18', b'0', 122); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (4234, 111, 2721, '1', '2024-03-30 17:53:18', '1', '2024-03-30 17:53:18', b'0', 122); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (4235, 111, 1186, '1', '2024-03-30 17:53:18', '1', '2024-03-30 17:53:18', b'0', 122); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (4236, 111, 2722, '1', '2024-03-30 17:53:18', '1', '2024-03-30 17:53:18', b'0', 122); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (4237, 111, 1187, '1', '2024-03-30 17:53:18', '1', '2024-03-30 17:53:18', b'0', 122); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (4238, 111, 2723, '1', '2024-03-30 17:53:18', '1', '2024-03-30 17:53:18', b'0', 122); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (4239, 111, 1188, '1', '2024-03-30 17:53:18', '1', '2024-03-30 17:53:18', b'0', 122); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (4240, 111, 2724, '1', '2024-03-30 17:53:18', '1', '2024-03-30 17:53:18', b'0', 122); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (4241, 111, 1189, '1', '2024-03-30 17:53:18', '1', '2024-03-30 17:53:18', b'0', 122); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (4242, 111, 2725, '1', '2024-03-30 17:53:18', '1', '2024-03-30 17:53:18', b'0', 122); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (4243, 111, 1190, '1', '2024-03-30 17:53:18', '1', '2024-03-30 17:53:18', b'0', 122); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (4244, 111, 2726, '1', '2024-03-30 17:53:18', '1', '2024-03-30 17:53:18', b'0', 122); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (4245, 111, 1191, '1', '2024-03-30 17:53:18', '1', '2024-03-30 17:53:18', b'0', 122); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (4246, 111, 2727, '1', '2024-03-30 17:53:18', '1', '2024-03-30 17:53:18', b'0', 122); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (4247, 111, 1192, '1', '2024-03-30 17:53:18', '1', '2024-03-30 17:53:18', b'0', 122); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (4248, 111, 2728, '1', '2024-03-30 17:53:18', '1', '2024-03-30 17:53:18', b'0', 122); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (4249, 111, 1193, '1', '2024-03-30 17:53:18', '1', '2024-03-30 17:53:18', b'0', 122); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (4250, 111, 2729, '1', '2024-03-30 17:53:18', '1', '2024-03-30 17:53:18', b'0', 122); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (4251, 111, 1194, '1', '2024-03-30 17:53:18', '1', '2024-03-30 17:53:18', b'0', 122); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (4252, 111, 2730, '1', '2024-03-30 17:53:18', '1', '2024-03-30 17:53:18', b'0', 122); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (4253, 111, 1195, '1', '2024-03-30 17:53:18', '1', '2024-03-30 17:53:18', b'0', 122); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (4254, 111, 2731, '1', '2024-03-30 17:53:18', '1', '2024-03-30 17:53:18', b'0', 122); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (4255, 111, 1196, '1', '2024-03-30 17:53:18', '1', '2024-03-30 17:53:18', b'0', 122); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (4256, 111, 2732, '1', '2024-03-30 17:53:18', '1', '2024-03-30 17:53:18', b'0', 122); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (4257, 111, 1197, '1', '2024-03-30 17:53:18', '1', '2024-03-30 17:53:18', b'0', 122); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (4258, 111, 2733, '1', '2024-03-30 17:53:18', '1', '2024-03-30 17:53:18', b'0', 122); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (4259, 111, 1198, '1', '2024-03-30 17:53:18', '1', '2024-03-30 17:53:18', b'0', 122); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (4260, 111, 2734, '1', '2024-03-30 17:53:18', '1', '2024-03-30 17:53:18', b'0', 122); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (4261, 111, 1199, '1', '2024-03-30 17:53:18', '1', '2024-03-30 17:53:18', b'0', 122); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (4262, 111, 2735, '1', '2024-03-30 17:53:18', '1', '2024-03-30 17:53:18', b'0', 122); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (4263, 111, 1200, '1', '2024-03-30 17:53:18', '1', '2024-03-30 17:53:18', b'0', 122); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (4264, 111, 1201, '1', '2024-03-30 17:53:18', '1', '2024-03-30 17:53:18', b'0', 122); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (4265, 111, 1202, '1', '2024-03-30 17:53:18', '1', '2024-03-30 17:53:18', b'0', 122); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (4266, 111, 1207, '1', '2024-03-30 17:53:18', '1', '2024-03-30 17:53:18', b'0', 122); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (4267, 111, 1208, '1', '2024-03-30 17:53:18', '1', '2024-03-30 17:53:18', b'0', 122); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (4268, 111, 1209, '1', '2024-03-30 17:53:18', '1', '2024-03-30 17:53:18', b'0', 122); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (4269, 111, 1210, '1', '2024-03-30 17:53:18', '1', '2024-03-30 17:53:18', b'0', 122); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (4270, 111, 1211, '1', '2024-03-30 17:53:18', '1', '2024-03-30 17:53:18', b'0', 122); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (4271, 111, 1212, '1', '2024-03-30 17:53:18', '1', '2024-03-30 17:53:18', b'0', 122); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (4272, 111, 1213, '1', '2024-03-30 17:53:18', '1', '2024-03-30 17:53:18', b'0', 122); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (4273, 111, 1215, '1', '2024-03-30 17:53:18', '1', '2024-03-30 17:53:18', b'0', 122); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (4274, 111, 1216, '1', '2024-03-30 17:53:18', '1', '2024-03-30 17:53:18', b'0', 122); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (4275, 111, 1217, '1', '2024-03-30 17:53:18', '1', '2024-03-30 17:53:18', b'0', 122); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (4276, 111, 1218, '1', '2024-03-30 17:53:18', '1', '2024-03-30 17:53:18', b'0', 122); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (4277, 111, 1219, '1', '2024-03-30 17:53:18', '1', '2024-03-30 17:53:18', b'0', 122); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (4278, 111, 1220, '1', '2024-03-30 17:53:18', '1', '2024-03-30 17:53:18', b'0', 122); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (4279, 111, 1221, '1', '2024-03-30 17:53:18', '1', '2024-03-30 17:53:18', b'0', 122); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (4280, 111, 1222, '1', '2024-03-30 17:53:18', '1', '2024-03-30 17:53:18', b'0', 122); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (5777, 101, 2739, '1', '2024-04-30 09:38:37', '1', '2024-04-30 09:38:37', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (5778, 101, 2740, '1', '2024-04-30 09:38:37', '1', '2024-04-30 09:38:37', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (5779, 2, 2739, '1', '2024-07-07 20:39:38', '1', '2024-07-07 20:39:38', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (5780, 2, 2740, '1', '2024-07-07 20:39:38', '1', '2024-07-07 20:39:38', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (5781, 2, 2758, '1', '2024-07-07 20:39:38', '1', '2024-07-07 20:39:38', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (5782, 2, 2759, '1', '2024-07-07 20:39:38', '1', '2024-07-07 20:39:38', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (5783, 2, 2362, '1', '2024-07-07 20:39:38', '1', '2024-07-07 20:39:38', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (5784, 2, 2387, '1', '2024-07-07 20:39:38', '1', '2024-07-07 20:39:38', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (5785, 2, 2030, '1', '2024-07-07 20:39:38', '1', '2024-07-07 20:39:38', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (5786, 101, 2758, '1', '2024-07-07 20:39:55', '1', '2024-07-07 20:39:55', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (5787, 101, 2759, '1', '2024-07-07 20:39:55', '1', '2024-07-07 20:39:55', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (5788, 101, 2783, '1', '2024-07-07 20:39:55', '1', '2024-07-07 20:39:55', b'0', 1); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (5789, 109, 2739, '1', '2024-07-13 22:37:24', '1', '2024-07-13 22:37:24', b'0', 121); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (5790, 109, 2740, '1', '2024-07-13 22:37:24', '1', '2024-07-13 22:37:24', b'0', 121); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (5791, 111, 2739, '1', '2024-07-13 22:37:24', '1', '2024-07-13 22:37:24', b'0', 122); +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (5792, 111, 2740, '1', '2024-07-13 22:37:24', '1', '2024-07-13 22:37:24', b'0', 122); +COMMIT; + +-- ---------------------------- +-- Table structure for system_sms_channel +-- ---------------------------- +DROP TABLE IF EXISTS `system_sms_channel`; +CREATE TABLE `system_sms_channel` +( + `id` bigint NOT NULL AUTO_INCREMENT COMMENT '编号', + `signature` varchar(12) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '短信签名', + `code` varchar(63) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '渠道编码', + `status` tinyint NOT NULL COMMENT '开启状态', + `remark` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '备注', + `api_key` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '短信 API 的账号', + `api_secret` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '短信 API 的秘钥', + `callback_url` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '短信发送回调 URL', + `creator` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '创建者', + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `updater` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '更新者', + `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 8 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '短信渠道'; + +-- ---------------------------- +-- Records of system_sms_channel +-- ---------------------------- +BEGIN; +INSERT INTO `system_sms_channel` (`id`, `signature`, `code`, `status`, `remark`, `api_key`, `api_secret`, + `callback_url`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (2, 'Ballcat', 'ALIYUN', 0, '你要改哦,只有我可以用!!!!', 'LTAI5tCnKso2uG3kJ5gRav88', + 'fGJ5SNXL7P1NHNRmJ7DJaMJGPyE55C', NULL, '', '2021-03-31 11:53:10', '1', '2024-08-04 08:53:26', b'0'); +INSERT INTO `system_sms_channel` (`id`, `signature`, `code`, `status`, `remark`, `api_key`, `api_secret`, + `callback_url`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (4, '测试渠道', 'DEBUG_DING_TALK', 0, '123', '696b5d8ead48071237e4aa5861ff08dbadb2b4ded1c688a7b7c9afc615579859', + 'SEC5c4e5ff888bc8a9923ae47f59e7ccd30af1f14d93c55b4e2c9cb094e35aeed67', NULL, '1', '2021-04-13 00:23:14', '1', + '2022-03-27 20:29:49', b'0'); +INSERT INTO `system_sms_channel` (`id`, `signature`, `code`, `status`, `remark`, `api_key`, `api_secret`, + `callback_url`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) +VALUES (7, 'mock腾讯云', 'TENCENT', 0, '', '1 2', '2 3', '', '1', '2024-09-30 08:53:45', '1', '2024-09-30 08:55:01', + b'0'); +COMMIT; + +-- ---------------------------- +-- Table structure for system_sms_code +-- ---------------------------- +DROP TABLE IF EXISTS `system_sms_code`; +CREATE TABLE `system_sms_code` +( + `id` bigint NOT NULL AUTO_INCREMENT COMMENT '编号', + `mobile` varchar(11) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '手机号', + `code` varchar(6) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '验证码', + `create_ip` varchar(15) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '创建 IP', + `scene` tinyint NOT NULL COMMENT '发送场景', + `today_index` tinyint NOT NULL COMMENT '今日发送的第几条', + `used` tinyint NOT NULL COMMENT '是否使用', + `used_time` datetime NULL DEFAULT NULL COMMENT '使用时间', + `used_ip` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '使用 IP', + `creator` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '创建者', + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `updater` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '更新者', + `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除', + `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号', + PRIMARY KEY (`id`) USING BTREE, + INDEX `idx_mobile`(`mobile` ASC) USING BTREE COMMENT '手机号' +) ENGINE = InnoDB AUTO_INCREMENT = 645 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '手机验证码'; + +-- ---------------------------- +-- Records of system_sms_code +-- ---------------------------- +BEGIN; +COMMIT; + +-- ---------------------------- +-- Table structure for system_sms_log +-- ---------------------------- +DROP TABLE IF EXISTS `system_sms_log`; +CREATE TABLE `system_sms_log` +( + `id` bigint NOT NULL AUTO_INCREMENT COMMENT '编号', + `channel_id` bigint NOT NULL COMMENT '短信渠道编号', + `channel_code` varchar(63) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '短信渠道编码', + `template_id` bigint NOT NULL COMMENT '模板编号', + `template_code` varchar(63) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '模板编码', + `template_type` tinyint NOT NULL COMMENT '短信类型', + `template_content` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '短信内容', + `template_params` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '短信参数', + `api_template_id` varchar(63) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '短信 API 的模板编号', + `mobile` varchar(11) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '手机号', + `user_id` bigint NULL DEFAULT NULL COMMENT '用户编号', + `user_type` tinyint NULL DEFAULT NULL COMMENT '用户类型', + `send_status` tinyint NOT NULL DEFAULT 0 COMMENT '发送状态', + `send_time` datetime NULL DEFAULT NULL COMMENT '发送时间', + `api_send_code` varchar(63) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '短信 API 发送结果的编码', + `api_send_msg` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '短信 API 发送失败的提示', + `api_request_id` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '短信 API 发送返回的唯一请求 ID', + `api_serial_no` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '短信 API 发送返回的序号', + `receive_status` tinyint NOT NULL DEFAULT 0 COMMENT '接收状态', + `receive_time` datetime NULL DEFAULT NULL COMMENT '接收时间', + `api_receive_code` varchar(63) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT 'API 接收结果的编码', + `api_receive_msg` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT 'API 接收结果的说明', + `creator` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '创建者', + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `updater` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '更新者', + `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 1241 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '短信日志'; + +-- ---------------------------- +-- Records of system_sms_log +-- ---------------------------- +BEGIN; +COMMIT; + +-- ---------------------------- +-- Table structure for system_sms_template +-- ---------------------------- +DROP TABLE IF EXISTS `system_sms_template`; +CREATE TABLE `system_sms_template` +( + `id` bigint NOT NULL AUTO_INCREMENT COMMENT '编号', + `type` tinyint NOT NULL COMMENT '模板类型', + `status` tinyint NOT NULL COMMENT '开启状态', + `code` varchar(63) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '模板编码', + `name` varchar(63) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '模板名称', + `content` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '模板内容', + `params` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '参数数组', + `remark` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '备注', + `api_template_id` varchar(63) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '短信 API 的模板编号', + `channel_id` bigint NOT NULL COMMENT '短信渠道编号', + `channel_code` varchar(63) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '短信渠道编码', + `creator` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '创建者', + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `updater` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '更新者', + `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 18 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '短信模板'; + +-- ---------------------------- +-- Records of system_sms_template +-- ---------------------------- +BEGIN; +INSERT INTO `system_sms_template` (`id`, `type`, `status`, `code`, `name`, `content`, `params`, `remark`, + `api_template_id`, `channel_id`, `channel_code`, `creator`, `create_time`, `updater`, + `update_time`, `deleted`) +VALUES (2, 1, 0, 'test_01', '测试验证码短信', '正在进行登录操作{operation},您的验证码是{code}', + '[\"operation\",\"code\"]', '测试备注', '4383920', 4, 'DEBUG_DING_TALK', '', '2021-03-31 10:49:38', '1', + '2024-08-18 11:57:18', b'0'); +INSERT INTO `system_sms_template` (`id`, `type`, `status`, `code`, `name`, `content`, `params`, `remark`, + `api_template_id`, `channel_id`, `channel_code`, `creator`, `create_time`, `updater`, + `update_time`, `deleted`) +VALUES (3, 1, 0, 'test_02', '公告通知', '您的验证码{code},该验证码5分钟内有效,请勿泄漏于他人!', '[\"code\"]', NULL, + 'SMS_207945135', 2, 'ALIYUN', '', '2021-03-31 11:56:30', '1', '2021-04-10 01:22:02', b'0'); +INSERT INTO `system_sms_template` (`id`, `type`, `status`, `code`, `name`, `content`, `params`, `remark`, + `api_template_id`, `channel_id`, `channel_code`, `creator`, `create_time`, `updater`, + `update_time`, `deleted`) +VALUES (6, 3, 0, 'test-01', '测试模板', '哈哈哈 {name}', '[\"name\"]', 'f哈哈哈', '4383920', 4, 'DEBUG_DING_TALK', '1', + '2021-04-10 01:07:21', '1', '2024-08-18 11:57:07', b'0'); +INSERT INTO `system_sms_template` (`id`, `type`, `status`, `code`, `name`, `content`, `params`, `remark`, + `api_template_id`, `channel_id`, `channel_code`, `creator`, `create_time`, `updater`, + `update_time`, `deleted`) +VALUES (7, 3, 0, 'test-04', '测试下', '老鸡{name},牛逼{code}', '[\"name\",\"code\"]', '哈哈哈哈', 'suibian', 7, + 'DEBUG_DING_TALK', '1', '2021-04-13 00:29:53', '1', '2024-09-30 00:56:24', b'0'); +INSERT INTO `system_sms_template` (`id`, `type`, `status`, `code`, `name`, `content`, `params`, `remark`, + `api_template_id`, `channel_id`, `channel_code`, `creator`, `create_time`, `updater`, + `update_time`, `deleted`) +VALUES (8, 1, 0, 'user-sms-login', '前台用户短信登录', '您的验证码是{code}', '[\"code\"]', NULL, '4372216', 4, + 'DEBUG_DING_TALK', '1', '2021-10-11 08:10:00', '1', '2024-08-18 11:57:06', b'0'); +INSERT INTO `system_sms_template` (`id`, `type`, `status`, `code`, `name`, `content`, `params`, `remark`, + `api_template_id`, `channel_id`, `channel_code`, `creator`, `create_time`, `updater`, + `update_time`, `deleted`) +VALUES (9, 2, 0, 'bpm_task_assigned', '【工作流】任务被分配', + '您收到了一条新的待办任务:{processInstanceName}-{taskName},申请人:{startUserNickname},处理链接:{detailUrl}', + '[\"processInstanceName\",\"taskName\",\"startUserNickname\",\"detailUrl\"]', NULL, 'suibian', 4, + 'DEBUG_DING_TALK', '1', '2022-01-21 22:31:19', '1', '2022-01-22 00:03:36', b'0'); +INSERT INTO `system_sms_template` (`id`, `type`, `status`, `code`, `name`, `content`, `params`, `remark`, + `api_template_id`, `channel_id`, `channel_code`, `creator`, `create_time`, `updater`, + `update_time`, `deleted`) +VALUES (10, 2, 0, 'bpm_process_instance_reject', '【工作流】流程被不通过', + '您的流程被审批不通过:{processInstanceName},原因:{reason},查看链接:{detailUrl}', + '[\"processInstanceName\",\"reason\",\"detailUrl\"]', NULL, 'suibian', 4, 'DEBUG_DING_TALK', '1', + '2022-01-22 00:03:31', '1', '2022-05-01 12:33:14', b'0'); +INSERT INTO `system_sms_template` (`id`, `type`, `status`, `code`, `name`, `content`, `params`, `remark`, + `api_template_id`, `channel_id`, `channel_code`, `creator`, `create_time`, `updater`, + `update_time`, `deleted`) +VALUES (11, 2, 0, 'bpm_process_instance_approve', '【工作流】流程被通过', + '您的流程被审批通过:{processInstanceName},查看链接:{detailUrl}', '[\"processInstanceName\",\"detailUrl\"]', + NULL, 'suibian', 4, 'DEBUG_DING_TALK', '1', '2022-01-22 00:04:31', '1', '2022-03-27 20:32:21', b'0'); +INSERT INTO `system_sms_template` (`id`, `type`, `status`, `code`, `name`, `content`, `params`, `remark`, + `api_template_id`, `channel_id`, `channel_code`, `creator`, `create_time`, `updater`, + `update_time`, `deleted`) +VALUES (12, 2, 0, 'demo', '演示模板', '我就是测试一下下', '[]', NULL, 'biubiubiu', 4, 'DEBUG_DING_TALK', '1', + '2022-04-10 23:22:49', '1', '2024-08-18 11:57:04', b'0'); +INSERT INTO `system_sms_template` (`id`, `type`, `status`, `code`, `name`, `content`, `params`, `remark`, + `api_template_id`, `channel_id`, `channel_code`, `creator`, `create_time`, `updater`, + `update_time`, `deleted`) +VALUES (14, 1, 0, 'user-update-mobile', '会员用户 - 修改手机', '您的验证码{code},该验证码 5 分钟内有效,请勿泄漏于他人!', + '[\"code\"]', '', 'null', 4, 'DEBUG_DING_TALK', '1', '2023-08-19 18:58:01', '1', '2023-08-19 11:34:04', b'0'); +INSERT INTO `system_sms_template` (`id`, `type`, `status`, `code`, `name`, `content`, `params`, `remark`, + `api_template_id`, `channel_id`, `channel_code`, `creator`, `create_time`, `updater`, + `update_time`, `deleted`) +VALUES (15, 1, 0, 'user-update-password', '会员用户 - 修改密码', + '您的验证码{code},该验证码 5 分钟内有效,请勿泄漏于他人!', '[\"code\"]', '', 'null', 4, 'DEBUG_DING_TALK', '1', + '2023-08-19 18:58:01', '1', '2023-08-19 11:34:18', b'0'); +INSERT INTO `system_sms_template` (`id`, `type`, `status`, `code`, `name`, `content`, `params`, `remark`, + `api_template_id`, `channel_id`, `channel_code`, `creator`, `create_time`, `updater`, + `update_time`, `deleted`) +VALUES (16, 1, 0, 'user-reset-password', '会员用户 - 重置密码', + '您的验证码{code},该验证码 5 分钟内有效,请勿泄漏于他人!', '[\"code\"]', '', 'null', 4, 'DEBUG_DING_TALK', '1', + '2023-08-19 18:58:01', '1', '2023-12-02 22:35:27', b'0'); +INSERT INTO `system_sms_template` (`id`, `type`, `status`, `code`, `name`, `content`, `params`, `remark`, + `api_template_id`, `channel_id`, `channel_code`, `creator`, `create_time`, `updater`, + `update_time`, `deleted`) +VALUES (17, 2, 0, 'bpm_task_timeout', '【工作流】任务审批超时', + '您收到了一条超时的待办任务:{processInstanceName}-{taskName},处理链接:{detailUrl}', + '[\"processInstanceName\",\"taskName\",\"detailUrl\"]', '', 'X', 4, 'DEBUG_DING_TALK', '1', + '2024-08-16 21:59:15', '1', '2024-08-16 21:59:34', b'0'); +COMMIT; + +-- ---------------------------- +-- Table structure for system_social_client +-- ---------------------------- +DROP TABLE IF EXISTS `system_social_client`; +CREATE TABLE `system_social_client` +( + `id` bigint NOT NULL AUTO_INCREMENT COMMENT '编号', + `name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '应用名', + `social_type` tinyint NOT NULL COMMENT '社交平台的类型', + `user_type` tinyint NOT NULL COMMENT '用户类型', + `client_id` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '客户端编号', + `client_secret` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '客户端密钥', + `agent_id` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '代理编号', + `status` tinyint NOT NULL COMMENT '状态', + `creator` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '创建者', + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `updater` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '更新者', + `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除', + `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 44 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '社交客户端表'; + +-- ---------------------------- +-- Records of system_social_client +-- ---------------------------- +BEGIN; +INSERT INTO `system_social_client` (`id`, `name`, `social_type`, `user_type`, `client_id`, `client_secret`, `agent_id`, + `status`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, + `tenant_id`) +VALUES (1, '钉钉', 20, 2, 'dingvrnreaje3yqvzhxg', 'i8E6iZyDvZj51JIb0tYsYfVQYOks9Cq1lgryEjFRqC79P3iJcrxEwT6Qk2QvLrLI', + NULL, 0, '', '2023-10-18 11:21:18', '1', '2023-12-20 21:28:26', b'1', 1); +INSERT INTO `system_social_client` (`id`, `name`, `social_type`, `user_type`, `client_id`, `client_secret`, `agent_id`, + `status`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, + `tenant_id`) +VALUES (2, '钉钉(王土豆)', 20, 2, 'dingtsu9hpepjkbmthhw', + 'FP_bnSq_HAHKCSncmJjw5hxhnzs6vaVDSZZn3egj6rdqTQ_hu5tQVJyLMpgCakdP', NULL, 0, '', '2023-10-18 11:21:18', '', + '2023-12-20 21:28:26', b'1', 121); +INSERT INTO `system_social_client` (`id`, `name`, `social_type`, `user_type`, `client_id`, `client_secret`, `agent_id`, + `status`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, + `tenant_id`) +VALUES (3, '微信公众号', 31, 1, 'wx5b23ba7a5589ecbb', '2a7b3b20c537e52e74afd395eb85f61f', NULL, 0, '', + '2023-10-18 16:07:46', '1', '2023-12-20 21:28:23', b'1', 1); +INSERT INTO `system_social_client` (`id`, `name`, `social_type`, `user_type`, `client_id`, `client_secret`, `agent_id`, + `status`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, + `tenant_id`) +VALUES (43, '微信小程序', 34, 1, 'wx63c280fe3248a3e7', '6f270509224a7ae1296bbf1c8cb97aed', NULL, 0, '', + '2023-10-19 13:37:41', '1', '2023-12-20 21:28:25', b'1', 1); +COMMIT; + +-- ---------------------------- +-- Table structure for system_social_user +-- ---------------------------- +DROP TABLE IF EXISTS `system_social_user`; +CREATE TABLE `system_social_user` +( + `id` bigint UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键(自增策略)', + `type` tinyint NOT NULL COMMENT '社交平台的类型', + `openid` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '社交 openid', + `token` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '社交 token', + `raw_token_info` varchar(1024) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '原始 Token 数据,一般是 JSON 格式', + `nickname` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '用户昵称', + `avatar` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '用户头像', + `raw_user_info` varchar(1024) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '原始用户数据,一般是 JSON 格式', + `code` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '最后一次的认证 code', + `state` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '最后一次的认证 state', + `creator` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '创建者', + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `updater` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '更新者', + `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除', + `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 38 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '社交用户表'; + +-- ---------------------------- +-- Records of system_social_user +-- ---------------------------- +BEGIN; +COMMIT; + +-- ---------------------------- +-- Table structure for system_social_user_bind +-- ---------------------------- +DROP TABLE IF EXISTS `system_social_user_bind`; +CREATE TABLE `system_social_user_bind` +( + `id` bigint UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键(自增策略)', + `user_id` bigint NOT NULL COMMENT '用户编号', + `user_type` tinyint NOT NULL COMMENT '用户类型', + `social_type` tinyint NOT NULL COMMENT '社交平台的类型', + `social_user_id` bigint NOT NULL COMMENT '社交用户的编号', + `creator` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '创建者', + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `updater` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '更新者', + `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除', + `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 121 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '社交绑定表'; + +-- ---------------------------- +-- Records of system_social_user_bind +-- ---------------------------- +BEGIN; +COMMIT; + +-- ---------------------------- +-- Table structure for system_tenant +-- ---------------------------- +DROP TABLE IF EXISTS `system_tenant`; +CREATE TABLE `system_tenant` +( + `id` bigint NOT NULL AUTO_INCREMENT COMMENT '租户编号', + `name` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '租户名', + `contact_user_id` bigint NULL DEFAULT NULL COMMENT '联系人的用户编号', + `contact_name` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '联系人', + `contact_mobile` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '联系手机', + `status` tinyint NOT NULL DEFAULT 0 COMMENT '租户状态(0正常 1停用)', + `website` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '绑定域名', + `package_id` bigint NOT NULL COMMENT '租户套餐编号', + `expire_time` datetime NOT NULL COMMENT '过期时间', + `account_count` int NOT NULL COMMENT '账号数量', + `creator` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '创建者', + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `updater` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '更新者', + `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 162 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '租户表'; + +-- ---------------------------- +-- Records of system_tenant +-- ---------------------------- +BEGIN; +INSERT INTO `system_tenant` (`id`, `name`, `contact_user_id`, `contact_name`, `contact_mobile`, `status`, `website`, + `package_id`, `expire_time`, `account_count`, `creator`, `create_time`, `updater`, + `update_time`, `deleted`) +VALUES (1, '芋道源码', NULL, '芋艿', '17321315478', 0, 'www.iocoder.cn', 0, '2099-02-19 17:14:16', 9999, '1', + '2021-01-05 17:03:47', '1', '2023-11-06 11:41:41', b'0'); +INSERT INTO `system_tenant` (`id`, `name`, `contact_user_id`, `contact_name`, `contact_mobile`, `status`, `website`, + `package_id`, `expire_time`, `account_count`, `creator`, `create_time`, `updater`, + `update_time`, `deleted`) +VALUES (121, '小租户', 110, '小王2', '15601691300', 0, 'zsxq.iocoder.cn', 111, '2025-03-11 00:00:00', 20, '1', + '2022-02-22 00:56:14', '1', '2024-07-20 22:21:53', b'0'); +INSERT INTO `system_tenant` (`id`, `name`, `contact_user_id`, `contact_name`, `contact_mobile`, `status`, `website`, + `package_id`, `expire_time`, `account_count`, `creator`, `create_time`, `updater`, + `update_time`, `deleted`) +VALUES (122, '测试租户', 113, '芋道', '15601691300', 0, 'test.iocoder.cn', 111, '2022-04-29 00:00:00', 50, '1', + '2022-03-07 21:37:58', '1', '2024-09-22 12:10:50', b'0'); +COMMIT; + +-- ---------------------------- +-- Table structure for system_tenant_package +-- ---------------------------- +DROP TABLE IF EXISTS `system_tenant_package`; +CREATE TABLE `system_tenant_package` +( + `id` bigint NOT NULL AUTO_INCREMENT COMMENT '套餐编号', + `name` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '套餐名', + `status` tinyint NOT NULL DEFAULT 0 COMMENT '租户状态(0正常 1停用)', + `remark` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '备注', + `menu_ids` varchar(4096) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '关联的菜单编号', + `creator` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '创建者', + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `updater` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '更新者', + `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 112 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '租户套餐表'; + +-- ---------------------------- +-- Records of system_tenant_package +-- ---------------------------- +BEGIN; +INSERT INTO `system_tenant_package` (`id`, `name`, `status`, `remark`, `menu_ids`, `creator`, `create_time`, `updater`, + `update_time`, `deleted`) +VALUES (111, '普通套餐', 0, '小功能', + '[1,2,5,1031,1032,1033,1034,1035,1036,1037,1038,1039,1050,1051,1052,1053,1054,1056,1057,1058,1059,1060,1063,1064,1065,1066,1067,1070,1075,1077,1078,1082,1083,1084,1085,1086,1087,1088,1089,1090,1091,1092,1118,1119,1120,100,101,102,103,106,107,110,111,112,113,1138,114,1139,115,1140,116,1141,1142,1143,2713,2714,2715,2716,2717,2718,2720,1185,2721,1186,2722,1187,2723,1188,2724,1189,2725,1190,2726,1191,2727,2472,1192,2728,1193,2729,1194,2730,1195,2731,1196,2732,1197,2733,2478,1198,2734,2479,1199,2735,2480,1200,2481,1201,2482,1202,2483,2739,2484,2740,2485,2486,2487,1207,2488,1208,2489,1209,2490,1210,2491,1211,2492,1212,2493,1213,2494,2495,1215,1216,2497,1217,1218,1219,1220,1221,1222,1224,1225,1226,1227,1228,1229,1237,1238,1239,1240,1241,1242,1243,2525,1255,1256,1001,1257,1002,1258,1003,1259,1004,1260,1005,1006,1007,1008,1009,1010,1011,1012,1013,1014,1015,1016,1017,1018,1019,1020]', + '1', '2022-02-22 00:54:00', '1', '2024-07-13 22:37:24', b'0'); +COMMIT; + +-- ---------------------------- +-- Table structure for system_user_post +-- ---------------------------- +DROP TABLE IF EXISTS `system_user_post`; +CREATE TABLE `system_user_post` +( + `id` bigint NOT NULL AUTO_INCREMENT COMMENT 'id', + `user_id` bigint NOT NULL DEFAULT 0 COMMENT '用户ID', + `post_id` bigint NOT NULL DEFAULT 0 COMMENT '岗位ID', + `creator` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '创建者', + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `updater` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '更新者', + `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除', + `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 126 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '用户岗位表'; + +-- ---------------------------- +-- Records of system_user_post +-- ---------------------------- +BEGIN; +INSERT INTO `system_user_post` (`id`, `user_id`, `post_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (112, 1, 1, 'admin', '2022-05-02 07:25:24', 'admin', '2022-05-02 07:25:24', b'0', 1); +INSERT INTO `system_user_post` (`id`, `user_id`, `post_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (113, 100, 1, 'admin', '2022-05-02 07:25:24', 'admin', '2022-05-02 07:25:24', b'0', 1); +INSERT INTO `system_user_post` (`id`, `user_id`, `post_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (115, 104, 1, '1', '2022-05-16 19:36:28', '1', '2022-05-16 19:36:28', b'0', 1); +INSERT INTO `system_user_post` (`id`, `user_id`, `post_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (116, 117, 2, '1', '2022-07-09 17:40:26', '1', '2022-07-09 17:40:26', b'0', 1); +INSERT INTO `system_user_post` (`id`, `user_id`, `post_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (117, 118, 1, '1', '2022-07-09 17:44:44', '1', '2022-07-09 17:44:44', b'0', 1); +INSERT INTO `system_user_post` (`id`, `user_id`, `post_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (119, 114, 5, '1', '2024-03-24 20:45:51', '1', '2024-03-24 20:45:51', b'0', 1); +INSERT INTO `system_user_post` (`id`, `user_id`, `post_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (123, 115, 1, '1', '2024-04-04 09:37:14', '1', '2024-04-04 09:37:14', b'0', 1); +INSERT INTO `system_user_post` (`id`, `user_id`, `post_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (124, 115, 2, '1', '2024-04-04 09:37:14', '1', '2024-04-04 09:37:14', b'0', 1); +INSERT INTO `system_user_post` (`id`, `user_id`, `post_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (125, 1, 2, '1', '2024-07-13 22:31:39', '1', '2024-07-13 22:31:39', b'0', 1); +COMMIT; + +-- ---------------------------- +-- Table structure for system_user_role +-- ---------------------------- +DROP TABLE IF EXISTS `system_user_role`; +CREATE TABLE `system_user_role` +( + `id` bigint NOT NULL AUTO_INCREMENT COMMENT '自增编号', + `user_id` bigint NOT NULL COMMENT '用户ID', + `role_id` bigint NOT NULL COMMENT '角色ID', + `creator` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '创建者', + `create_time` datetime NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `updater` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '更新者', + `update_time` datetime NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + `deleted` bit(1) NULL DEFAULT b'0' COMMENT '是否删除', + `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 47 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '用户和角色关联表'; + +-- ---------------------------- +-- Records of system_user_role +-- ---------------------------- +BEGIN; +INSERT INTO `system_user_role` (`id`, `user_id`, `role_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (1, 1, 1, '', '2022-01-11 13:19:45', '', '2022-05-12 12:35:17', b'0', 1); +INSERT INTO `system_user_role` (`id`, `user_id`, `role_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2, 2, 2, '', '2022-01-11 13:19:45', '', '2022-05-12 12:35:13', b'0', 1); +INSERT INTO `system_user_role` (`id`, `user_id`, `role_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (4, 100, 101, '', '2022-01-11 13:19:45', '', '2022-05-12 12:35:13', b'0', 1); +INSERT INTO `system_user_role` (`id`, `user_id`, `role_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (5, 100, 1, '', '2022-01-11 13:19:45', '', '2022-05-12 12:35:12', b'0', 1); +INSERT INTO `system_user_role` (`id`, `user_id`, `role_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (6, 100, 2, '', '2022-01-11 13:19:45', '', '2022-05-12 12:35:11', b'0', 1); +INSERT INTO `system_user_role` (`id`, `user_id`, `role_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (10, 103, 1, '1', '2022-01-11 13:19:45', '1', '2022-01-11 13:19:45', b'0', 1); +INSERT INTO `system_user_role` (`id`, `user_id`, `role_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (14, 110, 109, '1', '2022-02-22 00:56:14', '1', '2022-02-22 00:56:14', b'0', 121); +INSERT INTO `system_user_role` (`id`, `user_id`, `role_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (15, 111, 110, '110', '2022-02-23 13:14:38', '110', '2022-02-23 13:14:38', b'0', 121); +INSERT INTO `system_user_role` (`id`, `user_id`, `role_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (16, 113, 111, '1', '2022-03-07 21:37:58', '1', '2022-03-07 21:37:58', b'0', 122); +INSERT INTO `system_user_role` (`id`, `user_id`, `role_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (18, 1, 2, '1', '2022-05-12 20:39:29', '1', '2022-05-12 20:39:29', b'0', 1); +INSERT INTO `system_user_role` (`id`, `user_id`, `role_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (20, 104, 101, '1', '2022-05-28 15:43:57', '1', '2022-05-28 15:43:57', b'0', 1); +INSERT INTO `system_user_role` (`id`, `user_id`, `role_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (22, 115, 2, '1', '2022-07-21 22:08:30', '1', '2022-07-21 22:08:30', b'0', 1); +INSERT INTO `system_user_role` (`id`, `user_id`, `role_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (35, 112, 1, '1', '2024-03-15 20:00:24', '1', '2024-03-15 20:00:24', b'0', 1); +INSERT INTO `system_user_role` (`id`, `user_id`, `role_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (36, 118, 1, '1', '2024-03-17 09:12:08', '1', '2024-03-17 09:12:08', b'0', 1); +INSERT INTO `system_user_role` (`id`, `user_id`, `role_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (38, 114, 101, '1', '2024-03-24 22:23:03', '1', '2024-03-24 22:23:03', b'0', 1); +INSERT INTO `system_user_role` (`id`, `user_id`, `role_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (46, 117, 1, '1', '2024-10-02 10:16:11', '1', '2024-10-02 10:16:11', b'0', 1); +COMMIT; + +-- ---------------------------- +-- Table structure for system_users +-- ---------------------------- +DROP TABLE IF EXISTS `system_users`; +CREATE TABLE `system_users` +( + `id` bigint NOT NULL AUTO_INCREMENT COMMENT '用户ID', + `username` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '用户账号', + `password` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '密码', + `nickname` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '用户昵称', + `remark` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '备注', + `dept_id` bigint NULL DEFAULT NULL COMMENT '部门ID', + `post_ids` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '岗位编号数组', + `email` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '用户邮箱', + `mobile` varchar(11) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '手机号码', + `sex` tinyint NULL DEFAULT 0 COMMENT '用户性别', + `avatar` varchar(512) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '头像地址', + `status` tinyint NOT NULL DEFAULT 0 COMMENT '帐号状态(0正常 1停用)', + `login_ip` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '最后登录IP', + `login_date` datetime NULL DEFAULT NULL COMMENT '最后登录时间', + `creator` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '创建者', + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `updater` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '更新者', + `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除', + `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 140 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '用户信息表'; + +-- ---------------------------- +-- Records of system_users +-- ---------------------------- +BEGIN; +INSERT INTO `system_users` (`id`, `username`, `password`, `nickname`, `remark`, `dept_id`, `post_ids`, `email`, + `mobile`, `sex`, `avatar`, `status`, `login_ip`, `login_date`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`, `tenant_id`) +VALUES (1, 'admin', '$2a$10$mRMIYLDtRHlf6.9ipiqH1.Z.bh/R9dO9d5iHiGYPigi6r5KOoR2Wm', '芋道源码', '管理员', 103, '[1,2]', + 'aoteman@126.com', '18818260277', 2, + 'http://test.yudao.iocoder.cn/bf2002b38950c904243be7c825d3f82e29f25a44526583c3fde2ebdff3a87f75.png', 0, + '0:0:0:0:0:0:0:1', '2024-12-28 20:29:58', 'admin', '2021-01-05 17:03:47', NULL, '2024-12-28 20:29:58', b'0', 1); +INSERT INTO `system_users` (`id`, `username`, `password`, `nickname`, `remark`, `dept_id`, `post_ids`, `email`, + `mobile`, `sex`, `avatar`, `status`, `login_ip`, `login_date`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`, `tenant_id`) +VALUES (100, 'yudao', '$2a$04$IgUse/ibRzAZ3rngCThmtemJeoh15Ux1TQ2hIMe4iwt/K3LcFHEda', '芋道', '不要吓我', 104, '[1]', + 'yudao@iocoder.cn', '15601691300', 1, '', 0, '0:0:0:0:0:0:0:1', '2024-11-02 14:00:46', '', + '2021-01-07 09:07:17', NULL, '2024-11-02 14:00:46', b'0', 1); +INSERT INTO `system_users` (`id`, `username`, `password`, `nickname`, `remark`, `dept_id`, `post_ids`, `email`, + `mobile`, `sex`, `avatar`, `status`, `login_ip`, `login_date`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`, `tenant_id`) +VALUES (103, 'yuanma', '$2a$04$fUBSmjKCPYAUmnMzOb6qE.eZCGPhHi1JmAKclODbfS/O7fHOl2bH6', '源码', NULL, 106, NULL, + 'yuanma@iocoder.cn', '15601701300', 0, '', 0, '0:0:0:0:0:0:0:1', '2024-08-11 17:48:12', '', + '2021-01-13 23:50:35', NULL, '2024-08-11 17:48:12', b'0', 1); +INSERT INTO `system_users` (`id`, `username`, `password`, `nickname`, `remark`, `dept_id`, `post_ids`, `email`, + `mobile`, `sex`, `avatar`, `status`, `login_ip`, `login_date`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`, `tenant_id`) +VALUES (104, 'test', '$2a$04$jDFLttgfik0QqJKAbfhMa.2A9xXoZmAIxakdFJUzkX.MgBKT6ddo6', '测试号', NULL, 107, '[1,2]', + '111@qq.com', '15601691200', 1, '', 0, '0:0:0:0:0:0:0:1', '2024-09-17 15:05:43', '', '2021-01-21 02:13:53', + NULL, '2024-09-17 15:05:43', b'0', 1); +INSERT INTO `system_users` (`id`, `username`, `password`, `nickname`, `remark`, `dept_id`, `post_ids`, `email`, + `mobile`, `sex`, `avatar`, `status`, `login_ip`, `login_date`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`, `tenant_id`) +VALUES (107, 'admin107', '$2a$10$dYOOBKMO93v/.ReCqzyFg.o67Tqk.bbc2bhrpyBGkIw9aypCtr2pm', '芋艿', NULL, NULL, NULL, '', + '15601691300', 0, '', 0, '', NULL, '1', '2022-02-20 22:59:33', '1', '2022-02-27 08:26:51', b'0', 118); +INSERT INTO `system_users` (`id`, `username`, `password`, `nickname`, `remark`, `dept_id`, `post_ids`, `email`, + `mobile`, `sex`, `avatar`, `status`, `login_ip`, `login_date`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`, `tenant_id`) +VALUES (108, 'admin108', '$2a$10$y6mfvKoNYL1GXWak8nYwVOH.kCWqjactkzdoIDgiKl93WN3Ejg.Lu', '芋艿', NULL, NULL, NULL, '', + '15601691300', 0, '', 0, '', NULL, '1', '2022-02-20 23:00:50', '1', '2022-02-27 08:26:53', b'0', 119); +INSERT INTO `system_users` (`id`, `username`, `password`, `nickname`, `remark`, `dept_id`, `post_ids`, `email`, + `mobile`, `sex`, `avatar`, `status`, `login_ip`, `login_date`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`, `tenant_id`) +VALUES (109, 'admin109', '$2a$10$JAqvH0tEc0I7dfDVBI7zyuB4E3j.uH6daIjV53.vUS6PknFkDJkuK', '芋艿', NULL, NULL, NULL, '', + '15601691300', 0, '', 0, '', NULL, '1', '2022-02-20 23:11:50', '1', '2022-02-27 08:26:56', b'0', 120); +INSERT INTO `system_users` (`id`, `username`, `password`, `nickname`, `remark`, `dept_id`, `post_ids`, `email`, + `mobile`, `sex`, `avatar`, `status`, `login_ip`, `login_date`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`, `tenant_id`) +VALUES (110, 'admin110', '$2a$10$mRMIYLDtRHlf6.9ipiqH1.Z.bh/R9dO9d5iHiGYPigi6r5KOoR2Wm', '小王', NULL, NULL, NULL, '', + '15601691300', 0, '', 0, '0:0:0:0:0:0:0:1', '2024-07-20 22:23:17', '1', '2022-02-22 00:56:14', NULL, + '2024-07-20 22:23:17', b'0', 121); +INSERT INTO `system_users` (`id`, `username`, `password`, `nickname`, `remark`, `dept_id`, `post_ids`, `email`, + `mobile`, `sex`, `avatar`, `status`, `login_ip`, `login_date`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`, `tenant_id`) +VALUES (111, 'test', '$2a$10$mRMIYLDtRHlf6.9ipiqH1.Z.bh/R9dO9d5iHiGYPigi6r5KOoR2Wm', '测试用户', NULL, NULL, '[]', '', + '', 0, '', 0, '0:0:0:0:0:0:0:1', '2023-12-30 11:42:17', '110', '2022-02-23 13:14:33', NULL, + '2023-12-30 11:42:17', b'0', 121); +INSERT INTO `system_users` (`id`, `username`, `password`, `nickname`, `remark`, `dept_id`, `post_ids`, `email`, + `mobile`, `sex`, `avatar`, `status`, `login_ip`, `login_date`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`, `tenant_id`) +VALUES (112, 'newobject', '$2a$04$dB0z8Q819fJWz0hbaLe6B.VfHCjYgWx6LFfET5lyz3JwcqlyCkQ4C', '新对象', NULL, 100, '[]', '', + '15601691235', 1, '', 0, '0:0:0:0:0:0:0:1', '2024-03-16 23:11:38', '1', '2022-02-23 19:08:03', NULL, + '2024-03-16 23:11:38', b'0', 1); +INSERT INTO `system_users` (`id`, `username`, `password`, `nickname`, `remark`, `dept_id`, `post_ids`, `email`, + `mobile`, `sex`, `avatar`, `status`, `login_ip`, `login_date`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`, `tenant_id`) +VALUES (113, 'aoteman', '$2a$10$0acJOIk2D25/oC87nyclE..0lzeu9DtQ/n3geP4fkun/zIVRhHJIO', '芋道', NULL, NULL, NULL, '', + '15601691300', 0, '', 0, '127.0.0.1', '2022-03-19 18:38:51', '1', '2022-03-07 21:37:58', NULL, + '2022-03-19 18:38:51', b'0', 122); +INSERT INTO `system_users` (`id`, `username`, `password`, `nickname`, `remark`, `dept_id`, `post_ids`, `email`, + `mobile`, `sex`, `avatar`, `status`, `login_ip`, `login_date`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`, `tenant_id`) +VALUES (114, 'hrmgr', '$2a$10$TR4eybBioGRhBmDBWkqWLO6NIh3mzYa8KBKDDB5woiGYFVlRAi.fu', 'hr 小姐姐', NULL, NULL, '[5]', + '', '15601691236', 1, '', 0, '0:0:0:0:0:0:0:1', '2024-03-24 22:21:05', '1', '2022-03-19 21:50:58', NULL, + '2024-03-24 22:21:05', b'0', 1); +INSERT INTO `system_users` (`id`, `username`, `password`, `nickname`, `remark`, `dept_id`, `post_ids`, `email`, + `mobile`, `sex`, `avatar`, `status`, `login_ip`, `login_date`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`, `tenant_id`) +VALUES (115, 'aotemane', '$2a$04$GcyP0Vyzb2F2Yni5PuIK9ueGxM0tkZGMtDwVRwrNbtMvorzbpNsV2', '阿呆', '11222', 102, '[1,2]', + '7648@qq.com', '15601691229', 2, '', 0, '', NULL, '1', '2022-04-30 02:55:43', '1', '2024-04-04 09:37:14', b'0', + 1); +INSERT INTO `system_users` (`id`, `username`, `password`, `nickname`, `remark`, `dept_id`, `post_ids`, `email`, + `mobile`, `sex`, `avatar`, `status`, `login_ip`, `login_date`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`, `tenant_id`) +VALUES (117, 'admin123', '$2a$04$sEtimsHu9YCkYY4/oqElHem2Ijc9ld20eYO6lN.g/21NfLUTDLB9W', '测试号02', '1111', 100, '[2]', + '', '15601691234', 1, '', 0, '0:0:0:0:0:0:0:1', '2024-10-02 10:16:20', '1', '2022-07-09 17:40:26', NULL, + '2024-10-02 10:16:20', b'0', 1); +INSERT INTO `system_users` (`id`, `username`, `password`, `nickname`, `remark`, `dept_id`, `post_ids`, `email`, + `mobile`, `sex`, `avatar`, `status`, `login_ip`, `login_date`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`, `tenant_id`) +VALUES (118, 'goudan', '$2a$04$OB1SuphCdiLVRpiYRKeqH.8NYS7UIp5vmIv1W7U4w6toiFeOAATVK', '狗蛋', NULL, 103, '[1]', '', + '15601691239', 1, '', 0, '0:0:0:0:0:0:0:1', '2024-03-17 09:10:27', '1', '2022-07-09 17:44:43', '1', + '2024-09-06 21:40:43', b'0', 1); +INSERT INTO `system_users` (`id`, `username`, `password`, `nickname`, `remark`, `dept_id`, `post_ids`, `email`, + `mobile`, `sex`, `avatar`, `status`, `login_ip`, `login_date`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`, `tenant_id`) +VALUES (131, 'hh', '$2a$04$jyH9h6.gaw8mpOjPfHIpx.8as2Rzfcmdlj5rlJFwgCw4rsv/MTb2K', '呵呵', NULL, 100, '[]', + '777@qq.com', '15601882312', 1, '', 0, '', NULL, '1', '2024-04-27 08:45:56', '1', '2024-04-27 08:45:56', b'0', + 1); +INSERT INTO `system_users` (`id`, `username`, `password`, `nickname`, `remark`, `dept_id`, `post_ids`, `email`, + `mobile`, `sex`, `avatar`, `status`, `login_ip`, `login_date`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`, `tenant_id`) +VALUES (139, 'wwbwwb', '$2a$04$aOHoFbQU6zfBk/1Z9raF/ugTdhjNdx7culC1HhO0zvoczAnahCiMq', '小秃头', NULL, NULL, NULL, '', + '', 0, '', 0, '0:0:0:0:0:0:0:1', '2024-09-10 21:03:58', NULL, '2024-09-10 21:03:58', NULL, + '2024-09-10 21:03:58', b'0', 1); +COMMIT; + +-- ---------------------------- +-- Table structure for yudao_demo01_contact +-- ---------------------------- +DROP TABLE IF EXISTS `yudao_demo01_contact`; +CREATE TABLE `yudao_demo01_contact` +( + `id` bigint NOT NULL AUTO_INCREMENT COMMENT '编号', + `name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '名字', + `sex` tinyint(1) NOT NULL COMMENT '性别', + `birthday` datetime NOT NULL COMMENT '出生年', + `description` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '简介', + `avatar` varchar(512) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '头像', + `creator` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '创建者', + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `updater` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '更新者', + `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除', + `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '示例联系人表'; + +-- ---------------------------- +-- Records of yudao_demo01_contact +-- ---------------------------- +BEGIN; +INSERT INTO `yudao_demo01_contact` (`id`, `name`, `sex`, `birthday`, `description`, `avatar`, `creator`, `create_time`, + `updater`, `update_time`, `deleted`, `tenant_id`) +VALUES (1, '土豆', 2, '2023-11-07 00:00:00', '

天蚕土豆!呀

', + 'http://127.0.0.1:48080/admin-api/infra/file/4/get/46f8fa1a37db3f3960d8910ff2fe3962ab3b2db87cf2f8ccb4dc8145b8bdf237.jpeg', + '1', '2023-11-15 23:34:30', '1', '2023-11-15 23:47:39', b'0', 1); +COMMIT; + +-- ---------------------------- +-- Table structure for yudao_demo02_category +-- ---------------------------- +DROP TABLE IF EXISTS `yudao_demo02_category`; +CREATE TABLE `yudao_demo02_category` +( + `id` bigint NOT NULL AUTO_INCREMENT COMMENT '编号', + `name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '名字', + `parent_id` bigint NOT NULL COMMENT '父级编号', + `creator` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '创建者', + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `updater` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '更新者', + `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除', + `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 7 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '示例分类表'; + +-- ---------------------------- +-- Records of yudao_demo02_category +-- ---------------------------- +BEGIN; +INSERT INTO `yudao_demo02_category` (`id`, `name`, `parent_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (1, '土豆', 0, '1', '2023-11-15 23:34:30', '1', '2023-11-16 20:24:23', b'0', 1); +INSERT INTO `yudao_demo02_category` (`id`, `name`, `parent_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (2, '番茄', 0, '1', '2023-11-16 20:24:00', '1', '2023-11-16 20:24:15', b'0', 1); +INSERT INTO `yudao_demo02_category` (`id`, `name`, `parent_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (3, '怪怪', 0, '1', '2023-11-16 20:24:32', '1', '2023-11-16 20:24:32', b'0', 1); +INSERT INTO `yudao_demo02_category` (`id`, `name`, `parent_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (4, '小番茄', 2, '1', '2023-11-16 20:24:39', '1', '2023-11-16 20:24:39', b'0', 1); +INSERT INTO `yudao_demo02_category` (`id`, `name`, `parent_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (5, '大番茄', 2, '1', '2023-11-16 20:24:46', '1', '2023-11-16 20:24:46', b'0', 1); +INSERT INTO `yudao_demo02_category` (`id`, `name`, `parent_id`, `creator`, `create_time`, `updater`, `update_time`, + `deleted`, `tenant_id`) +VALUES (6, '11', 3, '1', '2023-11-24 19:29:34', '1', '2023-11-24 19:29:34', b'0', 1); +COMMIT; + +-- ---------------------------- +-- Table structure for yudao_demo03_course +-- ---------------------------- +DROP TABLE IF EXISTS `yudao_demo03_course`; +CREATE TABLE `yudao_demo03_course` +( + `id` bigint NOT NULL AUTO_INCREMENT COMMENT '编号', + `student_id` bigint NOT NULL COMMENT '学生编号', + `name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '名字', + `score` tinyint NOT NULL COMMENT '分数', + `creator` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '创建者', + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `updater` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '更新者', + `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除', + `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 20 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '学生课程表'; + +-- ---------------------------- +-- Records of yudao_demo03_course +-- ---------------------------- +BEGIN; +INSERT INTO `yudao_demo03_course` (`id`, `student_id`, `name`, `score`, `creator`, `create_time`, `updater`, + `update_time`, `deleted`, `tenant_id`) +VALUES (2, 2, '语文', 66, '1', '2023-11-16 23:21:49', '1', '2024-09-17 10:55:30', b'1', 1); +INSERT INTO `yudao_demo03_course` (`id`, `student_id`, `name`, `score`, `creator`, `create_time`, `updater`, + `update_time`, `deleted`, `tenant_id`) +VALUES (3, 2, '数学', 22, '1', '2023-11-16 23:21:49', '1', '2024-09-17 10:55:30', b'1', 1); +INSERT INTO `yudao_demo03_course` (`id`, `student_id`, `name`, `score`, `creator`, `create_time`, `updater`, + `update_time`, `deleted`, `tenant_id`) +VALUES (6, 5, '体育', 23, '1', '2023-11-16 23:22:46', '1', '2023-11-16 15:44:40', b'1', 1); +INSERT INTO `yudao_demo03_course` (`id`, `student_id`, `name`, `score`, `creator`, `create_time`, `updater`, + `update_time`, `deleted`, `tenant_id`) +VALUES (7, 5, '计算机', 11, '1', '2023-11-16 23:22:46', '1', '2023-11-16 15:44:40', b'1', 1); +INSERT INTO `yudao_demo03_course` (`id`, `student_id`, `name`, `score`, `creator`, `create_time`, `updater`, + `update_time`, `deleted`, `tenant_id`) +VALUES (8, 5, '体育', 23, '1', '2023-11-16 23:22:46', '1', '2023-11-16 15:47:09', b'1', 1); +INSERT INTO `yudao_demo03_course` (`id`, `student_id`, `name`, `score`, `creator`, `create_time`, `updater`, + `update_time`, `deleted`, `tenant_id`) +VALUES (9, 5, '计算机', 11, '1', '2023-11-16 23:22:46', '1', '2023-11-16 15:47:09', b'1', 1); +INSERT INTO `yudao_demo03_course` (`id`, `student_id`, `name`, `score`, `creator`, `create_time`, `updater`, + `update_time`, `deleted`, `tenant_id`) +VALUES (10, 5, '体育', 23, '1', '2023-11-16 23:22:46', '1', '2024-09-17 10:55:28', b'1', 1); +INSERT INTO `yudao_demo03_course` (`id`, `student_id`, `name`, `score`, `creator`, `create_time`, `updater`, + `update_time`, `deleted`, `tenant_id`) +VALUES (11, 5, '计算机', 11, '1', '2023-11-16 23:22:46', '1', '2024-09-17 10:55:28', b'1', 1); +INSERT INTO `yudao_demo03_course` (`id`, `student_id`, `name`, `score`, `creator`, `create_time`, `updater`, + `update_time`, `deleted`, `tenant_id`) +VALUES (12, 2, '电脑', 33, '1', '2023-11-17 00:20:42', '1', '2023-11-16 16:20:45', b'1', 1); +INSERT INTO `yudao_demo03_course` (`id`, `student_id`, `name`, `score`, `creator`, `create_time`, `updater`, + `update_time`, `deleted`, `tenant_id`) +VALUES (13, 9, '滑雪', 12, '1', '2023-11-17 13:13:20', '1', '2024-09-17 10:55:26', b'1', 1); +INSERT INTO `yudao_demo03_course` (`id`, `student_id`, `name`, `score`, `creator`, `create_time`, `updater`, + `update_time`, `deleted`, `tenant_id`) +VALUES (14, 9, '滑雪', 12, '1', '2023-11-17 13:13:20', '1', '2024-09-17 10:55:49', b'1', 1); +INSERT INTO `yudao_demo03_course` (`id`, `student_id`, `name`, `score`, `creator`, `create_time`, `updater`, + `update_time`, `deleted`, `tenant_id`) +VALUES (15, 5, '体育', 23, '1', '2023-11-16 23:22:46', '1', '2024-09-17 18:55:29', b'0', 1); +INSERT INTO `yudao_demo03_course` (`id`, `student_id`, `name`, `score`, `creator`, `create_time`, `updater`, + `update_time`, `deleted`, `tenant_id`) +VALUES (16, 5, '计算机', 11, '1', '2023-11-16 23:22:46', '1', '2024-09-17 18:55:29', b'0', 1); +INSERT INTO `yudao_demo03_course` (`id`, `student_id`, `name`, `score`, `creator`, `create_time`, `updater`, + `update_time`, `deleted`, `tenant_id`) +VALUES (17, 2, '语文', 66, '1', '2023-11-16 23:21:49', '1', '2024-09-17 18:55:31', b'0', 1); +INSERT INTO `yudao_demo03_course` (`id`, `student_id`, `name`, `score`, `creator`, `create_time`, `updater`, + `update_time`, `deleted`, `tenant_id`) +VALUES (18, 2, '数学', 22, '1', '2023-11-16 23:21:49', '1', '2024-09-17 18:55:31', b'0', 1); +INSERT INTO `yudao_demo03_course` (`id`, `student_id`, `name`, `score`, `creator`, `create_time`, `updater`, + `update_time`, `deleted`, `tenant_id`) +VALUES (19, 9, '滑雪', 12, '1', '2023-11-17 13:13:20', '1', '2024-09-17 18:55:50', b'0', 1); +COMMIT; + +-- ---------------------------- +-- Table structure for yudao_demo03_grade +-- ---------------------------- +DROP TABLE IF EXISTS `yudao_demo03_grade`; +CREATE TABLE `yudao_demo03_grade` +( + `id` bigint NOT NULL AUTO_INCREMENT COMMENT '编号', + `student_id` bigint NOT NULL COMMENT '学生编号', + `name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '名字', + `teacher` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '班主任', + `creator` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '创建者', + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `updater` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '更新者', + `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除', + `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 10 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '学生班级表'; + +-- ---------------------------- +-- Records of yudao_demo03_grade +-- ---------------------------- +BEGIN; +INSERT INTO `yudao_demo03_grade` (`id`, `student_id`, `name`, `teacher`, `creator`, `create_time`, `updater`, + `update_time`, `deleted`, `tenant_id`) +VALUES (7, 2, '三年 2 班', '周杰伦', '1', '2023-11-16 23:21:49', '1', '2024-09-17 18:55:31', b'0', 1); +INSERT INTO `yudao_demo03_grade` (`id`, `student_id`, `name`, `teacher`, `creator`, `create_time`, `updater`, + `update_time`, `deleted`, `tenant_id`) +VALUES (8, 5, '华为', '遥遥领先', '1', '2023-11-16 23:22:46', '1', '2024-09-17 18:55:29', b'0', 1); +INSERT INTO `yudao_demo03_grade` (`id`, `student_id`, `name`, `teacher`, `creator`, `create_time`, `updater`, + `update_time`, `deleted`, `tenant_id`) +VALUES (9, 9, '小图', '小娃111', '1', '2023-11-17 13:10:23', '1', '2024-09-17 18:55:50', b'0', 1); +COMMIT; + +-- ---------------------------- +-- Table structure for yudao_demo03_student +-- ---------------------------- +DROP TABLE IF EXISTS `yudao_demo03_student`; +CREATE TABLE `yudao_demo03_student` +( + `id` bigint NOT NULL AUTO_INCREMENT COMMENT '编号', + `name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '名字', + `sex` tinyint NOT NULL COMMENT '性别', + `birthday` datetime NOT NULL COMMENT '出生日期', + `description` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '简介', + `creator` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '创建者', + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `updater` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '更新者', + `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除', + `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 10 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '学生表'; + +-- ---------------------------- +-- Records of yudao_demo03_student +-- ---------------------------- +BEGIN; +INSERT INTO `yudao_demo03_student` (`id`, `name`, `sex`, `birthday`, `description`, `creator`, `create_time`, `updater`, + `update_time`, `deleted`, `tenant_id`) +VALUES (2, '小白', 1, '2023-11-16 00:00:00', '

厉害

', '1', '2023-11-16 23:21:49', '1', '2024-09-17 18:55:31', + b'0', 1); +INSERT INTO `yudao_demo03_student` (`id`, `name`, `sex`, `birthday`, `description`, `creator`, `create_time`, `updater`, + `update_time`, `deleted`, `tenant_id`) +VALUES (5, '大黑', 2, '2023-11-13 00:00:00', '

你在教我做事?

', '1', '2023-11-16 23:22:46', '1', + '2024-09-17 18:55:29', b'0', 1); +INSERT INTO `yudao_demo03_student` (`id`, `name`, `sex`, `birthday`, `description`, `creator`, `create_time`, `updater`, + `update_time`, `deleted`, `tenant_id`) +VALUES (9, '小花', 1, '2023-11-07 00:00:00', '

哈哈哈

', '1', '2023-11-17 00:04:47', '1', '2024-09-17 18:55:50', + b'0', 1); +COMMIT; + +SET +FOREIGN_KEY_CHECKS = 1; diff --git a/sql/tools/.gitignore b/sql/tools/.gitignore new file mode 100644 index 0000000..e00c3e7 --- /dev/null +++ b/sql/tools/.gitignore @@ -0,0 +1,8 @@ +# 忽略python虚拟环境 +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ diff --git a/sql/tools/README.md b/sql/tools/README.md new file mode 100644 index 0000000..94c5300 --- /dev/null +++ b/sql/tools/README.md @@ -0,0 +1,130 @@ +## 0. 友情提示 + +在 `sql/tools` 目录下,我们提供一些数据库相关的工具,包括测试数据库的快速启动、MySQL 转换其它数据库等等。 + +注意!所有的操作,必须在 `sql/tools` 目录下执行。 + +## 1. 测试数据库的快速启动 + +基于 Docker Compose,快速启动 MySQL、Oracle、PostgreSQL、SQL Server 等数据库。 + +注意!使用 Docker Compose 启动完测试数据后,因为会自动导入项目的 SQL 脚本,所以可能需要等待 1-2 分钟。 + +### 1.1 MySQL + +```Bash +docker compose up -d mysql +``` + +#### 1.2 Oracle + +```Bash +## x86 版本 +docker compose up -d oracle + +## MacBook Apple Silicon +docker compose up -d oracle_m1 +``` + +> 注意:如果使用 MacBook Apple Silicon 版本,它的 ORACLE_SID 不是 XE,而是 FREE!!! + +### 1.3 PostgreSQL + +```Bash +docker compose up -d postgres +``` + +### 1.4 SQL Server + +```Bash +docker compose up -d sqlserver +# 注意:启动完 sqlserver 后,需要手动再执行如下命令,因为 SQL Server 不支持初始化脚本 +docker compose exec sqlserver bash /tmp/create_schema.sh +``` + +### 1.5 DM 达梦 + +① 下载达梦 Docker 镜像: 地址,点击“Docker 镜像”选项,进行下载。 + +② 加载镜像文件,在镜像 tar 文件所在目录运行: + +```Bash +docker load -i dm8_20240715_x86_rh6_rq_single.tar +``` + +③ 在项目 `sql/tools` 目录下运行: + +```Bash +docker compose up -d dm8 +# 注意:启动完 dm 后,需要手动再执行如下命令,因为 dm 不支持初始化脚本 +docker compose exec dm8 bash -c '/opt/dmdbms/bin/disql SYSDBA/SYSDBA001 \`/tmp/schema.sql' +exit +``` + +### 1.6 KingbaseES 人大金仓 + +① 下载人大金仓 Docker 镜像: + +* [x86_64 版本](https://kingbase.oss-cn-beijing.aliyuncs.com/KESV8R3/V009R001C001B0025-安装包-docker/x86_64/kdb_x86_64_V009R001C001B0025.tar) 【Windows 选择这个】 +* [aarch64 版本](https://kingbase.oss-cn-beijing.aliyuncs.com/KESV8R3/V009R001C001B0025-安装包-docker/aarch64/kdb_aarch64_V009R001C001B0025.tar) 【MacBook Apple Silicon 选择这个】 + +② 加载镜像文件,在镜像 tar 文件所在目录运行: + +```Bash +docker load -i kdb_x86_64_V009R001C001B0025.tar +``` + +③ 在项目 `sql/tools` 目录下运行: + +```Bash +docker compose up -d kingbase +# 注意:启动完 kingbase 后,需要手动再执行如下命令 +docker compose exec kingbase bash -c 'ksql -U $DB_USER -d test -f /tmp/schema.sql' +``` + +### 1.7 华为 OpenGauss + +```Bash +docker compose up -d opengauss +# 注意:启动完 opengauss 后,需要手动再执行如下命令 +docker compose exec opengauss bash -c '/usr/local/opengauss/bin/gsql -U $GS_USERNAME -W $GS_PASSWORD -d postgres -f /tmp/schema.sql' +``` + +## 1.X 容器的销毁重建 + +开发测试过程中,有时候需要创建全新干净的数据库。由于测试数据 Docker 容器采用数据卷 Volume 挂载数据库实例的数据目录,因此销毁数据需要停止容器后,删除数据卷,然后再重新创建容器。 + +以 postgres 为例,操作如下: + +```Bash +docker compose down postgres +docker volume rm ruoyi-vue-pro_postgres +``` + +## 2. MySQL 转换其它数据库 + +项目提供了 `sql/tools/convertor.py` 脚本,支持将 MySQL 转换为 Oracle、PostgreSQL、SQL Server、达梦、人大金仓、OpenGauss 等数据库的脚本。 + +### 2.1 实现原理 + +通过读取 MySQL 的 `sql/mysql/ruoyi-vue-pro.sql` 数据库文件,转换成对应的数据库脚本。 + +### 2.2 使用方法 + +① 安装依赖库 `simple-ddl-parser` + +```bash +pip install simple-ddl-parser +# pip3 install simple-ddl-parser +``` + +② 在 `sql/tools/` 目录下,执行如下命令打印生成 postgres 的脚本内容,其他可选参数有:`oracle`、`sqlserver`、`dm8`、`kingbase`、`opengauss`: + +```Bash +python3 convertor.py postgres +# python3 convertor.py postgres > tmp.sql +``` + +程序将 SQL 脚本打印到终端,可以重定向到临时文件 `tmp.sql`。 + +确认无误后,可以利用 IDEA 进行格式化。当然,也可以直接导入到数据库中。 diff --git a/sql/tools/convertor.py b/sql/tools/convertor.py new file mode 100644 index 0000000..f672cd7 --- /dev/null +++ b/sql/tools/convertor.py @@ -0,0 +1,844 @@ +# encoding=utf8 +"""芋道系统数据库迁移工具 + +Author: dhb52 (https://gitee.com/dhb52) + +pip install simple-ddl-parser +""" + +import argparse +import pathlib +import re +import time +from abc import ABC, abstractmethod +from typing import Dict, Generator, Optional, Tuple, Union + +from simple_ddl_parser import DDLParser + +PREAMBLE = """/* + Yudao Database Transfer Tool + + Source Server Type : MySQL + + Target Server Type : {db_type} + + Date: {date} +*/ + +""" + + +def load_and_clean(sql_file: str) -> str: + """加载源 SQL 文件,并清理内容方便下一步 ddl 解析 + + Args: + sql_file (str): sql文件路径 + + Returns: + str: 清理后的sql文件内容 + """ + REPLACE_PAIR_LIST = ( + (" CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci ", " "), + (" KEY `", " INDEX `"), + ("UNIQUE INDEX", "UNIQUE KEY"), + ("b'0'", "'0'"), + ("b'1'", "'1'"), + ) + + content = open(sql_file).read() + for replace_pair in REPLACE_PAIR_LIST: + content = content.replace(*replace_pair) + content = re.sub(r"ENGINE.*COMMENT", "COMMENT", content) + content = re.sub(r"ENGINE.*;", ";", content) + return content + + +class Convertor(ABC): + def __init__(self, src: str, db_type) -> None: + self.src = src + self.db_type = db_type + self.content = load_and_clean(self.src) + self.table_script_list = re.findall(r"CREATE TABLE [^;]*;", self.content) + + @abstractmethod + def translate_type(self, type: str, size: Optional[Union[int, Tuple[int]]]) -> str: + """字段类型转换 + + Args: + type (str): 字段类型 + size (Optional[Union[int, Tuple[int]]]): 字段长度描述, 如varchar(255), decimal(10,2) + + Returns: + str: 类型定义 + """ + pass + + @abstractmethod + def gen_create(self, table_ddl: Dict) -> str: + """生成 create 脚本 + + Args: + table_ddl (Dict): 表DDL + + Returns: + str: 生成脚本 + """ + pass + + @abstractmethod + def gen_pk(self, table_name: str) -> str: + """生成主键定义 + + Args: + table_name (str): 表名 + + Returns: + str: 生成脚本 + """ + pass + + @abstractmethod + def gen_index(self, ddl: Dict) -> str: + """生成索引定义 + + Args: + table_ddl (Dict): 表DDL + + Returns: + str: 生成脚本 + """ + pass + + @abstractmethod + def gen_comment(self, table_sql: str, table_name: str) -> str: + """生成字段/表注释 + + Args: + table_sql (str): 原始表SQL + table_name (str): 表名 + + Returns: + str: 生成脚本 + """ + pass + + @abstractmethod + def gen_insert(self, table_name: str) -> str: + """生成 insert 语句块 + + Args: + table_name (str): 表名 + + Returns: + str: 生成脚本 + """ + pass + + def gen_dual(self) -> str: + """生成虚拟 dual 表 + + Returns: + str: 生成脚本, 默认返回空脚本, 表示当前数据库无需手工创建 + """ + return "" + + @staticmethod + def inserts(table_name: str, script_content: str) -> Generator: + PREFIX = f"INSERT INTO `{table_name}`" + + # 收集 `table_name` 对应的 insert 语句 + for line in script_content.split("\n"): + if line.startswith(PREFIX): + head, tail = line.replace(PREFIX, "").split(" VALUES ", maxsplit=1) + head = head.strip().replace("`", "").lower() + tail = tail.strip().replace(r"\"", '"') + # tail = tail.replace("b'0'", "'0'").replace("b'1'", "'1'") + yield f"INSERT INTO {table_name.lower()} {head} VALUES {tail}" + + @staticmethod + def index(ddl: Dict) -> Generator: + """生成索引定义 + + Args: + ddl (Dict): 表DDL + + Yields: + Generator[str]: create index 语句 + """ + + def generate_columns(columns): + keys = [ + f"{col['name'].lower()}{' ' + col['order'].lower() if col['order'] != 'ASC' else ''}" + for col in columns[0] + ] + return ", ".join(keys) + + for no, index in enumerate(ddl["index"], 1): + columns = generate_columns(index["columns"]) + table_name = ddl["table_name"].lower() + yield f"CREATE INDEX idx_{table_name}_{no:02d} ON {table_name} ({columns})" + + @staticmethod + def filed_comments(table_sql: str) -> Generator: + for line in table_sql.split("\n"): + match = re.match(r"^`([^`]+)`.* COMMENT '([^']+)'", line.strip()) + if match: + field = match.group(1) + comment_string = match.group(2).replace("\\n", "\n") + yield field, comment_string + + def table_comment(self, table_sql: str) -> str: + match = re.search(r"COMMENT \= '([^']+)';", table_sql) + return match.group(1) if match else None + + def print(self): + """打印转换后的sql脚本到终端""" + print( + PREAMBLE.format( + db_type=self.db_type, + date=time.strftime("%Y-%m-%d %H:%M:%S"), + ) + ) + + dual = self.gen_dual() + if dual: + print( + f"""-- ---------------------------- +-- Table structure for dual +-- ---------------------------- +{dual} +""" + ) + + error_scripts = [] + for table_sql in self.table_script_list: + ddl = DDLParser(table_sql.replace("`", "")).run() + + # 如果parse失败, 需要跟进 + if len(ddl) == 0: + error_scripts.append(table_sql) + continue + + table_ddl = ddl[0] + table_name = table_ddl["table_name"] + + # 忽略 quartz 的内容 + if table_name.lower().startswith("qrtz"): + continue + + # 为每个表生成个5个基本部分 + create = self.gen_create(table_ddl) + pk = self.gen_pk(table_name) + index = self.gen_index(table_ddl) + comment = self.gen_comment(table_sql, table_name) + inserts = self.gen_insert(table_name) + + # 组合当前表的DDL脚本 + script = f"""{create} + +{pk} + +{index} + +{comment} + +{inserts} +""" + + # 清理 + script = re.sub("\n{3,}", "\n\n", script).strip() + "\n" + + print(script) + + # 将parse失败的脚本打印出来 + if error_scripts: + for script in error_scripts: + print(script) + + +class PostgreSQLConvertor(Convertor): + def __init__(self, src): + super().__init__(src, "PostgreSQL") + + def translate_type(self, type: str, size: Optional[Union[int, Tuple[int]]]): + """类型转换""" + + type = type.lower() + + if type == "varchar": + return f"varchar({size})" + if type == "int": + return "int4" + if type == "bigint" or type == "bigint unsigned": + return "int8" + if type == "datetime": + return "timestamp" + if type == "bit": + return "bool" + if type in ("tinyint", "smallint"): + return "int2" + if type == "text": + return "text" + if type in ("blob", "mediumblob"): + return "bytea" + if type == "decimal": + return ( + f"numeric({','.join(str(s) for s in size)})" if len(size) else "numeric" + ) + + def gen_create(self, ddl: Dict) -> str: + """生成 create""" + + def _generate_column(col): + name = col["name"].lower() + if name == "deleted": + return "deleted int2 NOT NULL DEFAULT 0" + + type = col["type"].lower() + full_type = self.translate_type(type, col["size"]) + nullable = "NULL" if col["nullable"] else "NOT NULL" + default = f"DEFAULT {col['default']}" if col["default"] is not None else "" + return f"{name} {full_type} {nullable} {default}" + + table_name = ddl["table_name"].lower() + columns = [f"{_generate_column(col).strip()}" for col in ddl["columns"]] + filed_def_list = ",\n ".join(columns) + script = f"""-- ---------------------------- +-- Table structure for {table_name} +-- ---------------------------- +DROP TABLE IF EXISTS {table_name}; +CREATE TABLE {table_name} ( + {filed_def_list} +);""" + + return script + + def gen_index(self, ddl: Dict) -> str: + return "\n".join(f"{script};" for script in self.index(ddl)) + + def gen_comment(self, table_sql: str, table_name: str) -> str: + """生成字段及表的注释""" + + script = "" + for field, comment_string in self.filed_comments(table_sql): + script += ( + f"COMMENT ON COLUMN {table_name}.{field} IS '{comment_string}';" + "\n" + ) + + table_comment = self.table_comment(table_sql) + if table_comment: + script += f"COMMENT ON TABLE {table_name} IS '{table_comment}';\n" + + return script + + def gen_pk(self, table_name) -> str: + """生成主键定义""" + return f"ALTER TABLE {table_name} ADD CONSTRAINT pk_{table_name} PRIMARY KEY (id);\n" + + def gen_insert(self, table_name: str) -> str: + """生成 insert 语句,以及根据最后的 insert id+1 生成 Sequence""" + + inserts = list(Convertor.inserts(table_name, self.content)) + ## 生成 insert 脚本 + script = "" + last_id = 0 + if inserts: + inserts_lines = "\n".join(inserts) + script += f"""\n\n-- ---------------------------- +-- Records of {table_name.lower()} +-- ---------------------------- +-- @formatter:off +BEGIN; +{inserts_lines} +COMMIT; +-- @formatter:on""" + match = re.search(r"VALUES \((\d+),", inserts[-1]) + if match: + last_id = int(match.group(1)) + + # 生成 Sequence + script += ( + "\n\n" + + f"""DROP SEQUENCE IF EXISTS {table_name}_seq; +CREATE SEQUENCE {table_name}_seq + START {last_id + 1};""" + ) + + return script + + def gen_dual(self) -> str: + return """DROP TABLE IF EXISTS dual; +CREATE TABLE dual +( + id int2 +); + +COMMENT ON TABLE dual IS '数据库连接的表'; + +-- ---------------------------- +-- Records of dual +-- ---------------------------- +-- @formatter:off +INSERT INTO dual VALUES (1); +-- @formatter:on""" + + +class OracleConvertor(Convertor): + def __init__(self, src): + super().__init__(src, "Oracle") + + def translate_type(self, type: str, size: Optional[Union[int, Tuple[int]]]): + """类型转换""" + type = type.lower() + + if type == "varchar": + return f"varchar2({size if size < 4000 else 4000})" + if type == "int": + return "number" + if type == "bigint" or type == "bigint unsigned": + return "number" + if type == "datetime": + return "date" + if type == "bit": + return "number(1,0)" + if type in ("tinyint", "smallint"): + return "smallint" + if type == "text": + return "clob" + if type in ("blob", "mediumblob"): + return "blob" + if type == "decimal": + return ( + f"number({','.join(str(s) for s in size)})" if len(size) else "number" + ) + + def gen_create(self, ddl) -> str: + """生成 CREATE 语句""" + + def generate_column(col): + name = col["name"].lower() + if name == "deleted": + return "deleted number(1,0) DEFAULT 0 NOT NULL" + + type = col["type"].lower() + full_type = self.translate_type(type, col["size"]) + nullable = "NULL" if col["nullable"] else "NOT NULL" + default = f"DEFAULT {col['default']}" if col["default"] is not None else "" + # Oracle 中 size 不能作为字段名 + field_name = '"size"' if name == "size" else name + # Oracle DEFAULT 定义在 NULLABLE 之前 + return f"{field_name} {full_type} {default} {nullable}" + + table_name = ddl["table_name"].lower() + columns = [f"{generate_column(col).strip()}" for col in ddl["columns"]] + field_def_list = ",\n ".join(columns) + script = f"""-- ---------------------------- +-- Table structure for {table_name} +-- ---------------------------- +CREATE TABLE {table_name} ( + {field_def_list} +);""" + + # oracle INSERT '' 不能通过 NOT NULL 校验 + script = script.replace("DEFAULT '' NOT NULL", "DEFAULT '' NULL") + + return script + + def gen_index(self, ddl: Dict) -> str: + return "\n".join(f"{script};" for script in self.index(ddl)) + + def gen_comment(self, table_sql: str, table_name: str) -> str: + script = "" + for field, comment_string in self.filed_comments(table_sql): + script += ( + f"COMMENT ON COLUMN {table_name}.{field} IS '{comment_string}';" + "\n" + ) + + table_comment = self.table_comment(table_sql) + if table_comment: + script += f"COMMENT ON TABLE {table_name} IS '{table_comment}';\n" + + return script + + def gen_pk(self, table_name: str) -> str: + """生成主键定义""" + return f"ALTER TABLE {table_name} ADD CONSTRAINT pk_{table_name} PRIMARY KEY (id);\n" + + def gen_index(self, ddl: Dict) -> str: + return "\n".join(f"{script};" for script in self.index(ddl)) + + def gen_insert(self, table_name: str) -> str: + """拷贝 INSERT 语句""" + inserts = [] + for insert_script in Convertor.inserts(table_name, self.content): + # 对日期数据添加 TO_DATE 转换 + insert_script = re.sub( + r"('\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}')", + r"to_date(\g<1>, 'SYYYY-MM-DD HH24:MI:SS')", + insert_script, + ) + inserts.append(insert_script) + + ## 生成 insert 脚本 + script = "" + last_id = 0 + if inserts: + inserts_lines = "\n".join(inserts) + script += f"""\n\n-- ---------------------------- +-- Records of {table_name.lower()} +-- ---------------------------- +-- @formatter:off +{inserts_lines} +COMMIT; +-- @formatter:on""" + match = re.search(r"VALUES \((\d+),", inserts[-1]) + if match: + last_id = int(match.group(1)) + + # 生成 Sequence + script += f""" + +CREATE SEQUENCE {table_name}_seq + START WITH {last_id + 1};""" + + return script + + +class SQLServerConvertor(Convertor): + """_summary_ + + Args: + Convertor (_type_): _description_ + """ + + def __init__(self, src): + super().__init__(src, "Microsoft SQL Server") + + def translate_type(self, type: str, size: Optional[Union[int, Tuple[int]]]): + """类型转换""" + + type = type.lower() + + if type == "varchar": + return f"nvarchar({size if size < 4000 else 4000})" + if type == "int": + return "int" + if type == "bigint" or type == "bigint unsigned": + return "bigint" + if type == "datetime": + return "datetime2" + if type == "bit": + return "varchar(1)" + if type in ("tinyint", "smallint"): + return "tinyint" + if type == "text": + return "nvarchar(max)" + if type in ("blob", "mediumblob"): + return "varbinary(max)" + if type == "decimal": + return ( + f"numeric({','.join(str(s) for s in size)})" if len(size) else "numeric" + ) + + def gen_create(self, ddl: Dict) -> str: + """生成 create""" + + def _generate_column(col): + name = col["name"].lower() + if name == "id": + return "id bigint NOT NULL PRIMARY KEY IDENTITY" + if name == "deleted": + return "deleted bit DEFAULT 0 NOT NULL" + + type = col["type"].lower() + full_type = self.translate_type(type, col["size"]) + nullable = "NULL" if col["nullable"] else "NOT NULL" + default = f"DEFAULT {col['default']}" if col["default"] is not None else "" + return f"{name} {full_type} {default} {nullable}" + + table_name = ddl["table_name"].lower() + columns = [f"{_generate_column(col).strip()}" for col in ddl["columns"]] + filed_def_list = ",\n ".join(columns) + script = f"""-- ---------------------------- +-- Table structure for {table_name} +-- ---------------------------- +DROP TABLE IF EXISTS {table_name} +GO +CREATE TABLE {table_name} ( + {filed_def_list} +) +GO""" + + return script + + def gen_comment(self, table_sql: str, table_name: str) -> str: + """生成字段及表的注释""" + + script = "" + + for field, comment_string in self.filed_comments(table_sql): + script += f"""EXEC sp_addextendedproperty + 'MS_Description', N'{comment_string}', + 'SCHEMA', N'dbo', + 'TABLE', N'{table_name}', + 'COLUMN', N'{field}' +GO + +""" + + table_comment = self.table_comment(table_sql) + if table_comment: + script += f"""EXEC sp_addextendedproperty + 'MS_Description', N'{table_comment}', + 'SCHEMA', N'dbo', + 'TABLE', N'{table_name}' +GO + +""" + return script + + def gen_pk(self, table_name: str) -> str: + """生成主键定义""" + return "" + + def gen_index(self, ddl: Dict) -> str: + """生成 index""" + return "\n".join(f"{script}\nGO" for script in self.index(ddl)) + + def gen_insert(self, table_name: str) -> str: + """生成 insert 语句""" + + # 收集 `table_name` 对应的 insert 语句 + inserts = [] + for insert_script in Convertor.inserts(table_name, self.content): + # SQLServer: 字符串前加N,hack,是否存在替换字符串内容的风险 + insert_script = insert_script.replace(", '", ", N'").replace( + "VALUES ('", "VALUES (N')" + ) + # 删除 insert 的结尾分号 + insert_script = re.sub(";$", r"\nGO", insert_script) + inserts.append(insert_script) + + ## 生成 insert 脚本 + script = "" + if inserts: + inserts_lines = "\n".join(inserts) + script += f"""\n\n-- ---------------------------- +-- Records of {table_name.lower()} +-- ---------------------------- +-- @formatter:off +BEGIN TRANSACTION +GO +SET IDENTITY_INSERT {table_name.lower()} ON +GO +{inserts_lines} +SET IDENTITY_INSERT {table_name.lower()} OFF +GO +COMMIT +GO +-- @formatter:on""" + + return script + + def gen_dual(self) -> str: + return """DROP TABLE IF EXISTS dual +GO +CREATE TABLE dual +( + id int +) +GO + +EXEC sp_addextendedproperty + 'MS_Description', N'数据库连接的表', + 'SCHEMA', N'dbo', + 'TABLE', N'dual' +GO + +-- ---------------------------- +-- Records of dual +-- ---------------------------- +-- @formatter:off +INSERT INTO dual VALUES (1) +GO +-- @formatter:on""" + + +class DM8Convertor(Convertor): + def __init__(self, src): + super().__init__(src, "DM8") + + def translate_type(self, type: str, size: Optional[Union[int, Tuple[int]]]): + """类型转换""" + type = type.lower() + + if type == "varchar": + return f"varchar({size})" + if type == "int": + return "int" + if type == "bigint" or type == "bigint unsigned": + return "bigint" + if type == "datetime": + return "datetime" + if type == "bit": + return "bit" + if type in ("tinyint", "smallint"): + return "smallint" + if type == "text": + return "text" + if type == "blob": + return "blob" + if type == "mediumblob": + return "varchar(10240)" + if type == "decimal": + return ( + f"decimal({','.join(str(s) for s in size)})" if len(size) else "decimal" + ) + + def gen_create(self, ddl) -> str: + """生成 CREATE 语句""" + + def generate_column(col): + name = col["name"].lower() + if name == "id": + return "id bigint NOT NULL PRIMARY KEY IDENTITY" + + type = col["type"].lower() + full_type = self.translate_type(type, col["size"]) + nullable = "NULL" if col["nullable"] else "NOT NULL" + default = f"DEFAULT {col['default']}" if col["default"] is not None else "" + return f"{name} {full_type} {default} {nullable}" + + table_name = ddl["table_name"].lower() + columns = [f"{generate_column(col).strip()}" for col in ddl["columns"]] + field_def_list = ",\n ".join(columns) + script = f"""-- ---------------------------- +-- Table structure for {table_name} +-- ---------------------------- +CREATE TABLE {table_name} ( + {field_def_list} +);""" + + # oracle INSERT '' 不能通过 NOT NULL 校验 + script = script.replace("DEFAULT '' NOT NULL", "DEFAULT '' NULL") + + return script + + def gen_index(self, ddl: Dict) -> str: + return "\n".join(f"{script};" for script in self.index(ddl)) + + def gen_comment(self, table_sql: str, table_name: str) -> str: + script = "" + for field, comment_string in self.filed_comments(table_sql): + script += ( + f"COMMENT ON COLUMN {table_name}.{field} IS '{comment_string}';" + "\n" + ) + + table_comment = self.table_comment(table_sql) + if table_comment: + script += f"COMMENT ON TABLE {table_name} IS '{table_comment}';\n" + + return script + + def gen_pk(self, table_name: str) -> str: + """生成主键定义""" + return "" + + def gen_index(self, ddl: Dict) -> str: + return "\n".join(f"{script};" for script in self.index(ddl)) + + def gen_insert(self, table_name: str) -> str: + """拷贝 INSERT 语句""" + inserts = list(Convertor.inserts(table_name, self.content)) + + ## 生成 insert 脚本 + script = "" + if inserts: + inserts_lines = "\n".join(inserts) + script += f"""\n\n-- ---------------------------- +-- Records of {table_name.lower()} +-- ---------------------------- +-- @formatter:off +SET IDENTITY_INSERT {table_name.lower()} ON; +{inserts_lines} +COMMIT; +SET IDENTITY_INSERT {table_name.lower()} OFF; +-- @formatter:on""" + + return script + + +class KingbaseConvertor(PostgreSQLConvertor): + def __init__(self, src): + super().__init__(src) + self.db_type = "Kingbase" + + def gen_create(self, ddl: Dict) -> str: + """生成 create""" + + def _generate_column(col): + name = col["name"].lower() + if name == "deleted": + return "deleted int2 NOT NULL DEFAULT 0" + + type = col["type"].lower() + full_type = self.translate_type(type, col["size"]) + nullable = "NULL" if col["nullable"] else "NOT NULL" + default = f"DEFAULT {col['default']}" if col["default"] is not None else "" + return f"{name} {full_type} {nullable} {default}" + + table_name = ddl["table_name"].lower() + columns = [f"{_generate_column(col).strip()}" for col in ddl["columns"]] + filed_def_list = ",\n ".join(columns) + script = f"""-- ---------------------------- +-- Table structure for {table_name} +-- ---------------------------- +DROP TABLE IF EXISTS {table_name}; +CREATE TABLE {table_name} ( + {filed_def_list} +);""" + + # Kingbase INSERT '' 不能通过 NOT NULL 校验 + script = script.replace("NOT NULL DEFAULT ''", "NULL DEFAULT ''") + + return script + + +class OpengaussConvertor(KingbaseConvertor): + def __init__(self, src): + super().__init__(src) + self.db_type = "OpenGauss" + + +def main(): + parser = argparse.ArgumentParser(description="芋道系统数据库转换工具") + parser.add_argument( + "type", + type=str, + help="目标数据库类型", + choices=["postgres", "oracle", "sqlserver", "dm8", "kingbase", "opengauss"], + ) + args = parser.parse_args() + + sql_file = pathlib.Path("../mysql/ruoyi-vue-pro.sql").resolve().as_posix() + convertor = None + if args.type == "postgres": + convertor = PostgreSQLConvertor(sql_file) + elif args.type == "oracle": + convertor = OracleConvertor(sql_file) + elif args.type == "sqlserver": + convertor = SQLServerConvertor(sql_file) + elif args.type == "dm8": + convertor = DM8Convertor(sql_file) + elif args.type == "kingbase": + convertor = KingbaseConvertor(sql_file) + elif args.type == "opengauss": + convertor = OpengaussConvertor(sql_file) + else: + raise NotImplementedError(f"不支持目标数据库类型: {args.type}") + + convertor.print() + + +if __name__ == "__main__": + main() diff --git a/sql/tools/docker-compose.yaml b/sql/tools/docker-compose.yaml new file mode 100644 index 0000000..0fa9513 --- /dev/null +++ b/sql/tools/docker-compose.yaml @@ -0,0 +1,134 @@ +name: ruoyi-vue-pro + +volumes: + mysql: { } + postgres: { } + sqlserver: { } + dm8: { } + kingbase: { } + opengauss: { } + +services: + mysql: + image: mysql:8.0.33 + restart: unless-stopped + environment: + TZ: Asia/Shanghai + MYSQL_ROOT_PASSWORD: 123456 + MYSQL_DATABASE: ruoyi-vue-pro + ports: + - "3306:3306" + volumes: + - mysql:/var/lib/mysql/ + # 注入初始化脚本 + - ./mysql/ruoyi-vue-pro.sql:/docker-entrypoint-initdb.d/init.sql:ro + command: + --default-authentication-plugin=mysql_native_password + --character-set-server=utf8mb4 + --collation-server=utf8mb4_general_ci + --explicit_defaults_for_timestamp=true + --lower_case_table_names=1 + + postgres: + image: postgres:14.2 + restart: unless-stopped + environment: + POSTGRES_USER: root + POSTGRES_PASSWORD: 123456 + POSTGRES_DB: ruoyi-vue-pro + ports: + - "5432:5432" + volumes: + - postgres:/var/lib/postgresql/data + # 注入初始化脚本 + - ../postgresql/quartz.sql:/docker-entrypoint-initdb.d/quartz.sql:ro + - ../postgresql/ruoyi-vue-pro.sql:/docker-entrypoint-initdb.d/ruoyi-vue-pro.sql:ro + + oracle: + image: gvenzl/oracle-xe:18-slim-faststart + restart: unless-stopped + environment: + ## 登录信息 SID: XE user: system password: oracle + ORACLE_PASSWORD: oracle + ports: + - "1521:1521" + volumes: + - ../oracle/ruoyi-vue-pro.sql:/tmp/schema.sql:ro + # 创建app用户: ROOT/123456@//localhost/XEPDB1 + - ./oracle/1_create_user.sql:/docker-entrypoint-initdb.d/1_create_user.sql:ro + - ./oracle/2_create_schema.sh:/docker-entrypoint-initdb.d/2_create_schema.sh:ro + + oracle_m1: + image: einslib/oracle-19c:19.3.0-ee-slim-faststart + restart: unless-stopped + environment: + ## 登录信息 SID: FREE user: system password: oracle + ORACLE_PASSWORD: oracle + ports: + - "1521:1521" + volumes: + - ../oracle/ruoyi-vue-pro.sql:/tmp/schema.sql:ro + # 创建app用户: ROOT/123456@//localhost/XEPDB1 + - ./oracle/1_create_user.sql:/docker-entrypoint-initdb.d/1_create_user.sql:ro + - ./oracle/2_create_schema.sh:/docker-entrypoint-initdb.d/2_create_schema.sh:ro + + sqlserver: + image: mcr.microsoft.com/mssql/server:2017-latest + restart: unless-stopped + environment: + TZ: Asia/Shanghai + ACCEPT_EULA: "Y" + SA_PASSWORD: "Yudao@2024" + ports: + - "1433:1433" + volumes: + - sqlserver:/var/opt/mssql + - ../sqlserver/ruoyi-vue-pro.sql:/tmp/schema.sql:ro + # docker compose exec sqlserver bash /tmp/create_schema.sh + - ./sqlserver/create_schema.sh:/tmp/create_schema.sh:ro + + dm8: + # docker load -i dm8_20240715_x86_rh6_rq_single.tar + image: dm8_single:dm8_20240715_rev232765_x86_rh6_64 + restart: unless-stopped + environment: + PAGE_SIZE: 16 + LD_LIBRARY_PATH: /opt/dmdbms/bin + EXTENT_SIZE: 32 + BLANK_PAD_MODE: 1 + LOG_SIZE: 1024 + UNICODE_FLAG: 1 + LENGTH_IN_CHAR: 1 + INSTANCE_NAME: dm8_test + ports: + - "5236:5236" + volumes: + - dm8:/opt/dmdbms/data + - ../dm/ruoyi-vue-pro-dm8.sql:/tmp/schema.sql:ro + + kingbase: + image: kingbase_v009r001c001b0025_single_x86:v1 +# image: kingbase_v009r001c001b0025_single_arm:v1 + restart: unless-stopped + environment: + DB_USER: root + DB_PASSWORD: 123456 + ports: + - "54321:54321" + volumes: + - kingbase:/home/kingbase/userdata + - ../kingbase/ruoyi-vue-pro.sql:/tmp/schema.sql:ro + + opengauss: + image: opengauss/opengauss:5.0.0 + restart: unless-stopped + environment: + GS_USERNAME: root + GS_PASSWORD: Yudao@2024 + LD_LIBRARY_PATH: /usr/local/opengauss/lib:/usr/lib + ports: + - "5432:5432" + volumes: + - opengauss:/var/lib/opengauss + - ../opengauss/ruoyi-vue-pro.sql:/tmp/schema.sql:ro + # docker compose exec opengauss bash -c '/usr/local/opengauss/bin/gsql -U $GS_USERNAME -W $GS_PASSWORD -d postgres -f /tmp/schema.sql' \ No newline at end of file diff --git a/sql/tools/oracle/1_create_user.sql b/sql/tools/oracle/1_create_user.sql new file mode 100644 index 0000000..58c9658 --- /dev/null +++ b/sql/tools/oracle/1_create_user.sql @@ -0,0 +1,3 @@ +ALTER SESSION SET CONTAINER=XEPDB1; +CREATE USER ROOT IDENTIFIED BY 123456 QUOTA UNLIMITED ON USERS; +GRANT CONNECT, RESOURCE TO ROOT; diff --git a/sql/tools/oracle/2_create_schema.sh b/sql/tools/oracle/2_create_schema.sh new file mode 100644 index 0000000..ce7955d --- /dev/null +++ b/sql/tools/oracle/2_create_schema.sh @@ -0,0 +1 @@ +sqlplus -s ROOT/123456@//localhost/XEPDB1 @/tmp/schema.sql diff --git a/sql/tools/sqlserver/create_schema.sh b/sql/tools/sqlserver/create_schema.sh new file mode 100644 index 0000000..172650b --- /dev/null +++ b/sql/tools/sqlserver/create_schema.sh @@ -0,0 +1,5 @@ +#!/usr/bin/env bash + +/opt/mssql-tools/bin/sqlcmd -S localhost -U sa -P ${SA_PASSWORD} -Q "CREATE DATABASE [ruoyi-vue-pro]; +GO" +/opt/mssql-tools/bin/sqlcmd -S localhost -U sa -P ${SA_PASSWORD} -d 'ruoyi-vue-pro' -i /tmp/schema.sql diff --git a/tashow-dependencies/pom.xml b/tashow-dependencies/pom.xml new file mode 100644 index 0000000..c427091 --- /dev/null +++ b/tashow-dependencies/pom.xml @@ -0,0 +1,663 @@ + + + 4.0.0 + + com.tashow.cloud + tashow-dependencies + ${revision} + pom + + ${project.artifactId} + 基础 bom 文件,管理整个项目的依赖版本 + + + 1.0.0 + 1.6.0 + + 3.4.1 + 2024.0.0 + 2023.0.3.2 + + 1.2.24 + 3.5.17 + 3.5.9 + 4.3.1 + 1.4.13 + 3.0.6 + 3.41.0 + 8.1.3.140 + 8.6.0 + 5.1.0 + + 2.3.1 + + + + 2.4.0 + + 2.2.7 + + 9.0.0 + 3.4.1 + 0.33.0 + + 8.0.2.RELEASE + 1.1.4 + 5.2.0 + + 7.0.1 + + 2.0.3 + 1.18.1 + 1.18.36 + 1.6.3 + 5.8.35 + 6.0.0-M19 + 4.0.3 + 2.4.1 + 1.2.83 + 33.4.0-jre + 2.14.5 + 3.11.1 + 0.1.55 + 2.9.2 + 2.7.0 + 3.0.6 + 0.10.2 + 4.1.116.Final + + 2.17.0 + 1.27.1 + 1.12.777 + 2.0.5 + 1.8.1 + 4.6.0 + + + + + + + io.netty + netty-bom + ${netty.version} + pom + import + + + org.springframework.boot + spring-boot-dependencies + ${spring.boot.version} + pom + import + + + org.springframework.cloud + spring-cloud-dependencies + ${spring.cloud.version} + pom + import + + + com.alibaba.cloud + spring-cloud-alibaba-dependencies + ${spring.cloud.alibaba.version} + pom + import + + + + + io.github.mouzt + bizlog-sdk + ${bizlog-sdk.version} + + + org.springframework.boot + spring-boot-starter + + + + + com.tashow.cloud + tashow-system-api + ${revision} + + + com.tashow.cloud + tashow-module-system + ${revision} + + + com.tashow.cloud + tashow-infra-api + ${revision} + + + com.tashow.cloud + tashow-module-infra + ${revision} + + + com.tashow.cloud + tashow-framework-tenant + ${revision} + + + com.tashow.cloud + tashow-data-permission + ${revision} + + + + + + org.springframework.boot + spring-boot-configuration-processor + ${spring.boot.version} + + + + com.tashow.cloud + tashow-framework-env + ${revision} + + + + + com.tashow.cloud + tashow-framework-web + ${revision} + + + + com.tashow.cloud + tashow-framework-security + ${revision} + + + + com.tashow.cloud + tashow-framework-websocket + ${revision} + + + + + com.tashow.cloud + tashow-data-mybatis + ${revision} + + + + com.alibaba + druid-spring-boot-3-starter + ${druid.version} + + + org.mybatis + mybatis + ${mybatis.version} + + + com.baomidou + mybatis-plus-spring-boot3-starter + ${mybatis-plus.version} + + + com.baomidou + mybatis-plus-jsqlparser + ${mybatis-plus.version} + + + com.baomidou + mybatis-plus-generator + ${mybatis-plus.version} + + + com.baomidou + dynamic-datasource-spring-boot3-starter + ${dynamic-datasource.version} + + + com.github.yulichang + mybatis-plus-join-boot-starter + ${mybatis-plus-join.version} + + + + com.fhs-opensource + easy-trans-spring-boot-starter + ${easy-trans.version} + + + org.springframework + spring-context + + + org.springframework.cloud + spring-cloud-commons + + + + + com.fhs-opensource + easy-trans-mybatis-plus-extend + ${easy-trans.version} + + + com.fhs-opensource + easy-trans-anno + ${easy-trans.version} + + + + com.tashow.cloud + tashow-data-redis + ${revision} + + + + org.redisson + redisson-spring-boot-starter + ${redisson.version} + + + + com.dameng + DmJdbcDriver18 + ${dm8.jdbc.version} + + + + org.opengauss + opengauss-jdbc + ${opengauss.jdbc.version} + + + + cn.com.kingbase + kingbase8 + ${kingbase.jdbc.version} + + + + + com.tashow.cloud + tashow-framework-rpc + ${revision} + + + + + + + + + com.xuxueli + xxl-job-core + ${xxl-job.version} + + + com.tashow.cloud + tashow-framework-job + ${revision} + + + + + com.tashow.cloud + tashow-framework-mq + ${revision} + + + + org.apache.rocketmq + rocketmq-spring-boot-starter + ${rocketmq-spring.version} + + + + + com.tashow.cloud + tashow-framework-protection + ${revision} + + + + com.baomidou + lock4j-redisson-spring-boot-starter + ${lock4j.version} + + + redisson-spring-boot-starter + org.redisson + + + + + + + com.tashow.cloud + tashow-framework-monitor + ${revision} + + + + org.apache.skywalking + apm-toolkit-trace + ${skywalking.version} + + + org.apache.skywalking + apm-toolkit-logback-1.x + ${skywalking.version} + + + org.apache.skywalking + apm-toolkit-opentracing + ${skywalking.version} + + + + + + + + + + + + + io.opentracing + opentracing-api + ${opentracing.version} + + + io.opentracing + opentracing-util + ${opentracing.version} + + + io.opentracing + opentracing-noop + ${opentracing.version} + + + + de.codecentric + spring-boot-admin-starter-server + ${spring-boot-admin.version} + + + de.codecentric + spring-boot-admin-starter-client + ${spring-boot-admin.version} + + + + + org.mockito + mockito-inline + ${mockito-inline.version} + + + + org.springframework.boot + spring-boot-starter-test + ${spring.boot.version} + + + asm + org.ow2.asm + + + org.mockito + mockito-core + + + + + + com.github.fppt + jedis-mock + ${jedis-mock.version} + + + + uk.co.jemos.podam + podam + ${podam.version} + + + + + org.flowable + flowable-spring-boot-starter-process + ${flowable.version} + + + org.flowable + flowable-spring-boot-starter-actuator + ${flowable.version} + + + + + + com.tashow.cloud + tashow-common + ${revision} + + + + com.tashow.cloud + tashow-data-excel + ${revision} + + + + org.projectlombok + lombok + ${lombok.version} + + + + org.mapstruct + mapstruct + ${mapstruct.version} + + + org.mapstruct + mapstruct-jdk8 + ${mapstruct.version} + + + org.mapstruct + mapstruct-processor + ${mapstruct.version} + + + + cn.hutool + hutool-all + ${hutool-5.version} + + + org.dromara.hutool + hutool-extra + ${hutool-6.version} + + + + com.alibaba + easyexcel + ${easyexcel.verion} + + + + commons-io + commons-io + ${commons-io.version} + + + org.apache.commons + commons-compress + ${commons-compress.version} + + + + org.apache.tika + tika-core + ${tika-core.version} + + + + org.apache.velocity + velocity-engine-core + ${velocity.version} + + + + com.alibaba + fastjson + ${fastjson.version} + + + + com.google.guava + guava + ${guava.version} + + + + com.google.inject + guice + ${guice.version} + + + + com.alibaba + transmittable-thread-local + ${transmittable-thread-local.version} + + + + commons-net + commons-net + ${commons-net.version} + + + com.jcraft + jsch + ${jsch.version} + + + + com.xingyuv + spring-boot-starter-captcha-plus + ${captcha-plus.version} + + + + org.lionsoul + ip2region + ${ip2region.version} + + + + org.jsoup + jsoup + ${jsoup.version} + + + + org.reflections + reflections + ${reflections.version} + + + + + com.amazonaws + aws-java-sdk-s3 + ${aws-java-sdk-s3.version} + + + + com.github.binarywang + weixin-java-pay + ${weixin-java.version} + + + com.github.binarywang + wx-java-mp-spring-boot-starter + ${weixin-java.version} + + + com.github.binarywang + wx-java-miniapp-spring-boot-starter + ${weixin-java.version} + + + + com.xingyuv + spring-boot-starter-justauth + ${justauth.version} + + + cn.hutool + hutool-core + + + + + + + org.jeecgframework.jimureport + jimureport-spring-boot3-starter-fastjson2 + ${jimureport.version} + + + com.alibaba + druid + + + + + + + + + + + org.codehaus.mojo + flatten-maven-plugin + ${flatten-maven-plugin.version} + + bom + true + + + + + flatten + + flatten + process-resources + + + + clean + + flatten.clean + clean + + + + + + + diff --git a/tashow-feign/pom.xml b/tashow-feign/pom.xml new file mode 100644 index 0000000..762c0de --- /dev/null +++ b/tashow-feign/pom.xml @@ -0,0 +1,19 @@ + + 4.0.0 + + com.tashow.cloud + tashow-platform + ${revision} + + + tashow-feign + pom + + + tashow-infra-api + tashow-system-api + tashow-product-api + + + diff --git a/tashow-feign/tashow-infra-api/pom.xml b/tashow-feign/tashow-infra-api/pom.xml new file mode 100644 index 0000000..a0b2a95 --- /dev/null +++ b/tashow-feign/tashow-infra-api/pom.xml @@ -0,0 +1,40 @@ + + + 4.0.0 + + com.tashow.cloud + tashow-feign + ${revision} + + tashow-infra-api + jar + + ${project.artifactId} + + infra 模块 API,暴露给其它模块调用 + + + + + com.tashow.cloud + tashow-common + + + + + org.springframework.boot + spring-boot-starter-validation + true + + + + + org.springframework.cloud + spring-cloud-starter-openfeign + true + + + + diff --git a/tashow-feign/tashow-infra-api/src/main/java/com/tashow/cloud/infraapi/api/config/ConfigApi.java b/tashow-feign/tashow-infra-api/src/main/java/com/tashow/cloud/infraapi/api/config/ConfigApi.java new file mode 100644 index 0000000..88b7849 --- /dev/null +++ b/tashow-feign/tashow-infra-api/src/main/java/com/tashow/cloud/infraapi/api/config/ConfigApi.java @@ -0,0 +1,18 @@ +package com.tashow.cloud.infraapi.api.config; + +import com.tashow.cloud.common.pojo.CommonResult; +import com.tashow.cloud.infraapi.enums.ApiConstants; +import org.springframework.cloud.openfeign.FeignClient; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestParam; + +@FeignClient(name = ApiConstants.NAME) // TODO 芋艿:fallbackFactory = +/** RPC 服务 - 参数配置 */ +public interface ConfigApi { + + String PREFIX = ApiConstants.PREFIX + "/config"; + + /** 根据参数键查询参数值 */ + @GetMapping(PREFIX + "/get-value-by-key") + CommonResult getConfigValueByKey(@RequestParam("key") String key); +} diff --git a/tashow-feign/tashow-infra-api/src/main/java/com/tashow/cloud/infraapi/api/file/FileApi.java b/tashow-feign/tashow-infra-api/src/main/java/com/tashow/cloud/infraapi/api/file/FileApi.java new file mode 100644 index 0000000..2ecf928 --- /dev/null +++ b/tashow-feign/tashow-infra-api/src/main/java/com/tashow/cloud/infraapi/api/file/FileApi.java @@ -0,0 +1,58 @@ +package com.tashow.cloud.infraapi.api.file; + +import com.tashow.cloud.common.pojo.CommonResult; +import com.tashow.cloud.infraapi.api.file.dto.FileCreateReqDTO; +import com.tashow.cloud.infraapi.enums.ApiConstants; +import jakarta.validation.Valid; +import org.springframework.cloud.openfeign.FeignClient; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestParam; + +@FeignClient(name = ApiConstants.NAME) // TODO 芋艿:fallbackFactory = +/** RPC 服务 - 文件 */ +public interface FileApi { + + String PREFIX = ApiConstants.PREFIX + "/file"; + + /** + * 保存文件,并返回文件的访问路径 + * + * @param content 文件内容 + * @return 文件路径 + */ + default String createFile(byte[] content) { + return createFile(null, null, content); + } + + /** + * 保存文件,并返回文件的访问路径 + * + * @param path 文件路径 + * @param content 文件内容 + * @return 文件路径 + */ + default String createFile(String path, byte[] content) { + return createFile(null, path, content); + } + + /** + * 保存文件,并返回文件的访问路径 + * + * @param name 原文件名称 + * @param path 文件路径 + * @param content 文件内容 + * @return 文件路径 + */ + default String createFile( + @RequestParam("name") String name, + @RequestParam("path") String path, + @RequestParam("content") byte[] content) { + return createFile(new FileCreateReqDTO().setName(name).setPath(path).setContent(content)) + .getCheckedData(); + } + + @PostMapping(PREFIX + "/create") + /** 保存文件,并返回文件的访问路径 */ + CommonResult createFile(@Valid @RequestBody FileCreateReqDTO createReqDTO); +} diff --git a/tashow-feign/tashow-infra-api/src/main/java/com/tashow/cloud/infraapi/api/file/dto/FileCreateReqDTO.java b/tashow-feign/tashow-infra-api/src/main/java/com/tashow/cloud/infraapi/api/file/dto/FileCreateReqDTO.java new file mode 100644 index 0000000..a580df8 --- /dev/null +++ b/tashow-feign/tashow-infra-api/src/main/java/com/tashow/cloud/infraapi/api/file/dto/FileCreateReqDTO.java @@ -0,0 +1,21 @@ +package com.tashow.cloud.infraapi.api.file.dto; + +import jakarta.validation.constraints.NotEmpty; +import lombok.Data; + +/** RPC 服务 - 文件创建 Request DTO */ +@Data +public class FileCreateReqDTO { + + /** 原文件名称 */ + private String name; + + /** 文件路径 */ + private String path; + + /** + * 文件内容 + */ + @NotEmpty(message = "文件内容不能为空") + private byte[] content; +} diff --git a/tashow-feign/tashow-infra-api/src/main/java/com/tashow/cloud/infraapi/api/logger/ApiAccessLogApi.java b/tashow-feign/tashow-infra-api/src/main/java/com/tashow/cloud/infraapi/api/logger/ApiAccessLogApi.java new file mode 100644 index 0000000..e46bce5 --- /dev/null +++ b/tashow-feign/tashow-infra-api/src/main/java/com/tashow/cloud/infraapi/api/logger/ApiAccessLogApi.java @@ -0,0 +1,31 @@ +package com.tashow.cloud.infraapi.api.logger; + +import com.tashow.cloud.common.pojo.CommonResult; +import com.tashow.cloud.infraapi.api.logger.dto.ApiAccessLogCreateReqDTO; +import com.tashow.cloud.infraapi.enums.ApiConstants; +import jakarta.validation.Valid; +import org.springframework.cloud.openfeign.FeignClient; +import org.springframework.scheduling.annotation.Async; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; + +@FeignClient(name = ApiConstants.NAME) // TODO 芋艿:fallbackFactory = +/** RPC 服务 - API 访问日志 */ +public interface ApiAccessLogApi { + + String PREFIX = ApiConstants.PREFIX + "/api-access-log"; + + @PostMapping(PREFIX + "/create") + /** 创建 API 访问日志 */ + CommonResult createApiAccessLog(@Valid @RequestBody ApiAccessLogCreateReqDTO createDTO); + + /** + * 【异步】创建 API 访问日志 + * + * @param createDTO 访问日志 DTO + */ + @Async + default void createApiAccessLogAsync(ApiAccessLogCreateReqDTO createDTO) { + createApiAccessLog(createDTO).checkError(); + } +} diff --git a/tashow-feign/tashow-infra-api/src/main/java/com/tashow/cloud/infraapi/api/logger/ApiErrorLogApi.java b/tashow-feign/tashow-infra-api/src/main/java/com/tashow/cloud/infraapi/api/logger/ApiErrorLogApi.java new file mode 100644 index 0000000..af893ed --- /dev/null +++ b/tashow-feign/tashow-infra-api/src/main/java/com/tashow/cloud/infraapi/api/logger/ApiErrorLogApi.java @@ -0,0 +1,31 @@ +package com.tashow.cloud.infraapi.api.logger; + +import com.tashow.cloud.common.pojo.CommonResult; +import com.tashow.cloud.infraapi.api.logger.dto.ApiErrorLogCreateReqDTO; +import com.tashow.cloud.infraapi.enums.ApiConstants; +import jakarta.validation.Valid; +import org.springframework.cloud.openfeign.FeignClient; +import org.springframework.scheduling.annotation.Async; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; + +@FeignClient(name = ApiConstants.NAME) // TODO 芋艿:fallbackFactory = +/** RPC 服务 - API 异常日志 */ +public interface ApiErrorLogApi { + + String PREFIX = ApiConstants.PREFIX + "/api-error-log"; + + @PostMapping(PREFIX + "/create") + /** 创建 API 异常日志 */ + CommonResult createApiErrorLog(@Valid @RequestBody ApiErrorLogCreateReqDTO createDTO); + + /** + * 【异步】创建 API 异常日志 + * + * @param createDTO 异常日志 DTO + */ + @Async + default void createApiErrorLogAsync(ApiErrorLogCreateReqDTO createDTO) { + createApiErrorLog(createDTO).checkError(); + } +} diff --git a/tashow-feign/tashow-infra-api/src/main/java/com/tashow/cloud/infraapi/api/logger/dto/ApiAccessLogCreateReqDTO.java b/tashow-feign/tashow-infra-api/src/main/java/com/tashow/cloud/infraapi/api/logger/dto/ApiAccessLogCreateReqDTO.java new file mode 100644 index 0000000..0657d80 --- /dev/null +++ b/tashow-feign/tashow-infra-api/src/main/java/com/tashow/cloud/infraapi/api/logger/dto/ApiAccessLogCreateReqDTO.java @@ -0,0 +1,81 @@ +package com.tashow.cloud.infraapi.api.logger.dto; + +import jakarta.validation.constraints.NotNull; +import java.time.LocalDateTime; +import lombok.Data; + +/** RPC 服务 - API 访问日志创建 Request DTO */ +@Data +public class ApiAccessLogCreateReqDTO { + + /** 链路追踪编号 */ + private String traceId; + + /** 用户编号" */ + private Long userId; + + /** 用户类型" */ + private Integer userType; + + /** 应用名" */ + @NotNull(message = "应用名不能为空") + private String applicationName; + + /** 请求方法名" */ + @NotNull(message = "http 请求方法不能为空") + private String requestMethod; + + /** 请求地址", example = "/xxx/yyy */ + @NotNull(message = "访问地址不能为空") + private String requestUrl; + + /** 请求参数 */ + private String requestParams; + + /** 响应结果 */ + private String responseBody; + + /** 用户 IP" */ + @NotNull(message = "ip 不能为空") + private String userIp; + + /** 浏览器 UserAgent" */ + @NotNull(message = "User-Agent 不能为空") + private String userAgent; + + /** 操作模块", example = "商品模块 */ + private String operateModule; + + /** 操作名", example = "商品新增 */ + private String operateName; + + /** 操作分类" */ + private Integer operateType; // 参见 OperateTypeEnum 枚举 + + /** + * 开始时间 + */ + @NotNull(message = "开始请求时间不能为空") + private LocalDateTime beginTime; + + /** + * 结束时间 + */ + @NotNull(message = "结束请求时间不能为空") + private LocalDateTime endTime; + + /** + * 执行时长,单位:毫秒 + */ + @NotNull(message = "执行时长不能为空") + private Integer duration; + + /** + * 结果码 + */ + @NotNull(message = "错误码不能为空") + private Integer resultCode; + + /** 结果提示 */ + private String resultMsg; +} diff --git a/tashow-feign/tashow-infra-api/src/main/java/com/tashow/cloud/infraapi/api/logger/dto/ApiErrorLogCreateReqDTO.java b/tashow-feign/tashow-infra-api/src/main/java/com/tashow/cloud/infraapi/api/logger/dto/ApiErrorLogCreateReqDTO.java new file mode 100644 index 0000000..173821b --- /dev/null +++ b/tashow-feign/tashow-infra-api/src/main/java/com/tashow/cloud/infraapi/api/logger/dto/ApiErrorLogCreateReqDTO.java @@ -0,0 +1,99 @@ +package com.tashow.cloud.infraapi.api.logger.dto; + +import jakarta.validation.constraints.NotNull; +import java.time.LocalDateTime; +import lombok.Data; + +/** RPC 服务 - API 错误日志创建 Request DTO */ +@Data +public class ApiErrorLogCreateReqDTO { + + /** 链路追踪编号 */ + private String traceId; + + /** 用户编号" */ + private Long userId; + + /** 用户类型" */ + private Integer userType; + + /** 应用名" */ + @NotNull(message = "应用名不能为空") + private String applicationName; + + /** 请求方法名" */ + @NotNull(message = "http 请求方法不能为空") + private String requestMethod; + + /** 请求地址", example = "/xxx/yyy */ + @NotNull(message = "访问地址不能为空") + private String requestUrl; + + /** + * 请求参数 + */ + @NotNull(message = "请求参数不能为空") + private String requestParams; + + /** 用户 IP" */ + @NotNull(message = "ip 不能为空") + private String userIp; + + /** 浏览器 UserAgent" */ + @NotNull(message = "User-Agent 不能为空") + private String userAgent; + + /** + * 异常时间 + */ + @NotNull(message = "异常时间不能为空") + private LocalDateTime exceptionTime; + + /** + * 异常名 + */ + @NotNull(message = "异常名不能为空") + private String exceptionName; + + /** + * 异常发生的类全名 + */ + @NotNull(message = "异常发生的类全名不能为空") + private String exceptionClassName; + + /** + * 异常发生的类文件 + */ + @NotNull(message = "异常发生的类文件不能为空") + private String exceptionFileName; + + /** + * 异常发生的方法名 + */ + @NotNull(message = "异常发生的方法名不能为空") + private String exceptionMethodName; + + /** + * 异常发生的方法所在行 + */ + @NotNull(message = "异常发生的方法所在行不能为空") + private Integer exceptionLineNumber; + + /** + * 异常的栈轨迹异常的栈轨迹 + */ + @NotNull(message = "异常的栈轨迹不能为空") + private String exceptionStackTrace; + + /** + * 异常导致的根消息 + */ + @NotNull(message = "异常导致的根消息不能为空") + private String exceptionRootCauseMessage; + + /** + * 异常导致的消息 + */ + @NotNull(message = "异常导致的消息不能为空") + private String exceptionMessage; +} diff --git a/tashow-feign/tashow-infra-api/src/main/java/com/tashow/cloud/infraapi/api/package-info.java b/tashow-feign/tashow-infra-api/src/main/java/com/tashow/cloud/infraapi/api/package-info.java new file mode 100644 index 0000000..061202d --- /dev/null +++ b/tashow-feign/tashow-infra-api/src/main/java/com/tashow/cloud/infraapi/api/package-info.java @@ -0,0 +1,4 @@ +/** + * infra API 包,定义暴露给其它模块的 API + */ +package com.tashow.cloud.infraapi.api; diff --git a/tashow-feign/tashow-infra-api/src/main/java/com/tashow/cloud/infraapi/api/websocket/WebSocketSenderApi.java b/tashow-feign/tashow-infra-api/src/main/java/com/tashow/cloud/infraapi/api/websocket/WebSocketSenderApi.java new file mode 100644 index 0000000..64678cb --- /dev/null +++ b/tashow-feign/tashow-infra-api/src/main/java/com/tashow/cloud/infraapi/api/websocket/WebSocketSenderApi.java @@ -0,0 +1,82 @@ +package com.tashow.cloud.infraapi.api.websocket; + +import com.tashow.cloud.common.pojo.CommonResult; +import com.tashow.cloud.common.util.json.JsonUtils; +import com.tashow.cloud.infraapi.api.websocket.dto.WebSocketSendReqDTO; +import com.tashow.cloud.infraapi.enums.ApiConstants; +import jakarta.validation.Valid; +import org.springframework.cloud.openfeign.FeignClient; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; + +@FeignClient(name = ApiConstants.NAME) // TODO 芋艿:fallbackFactory = +/** RPC 服务 - WebSocket 发送器的 */ +// 对 WebSocketMessageSender 进行封装,提供给其它模块使用 +public interface WebSocketSenderApi { + + String PREFIX = ApiConstants.PREFIX + "/websocket"; + + @PostMapping(PREFIX + "/send") + /** 发送 WebSocket 消息 */ + CommonResult send(@Valid @RequestBody WebSocketSendReqDTO message); + + /** + * 发送消息给指定用户 + * + * @param userType 用户类型 + * @param userId 用户编号 + * @param messageType 消息类型 + * @param messageContent 消息内容,JSON 格式 + */ + default void send(Integer userType, Long userId, String messageType, String messageContent) { + send(new WebSocketSendReqDTO() + .setUserType(userType) + .setUserId(userId) + .setMessageType(messageType) + .setMessageContent(messageContent)) + .checkError(); + } + + /** + * 发送消息给指定用户类型 + * + * @param userType 用户类型 + * @param messageType 消息类型 + * @param messageContent 消息内容,JSON 格式 + */ + default void send(Integer userType, String messageType, String messageContent) { + send(new WebSocketSendReqDTO() + .setUserType(userType) + .setMessageType(messageType) + .setMessageContent(messageContent)) + .checkError(); + } + + /** + * 发送消息给指定 Session + * + * @param sessionId Session 编号 + * @param messageType 消息类型 + * @param messageContent 消息内容,JSON 格式 + */ + default void send(String sessionId, String messageType, String messageContent) { + send(new WebSocketSendReqDTO() + .setSessionId(sessionId) + .setMessageType(messageType) + .setMessageContent(messageContent)) + .checkError(); + } + + default void sendObject( + Integer userType, Long userId, String messageType, Object messageContent) { + send(userType, userId, messageType, JsonUtils.toJsonString(messageContent)); + } + + default void sendObject(Integer userType, String messageType, Object messageContent) { + send(userType, messageType, JsonUtils.toJsonString(messageContent)); + } + + default void sendObject(String sessionId, String messageType, Object messageContent) { + send(sessionId, messageType, JsonUtils.toJsonString(messageContent)); + } +} diff --git a/tashow-feign/tashow-infra-api/src/main/java/com/tashow/cloud/infraapi/api/websocket/dto/WebSocketSendReqDTO.java b/tashow-feign/tashow-infra-api/src/main/java/com/tashow/cloud/infraapi/api/websocket/dto/WebSocketSendReqDTO.java new file mode 100644 index 0000000..680476f --- /dev/null +++ b/tashow-feign/tashow-infra-api/src/main/java/com/tashow/cloud/infraapi/api/websocket/dto/WebSocketSendReqDTO.java @@ -0,0 +1,26 @@ +package com.tashow.cloud.infraapi.api.websocket.dto; + +import jakarta.validation.constraints.NotEmpty; +import lombok.Data; + +/** RPC 服务 - WebSocket 消息发送 Request DTO */ +@Data +public class WebSocketSendReqDTO { + + /** Session 编号 */ + private String sessionId; + + /** 用户编号 */ + private Long userId; + + /** 用户类型 */ + private Integer userType; + + /** 消息类型 */ + @NotEmpty(message = "消息类型不能为空") + private String messageType; + + /** 消息内容 */ + @NotEmpty(message = "消息内容不能为空") + private String messageContent; +} diff --git a/tashow-feign/tashow-infra-api/src/main/java/com/tashow/cloud/infraapi/enums/ApiConstants.java b/tashow-feign/tashow-infra-api/src/main/java/com/tashow/cloud/infraapi/enums/ApiConstants.java new file mode 100644 index 0000000..4b64329 --- /dev/null +++ b/tashow-feign/tashow-infra-api/src/main/java/com/tashow/cloud/infraapi/enums/ApiConstants.java @@ -0,0 +1,24 @@ +package com.tashow.cloud.infraapi.enums; + + +import com.tashow.cloud.common.enums.RpcConstants; + +/** + * API 相关的枚举 + * + * @author 芋道源码 + */ +public class ApiConstants { + + /** + * 服务名 + * + * 注意,需要保证和 spring.application.name 保持一致 + */ + public static final String NAME = "infra-server"; + + public static final String PREFIX = RpcConstants.RPC_API_PREFIX + "/infra"; + + public static final String VERSION = "1.0.0"; + +} diff --git a/tashow-feign/tashow-infra-api/src/main/java/com/tashow/cloud/infraapi/enums/DictTypeConstants.java b/tashow-feign/tashow-infra-api/src/main/java/com/tashow/cloud/infraapi/enums/DictTypeConstants.java new file mode 100644 index 0000000..7667950 --- /dev/null +++ b/tashow-feign/tashow-infra-api/src/main/java/com/tashow/cloud/infraapi/enums/DictTypeConstants.java @@ -0,0 +1,20 @@ +package com.tashow.cloud.infraapi.enums; + +/** + * Infra 字典类型的枚举类 + * + * @author 芋道源码 + */ +public interface DictTypeConstants { + + String JOB_STATUS = "infra_job_status"; // 定时任务状态的枚举 + String JOB_LOG_STATUS = "infra_job_log_status"; // 定时任务日志状态的枚举 + + String API_ERROR_LOG_PROCESS_STATUS = "infra_api_error_log_process_status"; // API 错误日志的处理状态的枚举 + + String CONFIG_TYPE = "infra_config_type"; // 参数配置类型 + String BOOLEAN_STRING = "infra_boolean_string"; // Boolean 是否类型 + + String OPERATE_TYPE = "infra_operate_type"; // 操作类型 + +} diff --git a/tashow-feign/tashow-infra-api/src/main/java/com/tashow/cloud/infraapi/enums/ErrorCodeConstants.java b/tashow-feign/tashow-infra-api/src/main/java/com/tashow/cloud/infraapi/enums/ErrorCodeConstants.java new file mode 100644 index 0000000..509a2de --- /dev/null +++ b/tashow-feign/tashow-infra-api/src/main/java/com/tashow/cloud/infraapi/enums/ErrorCodeConstants.java @@ -0,0 +1,72 @@ +package com.tashow.cloud.infraapi.enums; + + +import com.tashow.cloud.common.exception.ErrorCode; + +/** + * Infra 错误码枚举类 + * + * infra 系统,使用 1-001-000-000 段 + */ +public interface ErrorCodeConstants { + + // ========== 参数配置 1-001-000-000 ========== + ErrorCode CONFIG_NOT_EXISTS = new ErrorCode(1_001_000_001, "参数配置不存在"); + ErrorCode CONFIG_KEY_DUPLICATE = new ErrorCode(1_001_000_002, "参数配置 key 重复"); + ErrorCode CONFIG_CAN_NOT_DELETE_SYSTEM_TYPE = new ErrorCode(1_001_000_003, "不能删除类型为系统内置的参数配置"); + ErrorCode CONFIG_GET_VALUE_ERROR_IF_VISIBLE = new ErrorCode(1_001_000_004, "获取参数配置失败,原因:不允许获取不可见配置"); + + // ========== 定时任务 1-001-001-000 ========== + ErrorCode JOB_NOT_EXISTS = new ErrorCode(1_001_001_000, "定时任务不存在"); + ErrorCode JOB_HANDLER_EXISTS = new ErrorCode(1_001_001_001, "定时任务的处理器已经存在"); + ErrorCode JOB_CHANGE_STATUS_INVALID = new ErrorCode(1_001_001_002, "只允许修改为开启或者关闭状态"); + ErrorCode JOB_CHANGE_STATUS_EQUALS = new ErrorCode(1_001_001_003, "定时任务已经处于该状态,无需修改"); + ErrorCode JOB_UPDATE_ONLY_NORMAL_STATUS = new ErrorCode(1_001_001_004, "只有开启状态的任务,才可以修改"); + ErrorCode JOB_CRON_EXPRESSION_VALID = new ErrorCode(1_001_001_005, "CRON 表达式不正确"); + ErrorCode JOB_HANDLER_BEAN_NOT_EXISTS = new ErrorCode(1_001_001_006, "定时任务的处理器 Bean 不存在,注意 Bean 默认首字母小写"); + ErrorCode JOB_HANDLER_BEAN_TYPE_ERROR = new ErrorCode(1_001_001_007, "定时任务的处理器 Bean 类型不正确,未实现 JobHandler 接口"); + + // ========== API 错误日志 1-001-002-000 ========== + ErrorCode API_ERROR_LOG_NOT_FOUND = new ErrorCode(1_001_002_000, "API 错误日志不存在"); + ErrorCode API_ERROR_LOG_PROCESSED = new ErrorCode(1_001_002_001, "API 错误日志已处理"); + + // ========= 文件相关 1-001-003-000 ================= + ErrorCode FILE_PATH_EXISTS = new ErrorCode(1_001_003_000, "文件路径已存在"); + ErrorCode FILE_NOT_EXISTS = new ErrorCode(1_001_003_001, "文件不存在"); + ErrorCode FILE_IS_EMPTY = new ErrorCode(1_001_003_002, "文件为空"); + + // ========== 代码生成器 1-001-004-000 ========== + ErrorCode CODEGEN_TABLE_EXISTS = new ErrorCode(1_001_004_002, "表定义已经存在"); + ErrorCode CODEGEN_IMPORT_TABLE_NULL = new ErrorCode(1_001_004_001, "导入的表不存在"); + ErrorCode CODEGEN_IMPORT_COLUMNS_NULL = new ErrorCode(1_001_004_002, "导入的字段不存在"); + ErrorCode CODEGEN_TABLE_NOT_EXISTS = new ErrorCode(1_001_004_004, "表定义不存在"); + ErrorCode CODEGEN_COLUMN_NOT_EXISTS = new ErrorCode(1_001_004_005, "字段义不存在"); + ErrorCode CODEGEN_SYNC_COLUMNS_NULL = new ErrorCode(1_001_004_006, "同步的字段不存在"); + ErrorCode CODEGEN_SYNC_NONE_CHANGE = new ErrorCode(1_001_004_007, "同步失败,不存在改变"); + ErrorCode CODEGEN_TABLE_INFO_TABLE_COMMENT_IS_NULL = new ErrorCode(1_001_004_008, "数据库的表注释未填写"); + ErrorCode CODEGEN_TABLE_INFO_COLUMN_COMMENT_IS_NULL = new ErrorCode(1_001_004_009, "数据库的表字段({})注释未填写"); + ErrorCode CODEGEN_MASTER_TABLE_NOT_EXISTS = new ErrorCode(1_001_004_010, "主表(id={})定义不存在,请检查"); + ErrorCode CODEGEN_SUB_COLUMN_NOT_EXISTS = new ErrorCode(1_001_004_011, "子表的字段(id={})不存在,请检查"); + ErrorCode CODEGEN_MASTER_GENERATION_FAIL_NO_SUB_TABLE = new ErrorCode(1_001_004_012, "主表生成代码失败,原因:它没有子表"); + + // ========== 文件配置 1-001-006-000 ========== + ErrorCode FILE_CONFIG_NOT_EXISTS = new ErrorCode(1_001_006_000, "文件配置不存在"); + ErrorCode FILE_CONFIG_DELETE_FAIL_MASTER = new ErrorCode(1_001_006_001, "该文件配置不允许删除,原因:它是主配置,删除会导致无法上传文件"); + + // ========== 数据源配置 1-001-007-000 ========== + ErrorCode DATA_SOURCE_CONFIG_NOT_EXISTS = new ErrorCode(1_001_007_000, "数据源配置不存在"); + ErrorCode DATA_SOURCE_CONFIG_NOT_OK = new ErrorCode(1_001_007_001, "数据源配置不正确,无法进行连接"); + + // ========== 学生 1-001-201-000 ========== + ErrorCode DEMO01_CONTACT_NOT_EXISTS = new ErrorCode(1_001_201_000, "示例联系人不存在"); + ErrorCode DEMO02_CATEGORY_NOT_EXISTS = new ErrorCode(1_001_201_001, "示例分类不存在"); + ErrorCode DEMO02_CATEGORY_EXITS_CHILDREN = new ErrorCode(1_001_201_002, "存在存在子示例分类,无法删除"); + ErrorCode DEMO02_CATEGORY_PARENT_NOT_EXITS = new ErrorCode(1_001_201_003,"父级示例分类不存在"); + ErrorCode DEMO02_CATEGORY_PARENT_ERROR = new ErrorCode(1_001_201_004, "不能设置自己为父示例分类"); + ErrorCode DEMO02_CATEGORY_NAME_DUPLICATE = new ErrorCode(1_001_201_005, "已经存在该名字的示例分类"); + ErrorCode DEMO02_CATEGORY_PARENT_IS_CHILD = new ErrorCode(1_001_201_006, "不能设置自己的子示例分类为父示例分类"); + ErrorCode DEMO03_STUDENT_NOT_EXISTS = new ErrorCode(1_001_201_007, "学生不存在"); + ErrorCode DEMO03_GRADE_NOT_EXISTS = new ErrorCode(1_001_201_008, "学生班级不存在"); + ErrorCode DEMO03_GRADE_EXISTS = new ErrorCode(1_001_201_009, "学生班级已存在"); + +} diff --git a/tashow-feign/tashow-product-api/pom.xml b/tashow-feign/tashow-product-api/pom.xml new file mode 100644 index 0000000..67eb172 --- /dev/null +++ b/tashow-feign/tashow-product-api/pom.xml @@ -0,0 +1,90 @@ + + + 4.0.0 + + com.tashow.cloud + tashow-feign + ${revision} + + tashow-product-api + jar + + ${project.artifactId} + + infra 模块 API,暴露给其它模块调用 + + + + + com.tashow.cloud + tashow-common + + + + + org.springframework.boot + spring-boot-starter-validation + true + + + + jakarta.validation + jakarta.validation-api + + + + + org.springframework.cloud + spring-cloud-starter-openfeign + true + + + org.mybatis + mybatis + 3.5.13 + + + + io.swagger + swagger-models + 1.6.2 + + + + io.swagger.core.v3 + swagger-core + 2.2.20 + + + + + io.swagger.core.v3 + swagger-models + 2.2.20 + + + + com.alibaba + easyexcel + 4.0.3 + + + org.mybatis + mybatis + 3.5.9 + + + com.baomidou + mybatis-plus-annotation + 3.5.9 + compile + + + com.tashow.cloud + tashow-data-mybatis + + + + diff --git a/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/package-info.java b/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/package-info.java new file mode 100644 index 0000000..608b3bb --- /dev/null +++ b/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/package-info.java @@ -0,0 +1,4 @@ +/** + * infra API 包,定义暴露给其它模块的 API + */ +package com.tashow.cloud.productapi.api; diff --git a/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/CategoryApi.java b/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/CategoryApi.java new file mode 100644 index 0000000..5a8f109 --- /dev/null +++ b/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/CategoryApi.java @@ -0,0 +1,24 @@ +package com.tashow.cloud.productapi.api.product; + +import com.tashow.cloud.productapi.api.product.dto.CategoryDO; +import com.tashow.cloud.productapi.api.product.dto.CategoryDto; +import com.tashow.cloud.productapi.enums.ApiConstants; +import org.springframework.cloud.openfeign.FeignClient; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestParam; + +import java.util.List; + +@FeignClient(name = ApiConstants.NAME) // TODO 芋艿:fallbackFactory = +/** RPC 服务 - 参数配置 */ +public interface CategoryApi { + + String PREFIX = ApiConstants.PREFIX + "/category"; + + /** 根据参数键查询参数值 */ + @GetMapping(PREFIX + "/categoryList") + List categoryList(@RequestParam(value = "grade", required = false) Integer grade, + @RequestParam(value = "categoryId", required = false) Long categoryId, + @RequestParam(value = "categoryName", required = false) String categoryName, + @RequestParam(value = "status", required = false) Integer status); +} diff --git a/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/dto/CategoryDO.java b/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/dto/CategoryDO.java new file mode 100644 index 0000000..346dbe9 --- /dev/null +++ b/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/dto/CategoryDO.java @@ -0,0 +1,83 @@ +package com.tashow.cloud.productapi.api.product.dto; + +import com.tashow.cloud.mybatis.mybatis.core.dataobject.BaseDO; +import com.tashow.cloud.productapi.general.StringListTypeHandler; +import lombok.*; +import com.baomidou.mybatisplus.annotation.KeySequence; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; + +import java.util.List; + +/** + * 产品类目 DO + * + * @author 芋道源码 + */ +@TableName("tz_category") +@KeySequence("tz_category_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class CategoryDO extends BaseDO { + + /** + * 类目ID + */ + @TableId + private Long categoryId; + /** + * 店铺ID + */ + private Long shopId; + /** + * 父节点 + */ + private Long parentId; + + /** + * 父节名称 + */ + private String parentName; + + /** + * 产品类目名称 + */ + private String categoryName; + /** + * 类目图标 + */ + private String icon; + /** + * 类目的显示图片 + */ + private String pic; + /** + * 类目描述 + */ + private String description; + /** + * 标签 + */ + @TableField(typeHandler = StringListTypeHandler.class) + private List tag; + /** + * 排序 + */ + private Integer sort; + /** + * 默认是1,表示正常状态,0为下线状态 + */ + private Integer status; + /** + * 分类层级 1、2、3级 + */ + private Integer grade; + + + +} \ No newline at end of file diff --git a/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/dto/CategoryDto.java b/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/dto/CategoryDto.java new file mode 100644 index 0000000..baac0ce --- /dev/null +++ b/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/dto/CategoryDto.java @@ -0,0 +1,72 @@ +package com.tashow.cloud.productapi.api.product.dto; + +import com.baomidou.mybatisplus.annotation.TableField; +import com.tashow.cloud.productapi.general.StringListTypeHandler; +import lombok.*; + +import java.util.List; + +/** + * 产品类目 DO + * + * @author 芋道源码 + */ +@Data + +public class CategoryDto { + + /** + * 类目ID + */ + private Long categoryId; + /** + * 店铺ID + */ + private Long shopId; + /** + * 父节点 + */ + private Long parentId; + + /** + * 父节名称 + */ + private String parentName; + + /** + * 产品类目名称 + */ + private String categoryName; + /** + * 类目图标 + */ + private String icon; + /** + * 类目的显示图片 + */ + private String pic; + /** + * 类目描述 + */ + private String description; + /** + * 标签 + */ + @TableField(typeHandler = StringListTypeHandler.class) + private List tag; + /** + * 排序 + */ + private Integer sort; + /** + * 默认是1,表示正常状态,0为下线状态 + */ + private Integer status; + /** + * 分类层级 1、2、3级 + */ + private Integer grade; + + + +} \ No newline at end of file diff --git a/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/dto/ProdAdditionalFeeDatesDO.java b/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/dto/ProdAdditionalFeeDatesDO.java new file mode 100644 index 0000000..a66391c --- /dev/null +++ b/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/dto/ProdAdditionalFeeDatesDO.java @@ -0,0 +1,77 @@ +package com.tashow.cloud.productapi.api.product.dto; + +import com.baomidou.mybatisplus.annotation.KeySequence; +import com.tashow.cloud.mybatis.mybatis.core.dataobject.BaseDO; +import com.tashow.cloud.productapi.general.StringListTypeHandler; +import lombok.*; +import com.baomidou.mybatisplus.annotation.KeySequence; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import java.math.BigDecimal; +import java.util.List; + +/** + * 特殊日期附加费用规则 DO + * + * @author 芋道源码 + */ +@TableName("tz_prod_additional_fee_dates") +@KeySequence("tz_prod_additional_fee_dates_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class ProdAdditionalFeeDatesDO extends BaseDO { + + /** + * 特殊日期规则的唯一标识符 + */ + @TableId + private Long id; + /** + * 商品id + */ + private Long prodId; + /** + * 名称 + */ + private String name; + /** + * 日期类型0:'自定义日期范围':1:'指定日期':2:'法定节假日',3:'固定休息日' + */ + private Integer dateType; + + /** + * 类型:1:特殊日期 2:可预约时段黑名单日期 3:紧急相应服务黑名单日期 + */ + private Integer type; + + /** + * 日期 + */ + @TableField(typeHandler = StringListTypeHandler.class) + private List customTimeSlots; +/* *//** + * 指定日期 + *//* + @TableField(typeHandler = StringListTypeHandler.class) + private List specificDates;*/ + /** + * 收费方式0:''固定金额'':1:''基准价上浮 + */ + private Integer chargeMode; + /** + * 价格或上浮百分比 + */ + private BigDecimal price; + /** + * 是否启用该规则是否启用该规则0关1开 + */ + private Integer isEnabled; + public boolean isEmpty() { + return id == null ; + } +} \ No newline at end of file diff --git a/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/dto/ProdAdditionalFeePeriodsDO.java b/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/dto/ProdAdditionalFeePeriodsDO.java new file mode 100644 index 0000000..0c31709 --- /dev/null +++ b/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/dto/ProdAdditionalFeePeriodsDO.java @@ -0,0 +1,62 @@ +package com.tashow.cloud.productapi.api.product.dto; + +import com.tashow.cloud.mybatis.mybatis.core.dataobject.BaseDO; +import com.tashow.cloud.productapi.general.StringListTypeHandler; +import lombok.*; + +import java.math.BigDecimal; +import java.util.List; + +import com.baomidou.mybatisplus.annotation.KeySequence; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +/** + * 特殊时段附加费用规则 DO + * + * @author 芋道源码 + */ +@TableName("tz_prod_additional_fee_periods") +@KeySequence("tz_prod_additional_fee_periods_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class ProdAdditionalFeePeriodsDO extends BaseDO { + + /** + * 特殊时段规则的唯一标识符 + */ + @TableId + private Long id; + /** + * 商品ID + */ + private Long prodId; + /** + * 名称 + */ + private String name; + /** + * 特殊时段设置 + */ + @TableField(typeHandler = StringListTypeHandler.class) + private List specialTimeSlots; + /** + * 收费方式0:'固定金额',1:'基准价上浮' + */ + private Integer chargeMode; + /** + * 价格或上浮百分比 + */ + private BigDecimal price; + /** + * 浮动百分比 + */ + private BigDecimal floatingPercentage; + public boolean isEmpty() { + return id == null ; + } +} \ No newline at end of file diff --git a/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/dto/ProdDO.java b/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/dto/ProdDO.java new file mode 100644 index 0000000..3cfe1e0 --- /dev/null +++ b/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/dto/ProdDO.java @@ -0,0 +1,178 @@ +package com.tashow.cloud.productapi.api.product.dto; + +import com.baomidou.mybatisplus.annotation.KeySequence; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import com.tashow.cloud.mybatis.mybatis.core.dataobject.BaseDO; +import com.tashow.cloud.productapi.general.StringListTypeHandler; +import lombok.*; + +import java.util.Date; +import java.util.List; + +/** + * 商品 DO + * + * @author 芋道源码 + */ +@TableName("tz_prod") +@KeySequence("tz_prod_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class ProdDO extends BaseDO { + + /** + * 产品ID + */ + @TableId + private Long prodId; + /** + * 商品名称 + */ + private String prodName; + /** + * 商品简称 + */ + private String abbreviation; + /** + * seo标题 + */ + private String seoShortName; + /** + * seo搜索 + */ + private String seoSearch; + /** + * 关键词 + */ + private String keyword; + /** + * 店铺id + */ + private Long shopId; + /** + * 简要描述,卖点等 + */ + private String brief; + /** + * 品牌 + */ + private String brand; + + + /** + * 是否置灰0否1是 + */ + private Integer isProhibit; + + /** + * 审核备注 + */ + private String processNotes; + /** + * 详细描述 + */ + private String content; + /** + * 商品编号 + */ + private String prodNumber; + /** + * 商品主图 + */ + private String pic; + /** + * 商品轮播图片,以,分割 + */ + private String imgs; + + /** + * 视频 + */ + private String video; + + /** + * 商品轮播图片,以,分割 + */ + private String whiteImg; + + /** + * 标签 + */ + @TableField(typeHandler = StringListTypeHandler.class) + private List tag; + + /** + * 默认是1,正常状态(出售中), 0:下架(仓库中) 2:待审核 + */ + private Integer status; + /** + * 商品分类id + */ + private Long categoryId; + + /** + * 商品分类名称 + */ + private String categoryName; + + /** + * 销量 + */ + private Integer soldNum; + /** + * 分享图 + */ + private String shareImage; + /** + * 分享话术 + */ + private String shareContent; + /** + * 是否开启区域0关1开 + */ + private Integer regionSwitch; + /** + * 是否特殊时段0关1开 + */ + private Integer additionalSwitch; + /** + * 是否特殊日期(节假日周末什么的)0关1开 + */ + private Integer additionalFeeSwitch; + /** + * 是否紧急响应服务0关1开 + */ + private Integer emergencySwitch; + /** + * 是否预约0关1开 + */ + private Integer reservationSwitch; + /** + * 是否接单上线0关1开 + */ + private Integer orderLimitSwitch; + /** + * 是否开启体重配置0关1开 + */ + private Integer weightSwitch; + /** + * 版本 乐观锁 + */ + private Integer version; + /** + * 展示的权重 + */ + private Integer top; + + /** + * 删除时间 + */ + private Date deleteTime; + +} \ No newline at end of file diff --git a/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/dto/ProdEmergencyResponseDO.java b/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/dto/ProdEmergencyResponseDO.java new file mode 100644 index 0000000..36916f4 --- /dev/null +++ b/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/dto/ProdEmergencyResponseDO.java @@ -0,0 +1,43 @@ +package com.tashow.cloud.productapi.api.product.dto; + +import com.tashow.cloud.mybatis.mybatis.core.dataobject.BaseDO; +import com.tashow.cloud.productapi.general.StringListTypeHandler; +import lombok.*; +import com.baomidou.mybatisplus.annotation.KeySequence; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; + +import java.util.List; + +/** + * 商品紧急响应服务设置 DO + * + * @author 芋道源码 + */ +@TableName("tz_prod_emergency_response") +@KeySequence("tz_prod_emergency_response_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class ProdEmergencyResponseDO extends BaseDO { + + /** + * 紧急响应服务配置的唯一标识符 + */ + @TableId + private Long id; + /** + * 关联的商品ID + */ + private Long prodId; + /** + * 可响应时间段 + */ + @TableField(typeHandler = StringListTypeHandler.class) + private List responseTimeSlots; + +} \ No newline at end of file diff --git a/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/dto/ProdEmergencyResponseIntervalsDO.java b/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/dto/ProdEmergencyResponseIntervalsDO.java new file mode 100644 index 0000000..6e3d970 --- /dev/null +++ b/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/dto/ProdEmergencyResponseIntervalsDO.java @@ -0,0 +1,65 @@ +package com.tashow.cloud.productapi.api.product.dto; + +import com.tashow.cloud.mybatis.mybatis.core.dataobject.BaseDO; +import lombok.*; + +import java.math.BigDecimal; +import com.baomidou.mybatisplus.annotation.KeySequence; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +/** + * 紧急响应时间区间设置 DO + * + * @author 芋道源码 + */ +@TableName("tz_prod_emergency_response_intervals") +@KeySequence("tz_prod_emergency_response_intervals_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class ProdEmergencyResponseIntervalsDO extends BaseDO { + + /** + * 响应时间区间的唯一标识符 + */ + @TableId + private Long id; + /** + * 关联的紧急响应服务配置ID + */ + private Long configId; + /** + * 商品ID + */ + private Long prodId; + + /** + * 响应模式名称modeName + */ + private String name; + /** + * 响应时间(小时) + */ + private BigDecimal responseHours; + /** + * 收费模式0:固定收费 1:浮动收费 + */ + private Integer chargeMode; + /** + * 浮动百分比 + */ + private BigDecimal floatingPercentage; + /** + * 价格或上浮百分比 + */ + private BigDecimal price; + + + public boolean isEmpty() { + return id == null; + } +} \ No newline at end of file diff --git a/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/dto/ProdExtendDO.java b/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/dto/ProdExtendDO.java new file mode 100644 index 0000000..df8fca3 --- /dev/null +++ b/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/dto/ProdExtendDO.java @@ -0,0 +1,49 @@ +package com.tashow.cloud.productapi.api.product.dto; + +import com.baomidou.mybatisplus.annotation.KeySequence; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.*; +import com.baomidou.mybatisplus.annotation.KeySequence; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +/** + * 属性规则 DO + * + * @author 芋道源码 + */ +@TableName("tz_prod_extend") +@KeySequence("tz_prod_extend_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 +@Data +@ToString(callSuper = true) +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class ProdExtendDO { + + /** + * 属性值ID + */ + @TableId + private Long id; + /** + * 商品id + */ + private Long prodId; + /** + * 是否显示失效规格值 0否1是 + */ + private Integer isExpire; + + /** + * 是否显示禁用规格值 0否1是 + */ + private Integer isDisable; + + /** + * 体重是否收费0否1是 + */ + private Integer isWeightCharge; + +} \ No newline at end of file diff --git a/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/dto/ProdPropDO.java b/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/dto/ProdPropDO.java new file mode 100644 index 0000000..660a13a --- /dev/null +++ b/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/dto/ProdPropDO.java @@ -0,0 +1,78 @@ +package com.tashow.cloud.productapi.api.product.dto; + +import com.baomidou.mybatisplus.annotation.KeySequence; +import com.baomidou.mybatisplus.annotation.TableName; +import com.tashow.cloud.productapi.api.product.dto.ProdPropValueDO; +import lombok.*; + +import java.util.List; +import com.baomidou.mybatisplus.annotation.KeySequence; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +/** + * 商品属性 DO + * + * @author 芋道源码 + */ +@TableName("tz_prod_prop") +@KeySequence("tz_prod_prop_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 +@Data +@ToString(callSuper = true) +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class ProdPropDO{ + + + /** + * 属性id + */ + @TableId + private Long id; + + private Long propId; + /** + * 属性名称 + */ + private String propName; + /** + * ProdPropRule 1:销售属性(规格); 2:参数属性; + */ + private Integer rule; + /** + * 店铺id + */ + private Long shopId; + /** + * 商品id + */ + private Long prodId; + + /** + * 排序 + */ + private Integer sort; + + /** + * 是否删除0否1是 + */ + private Integer deleted; + /** + * isExist 是否新增 0否1是 + */ + @TableField(exist=false) + private Integer isExist; + + + /** + * 状态0禁用1启用 + */ + private Integer state; + + /** + * 属性值 + */ + @TableField(exist=false) + private List prodPropValues; +} \ No newline at end of file diff --git a/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/dto/ProdPropValueDO.java b/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/dto/ProdPropValueDO.java new file mode 100644 index 0000000..c8cccd5 --- /dev/null +++ b/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/dto/ProdPropValueDO.java @@ -0,0 +1,69 @@ +package com.tashow.cloud.productapi.api.product.dto; + +import lombok.*; +import com.baomidou.mybatisplus.annotation.KeySequence; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; + +import java.util.Date; + +/** + * 属性规则 DO + * + * @author 芋道源码 + */ +@TableName("tz_prod_prop_value") +@KeySequence("tz_prod_prop_value_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 +@Data +@ToString(callSuper = true) +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class ProdPropValueDO { + + /** + * id + */ + @TableId + private Long id; + + private Long valueId; + /** + * 属性值名称 + */ + private String propValue; + /** + * 属性ID + */ + private Long propId; + /** + * 是否删除0否1是 + */ + private Integer deleted; + + /** + * 状态0禁用1启用 + */ + private Integer state; + + /** + * 是否失效0否1是 + */ + private Integer isExpire; + + /** + * 排序 + */ + private Integer sort; + /** + * isExist 是否新增 0否1是 + */ + @TableField(exist=false) + private Integer isExist; + + /** + * 删除时间 + */ + private Date deleteTime; +} \ No newline at end of file diff --git a/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/dto/ProdReservationConfigDO.java b/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/dto/ProdReservationConfigDO.java new file mode 100644 index 0000000..84c82eb --- /dev/null +++ b/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/dto/ProdReservationConfigDO.java @@ -0,0 +1,87 @@ +package com.tashow.cloud.productapi.api.product.dto; + +import com.tashow.cloud.mybatis.mybatis.core.dataobject.BaseDO; +import com.tashow.cloud.productapi.api.product.vo.prodreservationconfig.TimeBookVO; +import com.tashow.cloud.productapi.general.StringListTypeHandler; +import lombok.*; +import com.baomidou.mybatisplus.annotation.KeySequence; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; + +import java.util.List; + +/** + * 商品预约配置 DO + * + * @author 芋道源码 + */ +@TableName("tz_prod_reservation_config") +@KeySequence("tz_prod_reservation_config_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class ProdReservationConfigDO extends BaseDO { + + /** + * 预约配置的唯一标识符 + */ + @TableId + private Long id; + /** + * 关联的商品ID + */ + private Long prodId; + /** + * 预约时段设置 + */ + @TableField(typeHandler = StringListTypeHandler.class) + private List reservationTimeSlots; + /** + * 需提前多少小时预约 + */ + private Integer advanceHours; + /** + * 预约日期范围 7天 10天 15天 30天 + */ + private Integer reservationDateRange; + /** + * 是否允许更改预约时间 1可以 0不可以 + */ + private Integer allowChange; + + /** + * 时间段 + */ + private Integer timeSlot; + + /** + * 更改预约时间的时间规则(如服务开始前1小时可更改) + */ + private Integer changeTimeRule; + /** + * 允许更改预约时间的最大次数 + */ + private Integer maxChangeTimes; + /** + * 预约时间区间设置 + */ + @TableField(exist=false) + private TimeBookVO timeBook; + + public TimeBookVO getTimeBook() { + if (this.timeBook == null) { + this.timeBook = new TimeBookVO(); + this.timeBook.setTimeSlot(this.timeSlot); + this.timeBook.setReservationTimeSlots(this.reservationTimeSlots); + } + return this.timeBook; + } + + public void setTimeBook(TimeBookVO timeBook) { + this.timeBook = timeBook; + } +} \ No newline at end of file diff --git a/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/dto/ProdServiceAreaRelevanceDO.java b/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/dto/ProdServiceAreaRelevanceDO.java new file mode 100644 index 0000000..c9594bd --- /dev/null +++ b/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/dto/ProdServiceAreaRelevanceDO.java @@ -0,0 +1,31 @@ +package com.tashow.cloud.productapi.api.product.dto; + +import lombok.*; +import com.baomidou.mybatisplus.annotation.KeySequence; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +/** + * 商品与服务区域关联 DO + * + * @author 芋道源码 + */ +@TableName("tz_prod_service_area_relevance") +@KeySequence("tz_prod_service_area_relevance_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 +@Data +@ToString(callSuper = true) +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class ProdServiceAreaRelevanceDO{ + + /** + * 关联的商品ID + */ + private Long prodId; + /** + * 关联的服务区域ID + */ + private Long areaId; + +} \ No newline at end of file diff --git a/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/dto/ProdServiceAreasDO.java b/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/dto/ProdServiceAreasDO.java new file mode 100644 index 0000000..2427959 --- /dev/null +++ b/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/dto/ProdServiceAreasDO.java @@ -0,0 +1,34 @@ +package com.tashow.cloud.productapi.api.product.dto; + +import com.tashow.cloud.mybatis.mybatis.core.dataobject.BaseDO; +import lombok.*; +import com.baomidou.mybatisplus.annotation.KeySequence; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +/** + * 服务区域 DO + * + * @author 芋道源码 + */ +@TableName("tz_prod_service_areas") +@KeySequence("tz_prod_service_areas_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class ProdServiceAreasDO extends BaseDO { + + /** + * 服务区域的唯一标识符 + */ + @TableId + private Long id; + /** + * 服务区域名称(如台江区、鼓楼区) + */ + private String areaName; + +} \ No newline at end of file diff --git a/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/dto/ProdServiceOverAreaRulesDO.java b/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/dto/ProdServiceOverAreaRulesDO.java new file mode 100644 index 0000000..774a691 --- /dev/null +++ b/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/dto/ProdServiceOverAreaRulesDO.java @@ -0,0 +1,44 @@ +package com.tashow.cloud.productapi.api.product.dto; + +import com.tashow.cloud.mybatis.mybatis.core.dataobject.BaseDO; +import lombok.*; + +import java.math.BigDecimal; +import com.baomidou.mybatisplus.annotation.KeySequence; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +/** + * 超区规则 DO + * + * @author 芋道源码 + */ +@TableName("tz_prod_service_over_area_rules") +@KeySequence("tz_prod_service_over_area_rules_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class ProdServiceOverAreaRulesDO extends BaseDO { + + /** + * 超区规则的唯一标识符 + */ + @TableId + private Long id; + /** + * 关联的商品ID + */ + private Long prodId; + /** + * 超区规则类型(0:拒单、2:接单并收取超区费、3:接单并免超区费) + */ + private Integer ruleType; + /** + * 超区费用(仅在rule_type为accept_with_fee时有效) + */ + private BigDecimal fee; + +} \ No newline at end of file diff --git a/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/dto/ProdTagsDO.java b/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/dto/ProdTagsDO.java new file mode 100644 index 0000000..c29c5b8 --- /dev/null +++ b/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/dto/ProdTagsDO.java @@ -0,0 +1,33 @@ +package com.tashow.cloud.productapi.api.product.dto; + +import com.tashow.cloud.mybatis.mybatis.core.dataobject.BaseDO; +import lombok.*; +import com.baomidou.mybatisplus.annotation.KeySequence; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +/** + * 商品和标签管理 DO + * + * @author 芋道源码 + */ +@TableName("tz_prod_tags") +@KeySequence("tz_prod_tags_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class ProdTagsDO extends BaseDO { + + /** + * 商品id + */ + private Long productId; + /** + * 标签id + */ + private Long tagId; + +} \ No newline at end of file diff --git a/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/dto/ProdWeightRangePricesDO.java b/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/dto/ProdWeightRangePricesDO.java new file mode 100644 index 0000000..87c40a9 --- /dev/null +++ b/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/dto/ProdWeightRangePricesDO.java @@ -0,0 +1,48 @@ +package com.tashow.cloud.productapi.api.product.dto; + +import com.tashow.cloud.mybatis.mybatis.core.dataobject.BaseDO; +import lombok.*; + +import java.math.BigDecimal; +import com.baomidou.mybatisplus.annotation.KeySequence; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +/** + * 体重区间价格 DO + * + * @author 芋道源码 + */ +@TableName("tz_prod_weight_range_prices") +@KeySequence("tz_prod_weight_range_prices_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class ProdWeightRangePricesDO extends BaseDO { + + /** + * 体重区间价格的唯一标识符 + */ + @TableId + private Long id; + /** + * 关联的体重配置ID + */ + private Long prodId; + /** + * 体重区间 + */ + private String weightRange; + /** + * 价格 + */ + private BigDecimal price; + /** + * 是否收费0否1是 + */ + private Integer isEnabled; + +} \ No newline at end of file diff --git a/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/dto/ProductOrderLimitDO.java b/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/dto/ProductOrderLimitDO.java new file mode 100644 index 0000000..1fb7010 --- /dev/null +++ b/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/dto/ProductOrderLimitDO.java @@ -0,0 +1,42 @@ +package com.tashow.cloud.productapi.api.product.dto; + +import com.tashow.cloud.mybatis.mybatis.core.dataobject.BaseDO; +import lombok.*; +import com.baomidou.mybatisplus.annotation.KeySequence; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +/** + * 商品接单上限设置 DO + * + * @author 芋道源码 + */ +@TableName("tz_product_order_limit") +@KeySequence("tz_product_order_limit_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class ProductOrderLimitDO extends BaseDO { + + /** + * 接单上限配置的唯一标识符 + */ + @TableId + private Long id; + /** + * 关联的商品ID + */ + private Long prodId; + /** + * 限制单位'0:按自然天',1:'按自然周',2:'按自然月' + */ + private Integer limitUnit; + /** + * 上限阈值 + */ + private Integer maxOrders; + +} \ No newline at end of file diff --git a/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/dto/ShopDetailDO.java b/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/dto/ShopDetailDO.java new file mode 100644 index 0000000..595af98 --- /dev/null +++ b/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/dto/ShopDetailDO.java @@ -0,0 +1,128 @@ +package com.tashow.cloud.productapi.api.product.dto; + +import com.tashow.cloud.mybatis.mybatis.core.dataobject.BaseDO; +import lombok.*; + +import java.math.BigDecimal; +import com.baomidou.mybatisplus.annotation.KeySequence; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +/** + * 店铺信息 DO + * + * @author 芋道源码 + */ +@TableName("tz_shop_detail") +@KeySequence("tz_shop_detail_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class ShopDetailDO extends BaseDO { + + /** + * 店铺id + */ + @TableId + private Long shopId; + /** + * 店铺名称(数字、中文,英文(可混合,不可有特殊字符),可修改)、不唯一 + */ + private String shopName; + /** + * 店长用户id + */ + private String userId; + /** + * 店铺类型 + */ + private Integer shopType; + /** + * 店铺简介(可修改) + */ + private String intro; + /** + * 店铺公告(可修改) + */ + private String shopNotice; + /** + * 店铺行业(餐饮、生鲜果蔬、鲜花等) + */ + private Integer shopIndustry; + /** + * 店长 + */ + private String shopOwner; + /** + * 店铺绑定的手机(登录账号:唯一) + */ + private String mobile; + /** + * 店铺联系电话 + */ + private String tel; + /** + * 店铺所在纬度(可修改) + */ + private String shopLat; + /** + * 店铺所在经度(可修改) + */ + private String shopLng; + /** + * 店铺详细地址 + */ + private String shopAddress; + /** + * 店铺所在省份(描述) + */ + private String province; + /** + * 店铺所在城市(描述) + */ + private String city; + /** + * 店铺所在区域(描述) + */ + private String area; + /** + * 店铺省市区代码,用于回显 + */ + private String pcaCode; + /** + * 店铺logo(可修改) + */ + private String shopLogo; + /** + * 店铺相册 + */ + private String shopPhotos; + /** + * 每天营业时间段(可修改) + */ + private String openTime; + /** + * 店铺状态(-1:未开通 0: 停业中 1:营业中),可修改 + */ + private Integer shopStatus; + /** + * 0:商家承担运费; 1:买家承担运费 + */ + private Integer transportType; + /** + * 固定运费 + */ + private BigDecimal fixedFreight; + /** + * 满X包邮 + */ + private BigDecimal fullFreeShipping; + /** + * 分销开关(0:开启 1:关闭) + */ + private Integer isDistribution; + +} \ No newline at end of file diff --git a/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/dto/SkuDO.java b/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/dto/SkuDO.java new file mode 100644 index 0000000..cb11aaa --- /dev/null +++ b/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/dto/SkuDO.java @@ -0,0 +1,179 @@ +package com.tashow.cloud.productapi.api.product.dto; + +import com.tashow.cloud.mybatis.mybatis.core.dataobject.BaseDO; +import lombok.*; + +import java.math.BigDecimal; +import java.util.Date; +import com.baomidou.mybatisplus.annotation.KeySequence; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +/** + * 单品SKU DO + * + * @author 芋道源码 + */ +@TableName("tz_sku") +@KeySequence("tz_sku_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class SkuDO extends BaseDO { + + /** + * 单品ID + */ + @TableId + private Long skuId; + /** + * 商品ID + */ + private Long prodId; + /** + * 销售属性组合字符串 格式是p1:v1;p2:v2 + */ + private String properties; + /** + * 别名 + */ + private String alias; + /** + * 当前价格 + */ + private BigDecimal price; + /** + * 基准价 + */ + private BigDecimal basePrice; + /** + * 最低价格 + */ + private BigDecimal minPrice; + /** + * 最高价格 + */ + private BigDecimal maxPrice; + /** + * 成本价 + */ + private BigDecimal originalPrice; + /** + * 市场价 + */ + private BigDecimal marketPrice; + + /** + * 服务内容 + */ + private String serviceContent; + /** + * 规格id 多个用逗号分隔(1,2,3) + */ + private String propIds; + /** + * 单位 + */ + private String unit; + /** + * 0:主服务1:待定 + */ + private Integer type; + /** + * 概述 + */ + private String overview; + /** + * 库存 + */ + private Integer stocks; + + + /** + * 总库存是0 无线库存是1 + */ + private Integer stocksFlg; + + /** + * 锁定库存数 + */ + @TableField(exist=false) + private Integer stocksLockNum; + /** + * 预警库存 + */ + private Integer warnStocks; + /** + * 库存扣款时机0:付款扣1:下单扣 + */ + private Integer stocksType; + /** + * sku编码 + */ + private String skuCode; + /** + * 商品条形码 + */ + private String modelId; + /** + * sku图片 + */ + private String pic; + /** + * sku名称 + */ + private String skuName; + /** + * 商品名称 + */ + private String prodName; + /** + * 版本号 + */ + private Integer version; + /** + * 商品重量 + */ + private Double weight; + /** + * 商品体积 + */ + private Double volume; + /** + * 0 禁用 1 启用 + */ + private Integer status; + /** + * 最小购买数量 + */ + private Integer moq; + /** + * 是否上下架0下架1上架 + */ + private Integer isShelf; + + /** + * 是否默认规则0否1是 + */ + private Integer isSpecs; + + /** + * 扩展服务表单id + */ + private Long formId; + + + /** + * isExist 是否新增 0否1是 + */ + @TableField(exist=false) + private Integer isExist; + + /** + * 删除时间 + */ + private Date deleteTime; +} \ No newline at end of file diff --git a/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/dto/SkuServiceDeliverDO.java b/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/dto/SkuServiceDeliverDO.java new file mode 100644 index 0000000..57ad3bc --- /dev/null +++ b/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/dto/SkuServiceDeliverDO.java @@ -0,0 +1,67 @@ +package com.tashow.cloud.productapi.api.product.dto; + +import com.tashow.cloud.mybatis.mybatis.core.dataobject.BaseDO; +import lombok.*; + +import java.math.BigDecimal; +import com.baomidou.mybatisplus.annotation.KeySequence; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +/** + * 服务交付方式 DO + * + * @author 芋道源码 + */ +@TableName("tz_sku_service_deliver") +@KeySequence("tz_sku_service_deliver_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class SkuServiceDeliverDO extends BaseDO { + + /** + * 服务遗体运输唯一标识符 + */ + @TableId + private Long id; + /** + * 关联的扩展服务ID + */ + private Long serviceId; + /** + * 交互方式0:快递物流 1:到店自提 2:商家自送 + */ + private Integer type; + /** + * 价格 + */ + private BigDecimal price; + /** + * 是否收费0:免费1收费 + */ + private Integer isCharge; + /** + * 详细地址 + */ + private String address; + /** + * 省 + */ + private String province; + /** + * 市 + */ + private String city; + /** + * 区 + */ + private String area; + /** + * 电话号码 + */ + private String tel; +} \ No newline at end of file diff --git a/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/dto/SkuServiceDetailsDO.java b/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/dto/SkuServiceDetailsDO.java new file mode 100644 index 0000000..d08f625 --- /dev/null +++ b/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/dto/SkuServiceDetailsDO.java @@ -0,0 +1,86 @@ +package com.tashow.cloud.productapi.api.product.dto; + +import com.tashow.cloud.mybatis.mybatis.core.dataobject.BaseDO; +import lombok.*; + +import java.math.BigDecimal; +import com.baomidou.mybatisplus.annotation.KeySequence; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +/** + * 服务详情 DO + * + * @author 芋道源码 + */ +@TableName("tz_sku_service_details") +@KeySequence("tz_sku_service_details_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class SkuServiceDetailsDO extends BaseDO { + + /** + * 服务详情的唯一标识符 + */ + @TableId + private Long id; + /** + * 关联的扩展服务ID + */ + private Long serviceId; + /** + * 图片 + */ + private String pic; + /** + * 名称 + */ + private String name; + /** + * 价格 + */ + private BigDecimal price; + /** + * 是否收费0:免费1收费 + */ + private Integer isCharge; + /** + * 是否默认值0否1是 + */ + private Integer isDefault; + /** + * 类型:0:配置信息1:交付方式 + */ + private Integer type; + + /** + * 地点 + */ + private String adress; + + /** + * 触发节点名称 + */ + private String triggerName; + + /** + * 触发节点id(或关联节点) + */ + private Long triggerId; + + + /** + * 是否并行0串行1并行 + */ + private Integer isParallel; + + /** + * 描述 + */ + private String describeContent; + +} \ No newline at end of file diff --git a/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/dto/SkuServiceMaterialDO.java b/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/dto/SkuServiceMaterialDO.java new file mode 100644 index 0000000..70de6cc --- /dev/null +++ b/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/dto/SkuServiceMaterialDO.java @@ -0,0 +1,42 @@ +package com.tashow.cloud.productapi.api.product.dto; + +import com.tashow.cloud.mybatis.mybatis.core.dataobject.BaseDO; +import lombok.*; +import com.baomidou.mybatisplus.annotation.KeySequence; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +/** + * 服务物料详情 DO + * + * @author 芋道源码 + */ +@TableName("tz_sku_service_material") +@KeySequence("tz_sku_service_material_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class SkuServiceMaterialDO extends BaseDO { + + /** + * 服务物料的唯一标识符 + */ + @TableId + private Long id; + /** + * 关联的扩展服务ID + */ + private Long serviceId; + /** + * 名称 + */ + private String name; + /** + * 描述 + */ + private String describeContent; + +} \ No newline at end of file diff --git a/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/dto/SkuServiceTransportDO.java b/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/dto/SkuServiceTransportDO.java new file mode 100644 index 0000000..584219b --- /dev/null +++ b/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/dto/SkuServiceTransportDO.java @@ -0,0 +1,58 @@ +package com.tashow.cloud.productapi.api.product.dto; + +import com.tashow.cloud.mybatis.mybatis.core.dataobject.BaseDO; +import lombok.*; +import com.baomidou.mybatisplus.annotation.KeySequence; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +/** + * 服务遗体运输 DO + * + * @author 芋道源码 + */ +@TableName("tz_sku_service_transport") +@KeySequence("tz_sku_service_transport_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class SkuServiceTransportDO extends BaseDO { + + /** + * 服务遗体运输唯一标识符 + */ + @TableId + private Long id; + /** + * 关联的扩展服务ID + */ + private Long serviceId; + /** + * 联系人 + */ + private String contacts; + /** + * 详细地址 + */ + private String address; + /** + * 省 + */ + private String province; + /** + * 市 + */ + private String city; + /** + * 区 + */ + private String area; + /** + * 电话号码 + */ + private String tel; + +} \ No newline at end of file diff --git a/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/dto/SkuServicesFormDO.java b/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/dto/SkuServicesFormDO.java new file mode 100644 index 0000000..35cb66f --- /dev/null +++ b/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/dto/SkuServicesFormDO.java @@ -0,0 +1,53 @@ +package com.tashow.cloud.productapi.api.product.dto; + +import com.tashow.cloud.mybatis.mybatis.core.dataobject.BaseDO; +import lombok.*; +import com.baomidou.mybatisplus.annotation.KeySequence; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +/** + * 商品SKU扩展服务表单 DO + * + * @author 芋道源码 + */ +@TableName("tz_sku_services_form") +@KeySequence("tz_sku_services_form_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class SkuServicesFormDO extends BaseDO { + + /** + * 扩展服务的唯一标识符 + */ + @TableId + private Long id; + /** + * 表单名称 + */ + private String name; + + /** + * 表单ID + */ + private Integer formId; + + /** + * 服务名称 + */ + private String serviceName; + /** + * 是否启用该服务 + */ + private Integer isEnabled; + + /** + * 类型 + */ + private Integer type; + +} \ No newline at end of file diff --git a/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/vo/CategoryPageReqVO.java b/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/vo/CategoryPageReqVO.java new file mode 100644 index 0000000..551f9b8 --- /dev/null +++ b/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/vo/CategoryPageReqVO.java @@ -0,0 +1,55 @@ +package com.tashow.cloud.productapi.api.product.vo; + +import com.tashow.cloud.common.pojo.PageParam; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; +import org.springframework.format.annotation.DateTimeFormat; + +import java.time.LocalDateTime; +import java.util.List; + +import static com.tashow.cloud.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND; + +@Schema(description = "管理后台 - 产品类目分页 Request VO") +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class CategoryPageReqVO extends PageParam { + + @Schema(description = "店铺ID", example = "22369") + private Long shopId; + + @Schema(description = "父节点", example = "16509") + private Long parentId; + + @Schema(description = "产品类目名称", example = "王五") + private String categoryName; + + @Schema(description = "类目图标") + private String icon; + + @Schema(description = "类目的显示图片") + private String pic; + + @Schema(description = "类目描述") + private String description; + + @Schema(description = "标签") + private List tag; + + @Schema(description = "排序") + private Integer sort; + + @Schema(description = "默认是1,表示正常状态,0为下线状态", example = "1") + private Integer status; + + @Schema(description = "创建时间") + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) + private LocalDateTime[] createTime; + + @Schema(description = "分类层级") + private Integer grade; + +} \ No newline at end of file diff --git a/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/vo/CategoryRespVO.java b/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/vo/CategoryRespVO.java new file mode 100644 index 0000000..83b2dae --- /dev/null +++ b/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/vo/CategoryRespVO.java @@ -0,0 +1,64 @@ +package com.tashow.cloud.productapi.api.product.vo; + +import com.alibaba.excel.annotation.ExcelIgnoreUnannotated; +import com.alibaba.excel.annotation.ExcelProperty; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.time.LocalDateTime; +import java.util.List; + +@Schema(description = "管理后台 - 产品类目 Response VO") +@Data +@ExcelIgnoreUnannotated +public class CategoryRespVO { + + @Schema(description = "类目ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "15856") + @ExcelProperty("类目ID") + private Long categoryId; + + @Schema(description = "店铺ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "22369") + @ExcelProperty("店铺ID") + private Long shopId; + + @Schema(description = "父节点", requiredMode = Schema.RequiredMode.REQUIRED, example = "16509") + @ExcelProperty("父节点") + private Long parentId; + + @Schema(description = "产品类目名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "王五") + @ExcelProperty("产品类目名称") + private String categoryName; + + @Schema(description = "类目图标") + @ExcelProperty("类目图标") + private String icon; + + @Schema(description = "类目的显示图片") + @ExcelProperty("类目的显示图片") + private String pic; + + @Schema(description = "类目描述") + @ExcelProperty("类目描述") + private String description; + + @Schema(description = "标签") + @ExcelProperty("标签") + private List tag; + + @Schema(description = "排序", requiredMode = Schema.RequiredMode.REQUIRED) + @ExcelProperty("排序") + private Integer sort; + + @Schema(description = "默认是1,表示正常状态,0为下线状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + @ExcelProperty("默认是1,表示正常状态,0为下线状态") + private Integer status; + + @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED) + @ExcelProperty("创建时间") + private LocalDateTime createTime; + + @Schema(description = "分类层级", requiredMode = Schema.RequiredMode.REQUIRED) + @ExcelProperty("分类层级") + private Integer grade; + +} \ No newline at end of file diff --git a/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/vo/CategorySaveReqVO.java b/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/vo/CategorySaveReqVO.java new file mode 100644 index 0000000..35dc158 --- /dev/null +++ b/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/vo/CategorySaveReqVO.java @@ -0,0 +1,53 @@ +package com.tashow.cloud.productapi.api.product.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.util.List; + +@Schema(description = "管理后台 - 产品类目新增/修改 Request VO") +@Data +public class CategorySaveReqVO { + + @Schema(description = "类目ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "15856") + private Long categoryId; + + @Schema(description = "店铺ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "22369") + private Long shopId; + + @Schema(description = "父节点", requiredMode = Schema.RequiredMode.REQUIRED, example = "16509") + //@NotNull(message = "父节点不能为空") + private Long parentId; + + /** + * 父节名称 + */ + private String parentName; + + @Schema(description = "产品类目名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "王五") + //@NotEmpty(message = "产品类目名称不能为空") + private String categoryName; + + @Schema(description = "类目图标") + private String icon; + + @Schema(description = "类目的显示图片") + private String pic; + + @Schema(description = "类目描述") + private String description; + + @Schema(description = "标签") + private List tag; + + @Schema(description = "排序", requiredMode = Schema.RequiredMode.REQUIRED) + private Integer sort; + + @Schema(description = "默认是1,表示正常状态,0为下线状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + private Integer status; + + @Schema(description = "分类层级 1级 2级 3级", requiredMode = Schema.RequiredMode.REQUIRED) + //@NotNull(message = "分类层级不能为空") + private Integer grade; + +} \ No newline at end of file diff --git a/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/vo/prod/ProdListVO.java b/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/vo/prod/ProdListVO.java new file mode 100644 index 0000000..d4ae332 --- /dev/null +++ b/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/vo/prod/ProdListVO.java @@ -0,0 +1,95 @@ +package com.tashow.cloud.productapi.api.product.vo.prod; + +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.fasterxml.jackson.annotation.JsonFormat; +import com.tashow.cloud.productapi.api.product.dto.*; +import com.tashow.cloud.productapi.api.product.vo.prodemergencyresponse.ProdEmergencyInfoVO; +import com.tashow.cloud.productapi.api.product.vo.prodserviceareas.ProdServiceAreasInfoVO; +import com.tashow.cloud.productapi.general.StringListTypeHandler; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.math.BigDecimal; +import java.util.Date; +import java.util.List; + +@Schema(description = "商品服务配置 VO") +@Data +public class ProdListVO { + + /** + * 产品ID + */ + @TableId + private Long prodId; + /** + * 商品名称 + */ + private String prodName; + /** + * 分类名称 + */ + private String categoryName; + + /** + * 店铺id + */ + private Long shopId; + + /** + * 店铺id + */ + private String shopName; + + /** + * 默认是1,正常状态(出售中), 0:下架(仓库中) 2:待审核 + */ + private Integer status; + + /** + * 是否置灰0否1是 + */ + private Integer isProhibit; + /** + * 服务区域地址名称集合 + */ + private List areaNameList; + + /** + * 紧急服务最快响应时间(小时) + */ + private BigDecimal responseHours; + /** + * 服务时段 + */ + @TableField(typeHandler = StringListTypeHandler.class) + private List reservationTimeSlots; + + /** + * 还剩多少天 + */ + private Long remainingDays; + + /** + * 审核备注 + */ + private String processNotes; + /** + * 删除时间 + */ + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") + private Date deleteTime; + /** + * 创建时间 + */ + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") + private Date createTime; + + /** + * 更新时间 + */ + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") + private Date updateTime; + +} \ No newline at end of file diff --git a/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/vo/prod/ProdPageReqVO.java b/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/vo/prod/ProdPageReqVO.java new file mode 100644 index 0000000..33e3e6f --- /dev/null +++ b/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/vo/prod/ProdPageReqVO.java @@ -0,0 +1,42 @@ +package com.tashow.cloud.productapi.api.product.vo.prod; + +import com.tashow.cloud.common.pojo.PageParam; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; +import org.springframework.format.annotation.DateTimeFormat; + +import java.time.LocalDateTime; +import java.util.List; + +import static com.tashow.cloud.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND; + +@Schema(description = "管理后台 - 商品分页 Request VO") +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class ProdPageReqVO extends PageParam { + + @Schema(description = "商品名称", example = "赵六") + private String prodName; + + @Schema(description = "店铺id", example = "10843") + private Long shopId; + + @Schema(description = "默认是1,正常状态(出售中), 0:下架(仓库中) 2:待审核", example = "2") + private Integer status; + + @Schema(description = "商品分类", example = "14895") + private Long categoryId; + + /** + * 商品分类名称 + */ + private String categoryName; + + @Schema(description = "创建时间") + //@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) + private String[] createTime; + +} \ No newline at end of file diff --git a/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/vo/prod/ProdRecycleBinVO.java b/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/vo/prod/ProdRecycleBinVO.java new file mode 100644 index 0000000..4fcbc6b --- /dev/null +++ b/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/vo/prod/ProdRecycleBinVO.java @@ -0,0 +1,22 @@ +package com.tashow.cloud.productapi.api.product.vo.prod; + +import com.tashow.cloud.common.pojo.PageParam; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; + +@Schema(description = "商品回收站分页查询") +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class ProdRecycleBinVO extends PageParam { + + @Schema(description = "商品名称", example = "18784") + private String prodName; + + @Schema(description = "删除时间") + //@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) + private String[] deleteTime; + +} \ No newline at end of file diff --git a/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/vo/prod/ProdRespVO.java b/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/vo/prod/ProdRespVO.java new file mode 100644 index 0000000..dd4be2d --- /dev/null +++ b/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/vo/prod/ProdRespVO.java @@ -0,0 +1,162 @@ +package com.tashow.cloud.productapi.api.product.vo.prod; + +import com.alibaba.excel.annotation.ExcelIgnoreUnannotated; +import com.alibaba.excel.annotation.ExcelProperty; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.time.LocalDateTime; +import java.util.List; + +@Schema(description = "管理后台 - 商品 Response VO") +@Data +@ExcelIgnoreUnannotated +public class ProdRespVO { + + @Schema(description = "产品ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "6943") + @ExcelProperty("产品ID") + private Long prodId; + + @Schema(description = "商品名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "赵六") + @ExcelProperty("商品名称") + private String prodName; + + @Schema(description = "商品简称") + @ExcelProperty("商品简称") + private String abbreviation; + + @Schema(description = "seo标题", example = "李四") + @ExcelProperty("seo标题") + private String seoShortName; + + + @Schema(description = "seo搜索") + @ExcelProperty("seo搜索") + private String seoSearch; + + @Schema(description = "关键词") + @ExcelProperty("关键词") + private String keyword; + + @Schema(description = "店铺id", example = "10843") + @ExcelProperty("店铺id") + private Long shopId; + + @Schema(description = "简要描述,卖点等") + @ExcelProperty("简要描述,卖点等") + private String brief; + + @Schema(description = "品牌") + @ExcelProperty("品牌") + private String brand; + + @Schema(description = "详细描述") + @ExcelProperty("详细描述") + private String content; + + @Schema(description = "商品编号") + @ExcelProperty("商品编号") + private String prodNumber; + + @Schema(description = "商品主图") + @ExcelProperty("商品主图") + private String pic; + + @Schema(description = "商品轮播图片,以,分割") + @ExcelProperty("商品轮播图片,以,分割") + private String imgs; + + /** + * 视频 + */ + private String video; + + /** + * 商品轮播图片,以,分割 + */ + private String whiteImg; + + @Schema(description = "默认是1,正常状态(出售中), 0:下架(仓库中) 2:待审核", example = "2") + @ExcelProperty("默认是1,正常状态(出售中), 0:下架(仓库中) 2:待审核") + private Integer status; + + @Schema(description = "商品分类", example = "14895") + @ExcelProperty("商品分类") + private Long categoryId; + + /** + * 商品分类名称 + */ + private String categoryName; + + @Schema(description = "销量") + @ExcelProperty("销量") + private Integer soldNum; + + @Schema(description = "分享图") + @ExcelProperty("分享图") + private String shareImage; + + @Schema(description = "'是否置灰0否1是'") + private Integer isProhibit; + + @Schema(description = "审核备注") + private String processNotes; + /** + * 标签 + */ + public List tag; + + @Schema(description = "分享话术") + @ExcelProperty("分享话术") + private String shareContent; + + @Schema(description = "是否开启区域0关1开") + @ExcelProperty("是否开启区域0关1开") + private Integer regionSwitch; + + @Schema(description = "是否特殊时段0关1开") + @ExcelProperty("是否特殊时段0关1开") + private Integer additionalSwitch; + + @Schema(description = "是否特殊日期(节假日周末什么的)0关1开") + @ExcelProperty("是否特殊日期(节假日周末什么的)0关1开") + private Integer additionalFeeSwitch; + + @Schema(description = "是否紧急响应服务0关1开") + @ExcelProperty("是否紧急响应服务0关1开") + private Integer emergencySwitch; + + @Schema(description = "是否预约0关1开") + @ExcelProperty("是否预约0关1开") + private Integer reservationSwitch; + + @Schema(description = "是否接单上线0关1开") + @ExcelProperty("是否接单上线0关1开") + private Integer orderLimitSwitch; + + @Schema(description = "是否开启体重配置0关1开") + @ExcelProperty("是否开启体重配置0关1开") + private Integer weightSwitch; + + @Schema(description = "创建时间") + @ExcelProperty("创建时间") + private LocalDateTime createTime; + + @Schema(description = "创建人") + @ExcelProperty("创建人") + private String createBy; + + @Schema(description = "修改人") + @ExcelProperty("修改人") + private String updateBy; + + @Schema(description = "版本 乐观锁") + @ExcelProperty("版本 乐观锁") + private Integer version; + + @Schema(description = "展示的权重") + @ExcelProperty("展示的权重") + private Integer top; + +} \ No newline at end of file diff --git a/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/vo/prod/ProdRestoreListVO.java b/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/vo/prod/ProdRestoreListVO.java new file mode 100644 index 0000000..59b556f --- /dev/null +++ b/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/vo/prod/ProdRestoreListVO.java @@ -0,0 +1,74 @@ +package com.tashow.cloud.productapi.api.product.vo.prod; + +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.fasterxml.jackson.annotation.JsonFormat; +import com.tashow.cloud.productapi.general.StringListTypeHandler; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.math.BigDecimal; +import java.util.Date; +import java.util.List; + +@Schema(description = "商品服务配置 VO") +@Data +public class ProdRestoreListVO { + + /** + * 产品ID + */ + @TableId + private Long prodId; + /** + * 商品名称 + */ + private String prodName; + /** + * 分类名称 + */ + private String categoryName; + + /** + * 店铺id + */ + private Long shopId; + + /** + * 店铺id + */ + private String shopName; + + /** + * 服务区域地址名称集合 + */ + private List areaNameList; + + /** + * 紧急服务最快响应时间(小时) + */ + private BigDecimal responseHours; + /** + * 服务时段 + */ + @TableField(typeHandler = StringListTypeHandler.class) + private List reservationTimeSlots; + + /** + * 还剩多少天 + */ + private Long remainingDays; + + /** + * 删除时间 + */ + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") + private Date deleteTime; + /** + * 创建时间 + */ + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") + private Date createTime; + + +} \ No newline at end of file diff --git a/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/vo/prod/ProdSaveReqVO.java b/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/vo/prod/ProdSaveReqVO.java new file mode 100644 index 0000000..c620f86 --- /dev/null +++ b/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/vo/prod/ProdSaveReqVO.java @@ -0,0 +1,134 @@ +package com.tashow.cloud.productapi.api.product.vo.prod; + +import com.tashow.cloud.productapi.api.product.dto.SkuDO; +import com.tashow.cloud.productapi.api.product.vo.prodprop.ProdPropSaveReqVO; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.util.List; + +@Schema(description = "管理后台 - 商品新增/修改 Request VO") +@Data +public class ProdSaveReqVO { + + @Schema(description = "产品ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "6943") + private Long prodId; + + //@Schema(description = "商品名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "赵六") + //@NotEmpty(message = "商品名称不能为空") + private String prodName; + + @Schema(description = "商品简称") + private String abbreviation; + + @Schema(description = "seo标题", example = "李四") + private String seoShortName; + + @Schema(description = "seo搜索") + private String seoSearch; + + @Schema(description = "关键词") + private String keyword; + + /** + * 商品分类名称 + */ + private String categoryName; + + @Schema(description = "店铺id", example = "10843") + private Long shopId; + + @Schema(description = "简要描述,卖点等") + private String brief; + + @Schema(description = "品牌") + private String brand; + + @Schema(description = "详细描述") + private String content; + /** + * 视频 + */ + private String video; + + /** + * 白底图 + */ + private String whiteImg; + + @Schema(description = "商品编号") + private String prodNumber; + + @Schema(description = "商品主图") + private String pic; + + @Schema(description = "商品轮播图片,以,分割") + private String imgs; + + @Schema(description = "默认是1,正常状态(出售中), 0:下架(仓库中) 2:待审核", example = "2") + private Integer status; + + @Schema(description = "'是否置灰0否1是'") + private Integer isProhibit; + + @Schema(description = "审核备注") + private String processNotes; + + + @Schema(description = "商品分类", example = "14895") + private Long categoryId; + + @Schema(description = "销量") + private Integer soldNum; + + @Schema(description = "分享图") + private String shareImage; + + /** + * 标签 + */ + public List tag; + + @Schema(description = "分享话术") + private String shareContent; + + @Schema(description = "是否开启区域0关1开") + private Integer regionSwitch; + + @Schema(description = "是否特殊时段0关1开") + private Integer additionalSwitch; + + @Schema(description = "是否特殊日期(节假日周末什么的)0关1开") + private Integer additionalFeeSwitch; + + @Schema(description = "是否紧急响应服务0关1开") + private Integer emergencySwitch; + + @Schema(description = "是否预约0关1开") + private Integer reservationSwitch; + + @Schema(description = "是否接单上线0关1开") + private Integer orderLimitSwitch; + + @Schema(description = "是否开启体重配置0关1开") + private Integer weightSwitch; + + @Schema(description = "创建人") + private String createBy; + + @Schema(description = "修改人") + private String updateBy; + + @Schema(description = "版本 乐观锁") + private Integer version; + + @Schema(description = "展示的权重") + private Integer top; + + @Schema(description = "sku列表") + private List skuList; + + @Schema(description = "规格") + private List prodPropSaveReqVO; + +} \ No newline at end of file diff --git a/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/vo/prod/ProdServiceInfoVO.java b/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/vo/prod/ProdServiceInfoVO.java new file mode 100644 index 0000000..7639abc --- /dev/null +++ b/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/vo/prod/ProdServiceInfoVO.java @@ -0,0 +1,84 @@ +package com.tashow.cloud.productapi.api.product.vo.prod; + +import com.fasterxml.jackson.annotation.JsonFormat; +import com.tashow.cloud.productapi.api.product.dto.ProdAdditionalFeeDatesDO; +import com.tashow.cloud.productapi.api.product.dto.ProdAdditionalFeePeriodsDO; +import com.tashow.cloud.productapi.api.product.dto.ProdWeightRangePricesDO; +import com.tashow.cloud.productapi.api.product.dto.ProductOrderLimitDO; +import com.tashow.cloud.productapi.api.product.vo.prodemergencyresponse.ProdEmergencyInfoReqVO; +import com.tashow.cloud.productapi.api.product.vo.prodemergencyresponse.ProdEmergencyInfoVO; +import com.tashow.cloud.productapi.api.product.vo.prodreservationconfig.ProdReservationInfoReqVO; +import com.tashow.cloud.productapi.api.product.vo.prodreservationconfig.ProdReservationInfoVO; +import com.tashow.cloud.productapi.api.product.vo.prodserviceareas.ProdServiceAreasInfoVO; +import com.tashow.cloud.productapi.api.product.vo.prodweightrangeprices.ProdWeightRangePricesSaveInfoVO; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.util.Date; +import java.util.List; +import java.util.stream.Collectors; + +@Schema(description = "商品服务配置 VO") +@Data +public class ProdServiceInfoVO { + + @Schema(description = "产品ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "6943") + private Long prodId; + + @Schema(description = "创建时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") + private Date createTime; + + @Schema(description = "修改时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") + private Date updateTime; + + @Schema(description = "新建人") + private String creator; + + @Schema(description = "修改人") + private String updater; + + @Schema(description = "分类名称") + private String categoryName; + + @Schema(description = "是否开启服务区域配置0关1开") + private Integer regionSwitch; + @Schema(description = "服务区域配置") + public ProdServiceAreasInfoVO prodServiceAreasInfo; + + + @Schema(description = "是否预约0关1开") + private Integer reservationSwitch; + @Schema(description = "预约配置") + public ProdReservationInfoReqVO prodReservationConfig; + + @Schema(description = "是否紧急响应服务0关1开") + private Integer emergencySwitch; + @Schema(description = "急响应服务配置") + public ProdEmergencyInfoReqVO prodEmergencyInfoVO; + + @Schema(description = "是否接单上线0关1开") + private Integer orderLimitSwitch; + @Schema(description = "接单上线配置") + public ProductOrderLimitDO productOrderLimitVO; + + + @Schema(description = "是否特殊日期(节假日周末什么的)0关1开 ") + private Integer additionalSwitch; + @Schema(description = "特殊日期规则配置") + public List prodAdditionalFeeDatesList; + + @Schema(description = "是否特殊时段0关1开") + private Integer additionalFeeSwitch; + @Schema(description = "特殊时段规则配置 ") + public List prodAdditionalFeePeriodsList; + + + @Schema(description = "是否开启体重配置0关1开") + private Integer weightSwitch; + @Schema(description = "体重配置") + public ProdWeightRangePricesSaveInfoVO prodWeightConfig; + + +} \ No newline at end of file diff --git a/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/vo/prod/ProdServiceVO.java b/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/vo/prod/ProdServiceVO.java new file mode 100644 index 0000000..03cab05 --- /dev/null +++ b/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/vo/prod/ProdServiceVO.java @@ -0,0 +1,119 @@ +package com.tashow.cloud.productapi.api.product.vo.prod; + +import com.fasterxml.jackson.annotation.JsonFormat; +import com.tashow.cloud.productapi.api.product.vo.prodadditionalfeedates.ProdAdditionalFeeBlackVO; +import com.tashow.cloud.productapi.api.product.vo.prodemergencyresponse.ProdEmergencyInfoVO; +import com.tashow.cloud.productapi.api.product.vo.prodreservationconfig.ProdReservationInfoVO; +import com.tashow.cloud.productapi.api.product.vo.prodserviceareas.ProdServiceAreasInfoVO; +import com.tashow.cloud.productapi.api.product.dto.*; +import com.tashow.cloud.productapi.api.product.vo.prodweightrangeprices.ProdWeightRangePricesSaveInfoVO; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.util.ArrayList; +import java.util.Date; +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; + +@Schema(description = "商品服务配置 VO") +@Data +public class ProdServiceVO { + + @Schema(description = "产品ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "6943") + private Long prodId; + + @Schema(description = "创建时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") + private Date createTime; + + @Schema(description = "修改时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") + private Date updateTime; + + @Schema(description = "新建人") + private String creator; + + @Schema(description = "修改人") + private String updater; + + @Schema(description = "分类名称") + private String categoryName; + + @Schema(description = "是否开启服务区域配置0关1开") + private Integer regionSwitch; + @Schema(description = "服务区域配置") + public ProdServiceAreasInfoVO prodServiceAreasInfo; + + + @Schema(description = "是否预约0关1开") + private Integer reservationSwitch; + @Schema(description = "预约配置") + public ProdReservationInfoVO prodReservationConfig; + + /* public List getProdReservationBlackList() { + if (prodReservationBlackList == null || prodReservationBlackList.isEmpty()) { + return prodReservationBlackList; + } + return prodReservationBlackList.stream() + .filter(Objects::nonNull) + .collect(Collectors.toList()); + } + + public void setProdReservationBlackList(List prodReservationBlackList) { + this.prodReservationBlackList = prodReservationBlackList; + }*/ + + + + + @Schema(description = "是否紧急响应服务0关1开") + private Integer emergencySwitch; + @Schema(description = "急响应服务配置") + public ProdEmergencyInfoVO prodEmergencyInfoVO; + + @Schema(description = "是否接单上线0关1开") + private Integer orderLimitSwitch; + @Schema(description = "接单上线配置") + public ProductOrderLimitDO productOrderLimitVO; + + + @Schema(description = "是否特殊日期(节假日周末什么的)0关1开") + private Integer additionalSwitch; + @Schema(description = "特殊日期规则配置") + public List prodAdditionalFeeDatesList; + + @Schema(description = "是否特殊时段0关1开 ") + private Integer additionalFeeSwitch; + @Schema(description = "特殊时段规则配置 ") + public List prodAdditionalFeePeriodsList; + + + @Schema(description = "是否开启体重配置0关1开") + private Integer weightSwitch; + @Schema(description = "体重配置") + public ProdWeightRangePricesSaveInfoVO prodWeightConfig; + + + public ProdReservationInfoVO getProdReservationConfig() { + + if (this.prodReservationConfig == null) { + return null; + } + // 判断是否“逻辑上为空” + if (isProdReservationInfoEmpty(this.prodReservationConfig)) { + return null; + } + return this.prodReservationConfig; + } + + public void setProdReservationConfig(ProdReservationInfoVO prodReservationConfig) { + this.prodReservationConfig = prodReservationConfig; + } + + private boolean isProdReservationInfoEmpty(ProdReservationInfoVO config) { + if (config == null) return true; + // 判断所有字段是否都为 null 或空 + return config.getId() == null; + } +} \ No newline at end of file diff --git a/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/vo/prodadditionalfeedates/ProdAdditionalFeeBlackVO.java b/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/vo/prodadditionalfeedates/ProdAdditionalFeeBlackVO.java new file mode 100644 index 0000000..e693bc9 --- /dev/null +++ b/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/vo/prodadditionalfeedates/ProdAdditionalFeeBlackVO.java @@ -0,0 +1,42 @@ +package com.tashow.cloud.productapi.api.product.vo.prodadditionalfeedates; + +import com.alibaba.excel.annotation.ExcelIgnoreUnannotated; +import com.alibaba.excel.annotation.ExcelProperty; +import com.baomidou.mybatisplus.annotation.TableField; +import com.tashow.cloud.productapi.general.StringListTypeHandler; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.math.BigDecimal; +import java.util.List; + +@Schema(description = "管理后台 - 特殊日期附加费用规则 Response VO") +@Data +@ExcelIgnoreUnannotated +public class ProdAdditionalFeeBlackVO { + + @Schema(description = "特殊日期规则的唯一标识符") + @ExcelProperty("特殊日期规则的唯一标识符") + private Long id; + + @Schema(description = "日期类型0:'自定义日期范围':1:'指定日期':2:'法定节假日',3:'固定休息日'") + @ExcelProperty("日期类型0:'自定义日期范围':1:'指定日期':2:'法定节假日',3:'固定休息日'") + private Integer dateType; + + @Schema(description = "黑名单日期设置") + @ExcelProperty("黑名单日期设置") + @TableField(typeHandler = StringListTypeHandler.class) + private List customTimeSlots; + + + @Schema(description = "类型:1:特殊日期 2:可预约时段黑名单日期 3:紧急相应服务黑名单日期") + @ExcelProperty("类型:1:特殊日期 2:可预约时段黑名单日期 3:紧急相应服务黑名单日期") + private Integer type; + + @Schema(description = "是否启用该规则是否启用该规则0关1开", requiredMode = Schema.RequiredMode.REQUIRED) + @ExcelProperty("是否启用该规则是否启用该规则0关1开") + private Integer isEnabled; + public boolean isEmpty() { + return id == null ; + } +} \ No newline at end of file diff --git a/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/vo/prodadditionalfeedates/ProdAdditionalFeeDatesPageReqVO.java b/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/vo/prodadditionalfeedates/ProdAdditionalFeeDatesPageReqVO.java new file mode 100644 index 0000000..d5f251c --- /dev/null +++ b/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/vo/prodadditionalfeedates/ProdAdditionalFeeDatesPageReqVO.java @@ -0,0 +1,42 @@ +package com.tashow.cloud.productapi.api.product.vo.prodadditionalfeedates; + +import com.tashow.cloud.common.pojo.PageParam; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; + +import java.math.BigDecimal; + +@Schema(description = "管理后台 - 特殊日期附加费用规则分页 Request VO") +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class ProdAdditionalFeeDatesPageReqVO extends PageParam { + + @Schema(description = "商品id", example = "24324") + private Long prodId; + + @Schema(description = "名称", example = "赵六") + private String name; + + @Schema(description = "日期类型0:'自定义日期范围':1:'指定日期':2:'法定节假日',3:'固定休息日'", example = "2") + private Integer dateType; + + @Schema(description = "自定义日期时间段(JSON格式存储)") + private String customTimeSlots; + + @Schema(description = "指定日期(JSON格式存储)") + private String specificDates; + + @Schema(description = "收费方式0:''固定金额'':1:''基准价上浮") + private Integer chargeMode; + + @Schema(description = "价格或上浮百分比", example = "17305") + private BigDecimal price; + + @Schema(description = "是否启用该规则是否启用该规则0关1开") + private Integer isEnabled; + + +} \ No newline at end of file diff --git a/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/vo/prodadditionalfeedates/ProdAdditionalFeeDatesRespVO.java b/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/vo/prodadditionalfeedates/ProdAdditionalFeeDatesRespVO.java new file mode 100644 index 0000000..80da4d7 --- /dev/null +++ b/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/vo/prodadditionalfeedates/ProdAdditionalFeeDatesRespVO.java @@ -0,0 +1,51 @@ +package com.tashow.cloud.productapi.api.product.vo.prodadditionalfeedates; + +import com.alibaba.excel.annotation.ExcelIgnoreUnannotated; +import com.alibaba.excel.annotation.ExcelProperty; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.math.BigDecimal; + +@Schema(description = "管理后台 - 特殊日期附加费用规则 Response VO") +@Data +@ExcelIgnoreUnannotated +public class ProdAdditionalFeeDatesRespVO { + + @Schema(description = "特殊日期规则的唯一标识符", requiredMode = Schema.RequiredMode.REQUIRED, example = "1445") + @ExcelProperty("特殊日期规则的唯一标识符") + private Long id; + + @Schema(description = "商品id", requiredMode = Schema.RequiredMode.REQUIRED, example = "24324") + @ExcelProperty("商品id") + private Long prodId; + + @Schema(description = "名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "赵六") + @ExcelProperty("名称") + private String name; + + @Schema(description = "日期类型0:'自定义日期范围':1:'指定日期':2:'法定节假日',3:'固定休息日'", requiredMode = Schema.RequiredMode.REQUIRED, example = "2") + @ExcelProperty("日期类型0:'自定义日期范围':1:'指定日期':2:'法定节假日',3:'固定休息日'") + private Integer dateType; + + @Schema(description = "自定义日期时间段(JSON格式存储)") + @ExcelProperty("自定义日期时间段(JSON格式存储)") + private String customTimeSlots; + + @Schema(description = "指定日期(JSON格式存储)") + @ExcelProperty("指定日期(JSON格式存储)") + private String specificDates; + + @Schema(description = "收费方式0:''固定金额'':1:''基准价上浮") + private Integer chargeMode; + + @Schema(description = "价格或上浮百分比", example = "17305") + @ExcelProperty("价格或上浮百分比") + private BigDecimal price; + + @Schema(description = "是否启用该规则是否启用该规则0关1开", requiredMode = Schema.RequiredMode.REQUIRED) + @ExcelProperty("是否启用该规则是否启用该规则0关1开") + private Integer isEnabled; + + +} \ No newline at end of file diff --git a/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/vo/prodadditionalfeedates/ProdAdditionalFeeDatesSaveReqVO.java b/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/vo/prodadditionalfeedates/ProdAdditionalFeeDatesSaveReqVO.java new file mode 100644 index 0000000..7503ace --- /dev/null +++ b/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/vo/prodadditionalfeedates/ProdAdditionalFeeDatesSaveReqVO.java @@ -0,0 +1,48 @@ +package com.tashow.cloud.productapi.api.product.vo.prodadditionalfeedates; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +import java.math.BigDecimal; + +@Schema(description = "管理后台 - 特殊日期附加费用规则新增/修改 Request VO") +@Data +public class ProdAdditionalFeeDatesSaveReqVO { + + @Schema(description = "特殊日期规则的唯一标识符", requiredMode = Schema.RequiredMode.REQUIRED, example = "1445") + private Long id; + + @Schema(description = "商品id", requiredMode = Schema.RequiredMode.REQUIRED, example = "24324") + @NotNull(message = "商品id不能为空") + private Long prodId; + + @Schema(description = "名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "赵六") + @NotEmpty(message = "名称不能为空") + private String name; + + @Schema(description = "日期类型0:'自定义日期范围':1:'指定日期':2:'法定节假日',3:'固定休息日'", requiredMode = Schema.RequiredMode.REQUIRED, example = "2") + @NotNull(message = "日期类型0:'自定义日期范围':1:'指定日期':2:'法定节假日',3:'固定休息日'不能为空") + private Integer dateType; + + @Schema(description = "自定义日期时间段(JSON格式存储)") + private String customTimeSlots; + + @Schema(description = "指定日期(JSON格式存储)") + private String specificDates; + + @Schema(description = "收费方式0:''固定金额'':1:''基准价上浮", requiredMode = Schema.RequiredMode.REQUIRED) + @NotEmpty(message = "收费方式不能为空") + private Integer chargeMode; + + + @Schema(description = "价格或上浮百分比", example = "17305") + private BigDecimal price; + + @Schema(description = "是否启用该规则是否启用该规则0关1开", requiredMode = Schema.RequiredMode.REQUIRED) + @NotNull(message = "是否启用该规则不能为空") + private Integer isEnabled; + + +} \ No newline at end of file diff --git a/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/vo/prodadditionalfeeperiods/ProdAdditionalFeePeriodsPageReqVO.java b/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/vo/prodadditionalfeeperiods/ProdAdditionalFeePeriodsPageReqVO.java new file mode 100644 index 0000000..f941465 --- /dev/null +++ b/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/vo/prodadditionalfeeperiods/ProdAdditionalFeePeriodsPageReqVO.java @@ -0,0 +1,36 @@ +package com.tashow.cloud.productapi.api.product.vo.prodadditionalfeeperiods; + +import com.tashow.cloud.common.pojo.PageParam; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; + +import java.math.BigDecimal; + +@Schema(description = "管理后台 - 特殊时段附加费用规则分页 Request VO") +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class ProdAdditionalFeePeriodsPageReqVO extends PageParam { + + @Schema(description = "商品ID", example = "11100") + private Long prodId; + + @Schema(description = "名称", example = "张三") + private String name; + + @Schema(description = "特殊时段设置(JSON格式存储)") + private String specialTimeSlots; + + @Schema(description = "收费方式0:'固定金额',1:'基准价上浮'") + private Integer chargeMode; + + @Schema(description = "价格或上浮百分比", example = "20834") + private BigDecimal price; + + @Schema(description = "浮动百分比") + private BigDecimal floatingPercentage; + + +} \ No newline at end of file diff --git a/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/vo/prodadditionalfeeperiods/ProdAdditionalFeePeriodsRespVO.java b/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/vo/prodadditionalfeeperiods/ProdAdditionalFeePeriodsRespVO.java new file mode 100644 index 0000000..b479cce --- /dev/null +++ b/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/vo/prodadditionalfeeperiods/ProdAdditionalFeePeriodsRespVO.java @@ -0,0 +1,45 @@ +package com.tashow.cloud.productapi.api.product.vo.prodadditionalfeeperiods; + +import com.alibaba.excel.annotation.ExcelIgnoreUnannotated; +import com.alibaba.excel.annotation.ExcelProperty; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.math.BigDecimal; +import java.util.List; + +@Schema(description = "管理后台 - 特殊时段附加费用规则 Response VO") +@Data +@ExcelIgnoreUnannotated +public class ProdAdditionalFeePeriodsRespVO { + + @Schema(description = "特殊时段规则的唯一标识符", requiredMode = Schema.RequiredMode.REQUIRED, example = "24746") + @ExcelProperty("特殊时段规则的唯一标识符") + private Long id; + + @Schema(description = "商品ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "11100") + @ExcelProperty("商品ID") + private Long prodId; + + @Schema(description = "名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "张三") + @ExcelProperty("名称") + private String name; + + @Schema(description = "特殊时段设置(JSON格式存储)", requiredMode = Schema.RequiredMode.REQUIRED) + @ExcelProperty("特殊时段设置(JSON格式存储)") + private List specialTimeSlots; + + @Schema(description = "收费方式0:'固定金额',1:'基准价上浮'", requiredMode = Schema.RequiredMode.REQUIRED) + @ExcelProperty("收费方式0:'固定金额',1:'基准价上浮'") + private Integer chargeMode; + + @Schema(description = "价格或上浮百分比", example = "20834") + @ExcelProperty("价格或上浮百分比") + private BigDecimal price; + + @Schema(description = "浮动百分比") + @ExcelProperty("浮动百分比") + private BigDecimal floatingPercentage; + + +} \ No newline at end of file diff --git a/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/vo/prodadditionalfeeperiods/ProdAdditionalFeePeriodsSaveReqVO.java b/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/vo/prodadditionalfeeperiods/ProdAdditionalFeePeriodsSaveReqVO.java new file mode 100644 index 0000000..a304f90 --- /dev/null +++ b/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/vo/prodadditionalfeeperiods/ProdAdditionalFeePeriodsSaveReqVO.java @@ -0,0 +1,40 @@ +package com.tashow.cloud.productapi.api.product.vo.prodadditionalfeeperiods; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +import java.math.BigDecimal; +import java.util.List; + +@Schema(description = "管理后台 - 特殊时段附加费用规则新增/修改 Request VO") +@Data +public class ProdAdditionalFeePeriodsSaveReqVO { + + @Schema(description = "特殊时段规则的唯一标识符", requiredMode = Schema.RequiredMode.REQUIRED, example = "24746") + private Long id; + + @Schema(description = "商品ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "11100") + @NotNull(message = "商品ID不能为空") + private Long prodId; + + @Schema(description = "名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "张三") + @NotEmpty(message = "名称不能为空") + private String name; + + @Schema(description = "特殊时段设置(JSON格式存储)", requiredMode = Schema.RequiredMode.REQUIRED) + @NotEmpty(message = "特殊时段设置(JSON格式存储)不能为空") + private List specialTimeSlots; + + @Schema(description = "收费方式0:'固定金额',1:'基准价上浮'", requiredMode = Schema.RequiredMode.REQUIRED) + @NotNull(message = "收费方式0:'固定金额',1:'基准价上浮'不能为空") + private Integer chargeMode; + + @Schema(description = "价格或上浮百分比", example = "20834") + private BigDecimal price; + + @Schema(description = "浮动百分比") + private BigDecimal floatingPercentage; + +} \ No newline at end of file diff --git a/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/vo/prodemergencyresponse/ProdEmergencyInfoReqVO.java b/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/vo/prodemergencyresponse/ProdEmergencyInfoReqVO.java new file mode 100644 index 0000000..c6103fd --- /dev/null +++ b/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/vo/prodemergencyresponse/ProdEmergencyInfoReqVO.java @@ -0,0 +1,40 @@ +package com.tashow.cloud.productapi.api.product.vo.prodemergencyresponse; + +import com.alibaba.excel.annotation.ExcelIgnoreUnannotated; +import com.baomidou.mybatisplus.annotation.TableField; +import com.tashow.cloud.productapi.api.product.dto.ProdEmergencyResponseIntervalsDO; +import com.tashow.cloud.productapi.api.product.vo.prodadditionalfeedates.ProdAdditionalFeeBlackVO; +import com.tashow.cloud.productapi.general.StringListTypeHandler; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.util.List; +import java.util.stream.Collectors; + +@Schema(description = "管理后台 - 商品紧急响应服务设置 Response VO") +@Data +@ExcelIgnoreUnannotated +public class ProdEmergencyInfoReqVO { + + + /** + * 紧急响应服务配置的唯一标识符 + */ + private Long id; + /** + * 关联的商品ID + */ + private Long prodId; + /** + * 可响应时间段 + */ + @TableField(typeHandler = StringListTypeHandler.class) + private List responseTimeSlots; + + @Schema(description = "紧急响应时间区间设置") + public List prodEmergencyResponseIntervalsList; + + @Schema(description = "紧急响应黑名单日期设置") + public List prodEmergencyResponseBlackList; + +} \ No newline at end of file diff --git a/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/vo/prodemergencyresponse/ProdEmergencyInfoVO.java b/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/vo/prodemergencyresponse/ProdEmergencyInfoVO.java new file mode 100644 index 0000000..bdf4c6d --- /dev/null +++ b/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/vo/prodemergencyresponse/ProdEmergencyInfoVO.java @@ -0,0 +1,39 @@ +package com.tashow.cloud.productapi.api.product.vo.prodemergencyresponse; + +import com.alibaba.excel.annotation.ExcelIgnoreUnannotated; +import com.baomidou.mybatisplus.annotation.TableField; +import com.tashow.cloud.productapi.api.product.dto.ProdEmergencyResponseIntervalsDO; +import com.tashow.cloud.productapi.api.product.vo.prodadditionalfeedates.ProdAdditionalFeeBlackVO; +import com.tashow.cloud.productapi.general.StringListTypeHandler; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.util.List; +import java.util.stream.Collectors; + +@Schema(description = "管理后台 - 商品紧急响应服务设置 Response VO") +@Data +@ExcelIgnoreUnannotated +public class ProdEmergencyInfoVO { + + + /** + * 紧急响应服务配置的唯一标识符 + */ + private Long id; + /** + * 关联的商品ID + */ + private Long prodId; + /** + * 可响应时间段 + */ + @TableField(typeHandler = StringListTypeHandler.class) + private List responseTimeSlots; + + @Schema(description = "紧急响应时间区间设置") + public List prodEmergencyResponseIntervalsList; + + @Schema(description = "紧急响应黑名单日期设置") + public List prodEmergencyResponseBlackList; +} \ No newline at end of file diff --git a/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/vo/prodemergencyresponse/ProdEmergencyResponsePageReqVO.java b/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/vo/prodemergencyresponse/ProdEmergencyResponsePageReqVO.java new file mode 100644 index 0000000..fa65441 --- /dev/null +++ b/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/vo/prodemergencyresponse/ProdEmergencyResponsePageReqVO.java @@ -0,0 +1,42 @@ +package com.tashow.cloud.productapi.api.product.vo.prodemergencyresponse; + +import com.tashow.cloud.common.pojo.PageParam; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; + +import java.time.LocalDateTime; +import java.util.List; + +@Schema(description = "管理后台 - 商品紧急响应服务设置分页 Request VO") +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class ProdEmergencyResponsePageReqVO extends PageParam { + + @Schema(description = "关联的商品ID", example = "29152") + private Long prodId; + + @Schema(description = "可响应时间段") + private List responseTimeSlots; + + @Schema(description = "黑名自定义日期") + private List blacklistedDates; + + @Schema(description = "黑名单指定日期") + private List blackAppointDates; + + @Schema(description = "法定节假日是否开启0:关闭1开启") + private Integer blackHappy; + + @Schema(description = "固定休息日周末是否开启0关闭1开启") + private Integer blackWeekend; + + @Schema(description = "创建时间") + private LocalDateTime createdAt; + + @Schema(description = "更新时间") + private LocalDateTime updatedAt; + +} \ No newline at end of file diff --git a/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/vo/prodemergencyresponse/ProdEmergencyResponseRespVO.java b/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/vo/prodemergencyresponse/ProdEmergencyResponseRespVO.java new file mode 100644 index 0000000..8bfbab4 --- /dev/null +++ b/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/vo/prodemergencyresponse/ProdEmergencyResponseRespVO.java @@ -0,0 +1,51 @@ +package com.tashow.cloud.productapi.api.product.vo.prodemergencyresponse; + +import com.alibaba.excel.annotation.ExcelIgnoreUnannotated; +import com.alibaba.excel.annotation.ExcelProperty; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.time.LocalDateTime; + +@Schema(description = "管理后台 - 商品紧急响应服务设置 Response VO") +@Data +@ExcelIgnoreUnannotated +public class ProdEmergencyResponseRespVO { + + @Schema(description = "紧急响应服务配置的唯一标识符", requiredMode = Schema.RequiredMode.REQUIRED, example = "7448") + @ExcelProperty("紧急响应服务配置的唯一标识符") + private Long id; + + @Schema(description = "关联的商品ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "29152") + @ExcelProperty("关联的商品ID") + private Long prodId; + + @Schema(description = "可响应时间段(JSON格式存储)") + @ExcelProperty("可响应时间段(JSON格式存储)") + private String responseTimeSlots; + + @Schema(description = "黑名自定义日期(JSON格式存储)") + @ExcelProperty("黑名自定义日期(JSON格式存储)") + private String blacklistedDates; + + @Schema(description = "黑名单指定日期(JSON格式存储)") + @ExcelProperty("黑名单指定日期(JSON格式存储)") + private String blackAppointDates; + + @Schema(description = "法定节假日是否开启0:关闭1开启") + @ExcelProperty("法定节假日是否开启0:关闭1开启") + private Integer blackHappy; + + @Schema(description = "固定休息日周末是否开启0关闭1开启") + @ExcelProperty("固定休息日周末是否开启0关闭1开启") + private Integer blackWeekend; + + @Schema(description = "创建时间") + @ExcelProperty("创建时间") + private LocalDateTime createdAt; + + @Schema(description = "更新时间") + @ExcelProperty("更新时间") + private LocalDateTime updatedAt; + +} \ No newline at end of file diff --git a/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/vo/prodemergencyresponse/ProdEmergencyResponseSaveReqVO.java b/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/vo/prodemergencyresponse/ProdEmergencyResponseSaveReqVO.java new file mode 100644 index 0000000..8223536 --- /dev/null +++ b/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/vo/prodemergencyresponse/ProdEmergencyResponseSaveReqVO.java @@ -0,0 +1,44 @@ +package com.tashow.cloud.productapi.api.product.vo.prodemergencyresponse; + +import com.baomidou.mybatisplus.annotation.TableField; +import com.tashow.cloud.productapi.general.StringListTypeHandler; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +import java.time.LocalDateTime; +import java.util.List; + +@Schema(description = "管理后台 - 商品紧急响应服务设置新增/修改 Request VO") +@Data +public class ProdEmergencyResponseSaveReqVO { + + @Schema(description = "紧急响应服务配置的唯一标识符", requiredMode = Schema.RequiredMode.REQUIRED, example = "7448") + private Long id; + + @Schema(description = "关联的商品ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "29152") + @NotNull(message = "关联的商品ID不能为空") + private Long prodId; + + @Schema(description = "可响应时间段") + private List responseTimeSlots; + + @Schema(description = "黑名自定义日期") + private ListblacklistedDates; + + @Schema(description = "黑名单指定日期") + private List blackAppointDates; + + @Schema(description = "法定节假日是否开启0:关闭1开启") + private Integer blackHappy; + + @Schema(description = "固定休息日周末是否开启0关闭1开启") + private Integer blackWeekend; + + @Schema(description = "创建时间") + private LocalDateTime createdAt; + + @Schema(description = "更新时间") + private LocalDateTime updatedAt; + +} \ No newline at end of file diff --git a/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/vo/prodemergencyresponseintervals/ProdEmergencyResponseIntervalsPageReqVO.java b/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/vo/prodemergencyresponseintervals/ProdEmergencyResponseIntervalsPageReqVO.java new file mode 100644 index 0000000..765754e --- /dev/null +++ b/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/vo/prodemergencyresponseintervals/ProdEmergencyResponseIntervalsPageReqVO.java @@ -0,0 +1,42 @@ +package com.tashow.cloud.productapi.api.product.vo.prodemergencyresponseintervals; + +import com.tashow.cloud.common.pojo.PageParam; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; + +import java.math.BigDecimal; +import java.time.LocalDateTime; + +@Schema(description = "管理后台 - 紧急响应时间区间设置分页 Request VO") +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class ProdEmergencyResponseIntervalsPageReqVO extends PageParam { + + @Schema(description = "关联的紧急响应服务配置ID", example = "5737") + private Long configId; + + @Schema(description = "响应模式名称", example = "王五") + private String name; + + @Schema(description = "响应时间(小时)") + private BigDecimal responseHours; + + @Schema(description = "收费模式0:固定收费 1:浮动收费") + private Integer chargeMode; + + @Schema(description = "浮动百分比") + private BigDecimal floatingPercentage; + + @Schema(description = "价格或上浮百分比", example = "17810") + private BigDecimal price; + + @Schema(description = "创建时间") + private LocalDateTime createdAt; + + @Schema(description = "最后更新时间") + private LocalDateTime updatedAt; + +} \ No newline at end of file diff --git a/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/vo/prodemergencyresponseintervals/ProdEmergencyResponseIntervalsRespVO.java b/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/vo/prodemergencyresponseintervals/ProdEmergencyResponseIntervalsRespVO.java new file mode 100644 index 0000000..d2b89b9 --- /dev/null +++ b/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/vo/prodemergencyresponseintervals/ProdEmergencyResponseIntervalsRespVO.java @@ -0,0 +1,52 @@ +package com.tashow.cloud.productapi.api.product.vo.prodemergencyresponseintervals; + +import com.alibaba.excel.annotation.ExcelIgnoreUnannotated; +import com.alibaba.excel.annotation.ExcelProperty; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.math.BigDecimal; +import java.time.LocalDateTime; + +@Schema(description = "管理后台 - 紧急响应时间区间设置 Response VO") +@Data +@ExcelIgnoreUnannotated +public class ProdEmergencyResponseIntervalsRespVO { + + @Schema(description = "响应时间区间的唯一标识符", requiredMode = Schema.RequiredMode.REQUIRED, example = "29650") + @ExcelProperty("响应时间区间的唯一标识符") + private Long id; + + @Schema(description = "关联的紧急响应服务配置ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "5737") + @ExcelProperty("关联的紧急响应服务配置ID") + private Long configId; + + @Schema(description = "响应模式名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "王五") + @ExcelProperty("响应模式名称") + private String name; + + @Schema(description = "响应时间(小时)", requiredMode = Schema.RequiredMode.REQUIRED) + @ExcelProperty("响应时间(小时)") + private BigDecimal responseHours; + + @Schema(description = "收费模式0:固定收费 1:浮动收费", requiredMode = Schema.RequiredMode.REQUIRED) + @ExcelProperty("收费模式0:固定收费 1:浮动收费") + private Integer chargeMode; + + @Schema(description = "浮动百分比") + @ExcelProperty("浮动百分比") + private BigDecimal floatingPercentage; + + @Schema(description = "价格或上浮百分比", example = "17810") + @ExcelProperty("价格或上浮百分比") + private BigDecimal price; + + @Schema(description = "创建时间") + @ExcelProperty("创建时间") + private LocalDateTime createdAt; + + @Schema(description = "最后更新时间") + @ExcelProperty("最后更新时间") + private LocalDateTime updatedAt; + +} \ No newline at end of file diff --git a/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/vo/prodemergencyresponseintervals/ProdEmergencyResponseIntervalsSaveReqVO.java b/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/vo/prodemergencyresponseintervals/ProdEmergencyResponseIntervalsSaveReqVO.java new file mode 100644 index 0000000..28d9290 --- /dev/null +++ b/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/vo/prodemergencyresponseintervals/ProdEmergencyResponseIntervalsSaveReqVO.java @@ -0,0 +1,46 @@ +package com.tashow.cloud.productapi.api.product.vo.prodemergencyresponseintervals; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +import java.math.BigDecimal; +import java.time.LocalDateTime; + +@Schema(description = "管理后台 - 紧急响应时间区间设置新增/修改 Request VO") +@Data +public class ProdEmergencyResponseIntervalsSaveReqVO { + + @Schema(description = "响应时间区间的唯一标识符", requiredMode = Schema.RequiredMode.REQUIRED, example = "29650") + private Long id; + + @Schema(description = "关联的紧急响应服务配置ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "5737") + @NotNull(message = "关联的紧急响应服务配置ID不能为空") + private Long configId; + + @Schema(description = "响应模式名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "王五") + @NotEmpty(message = "响应模式名称不能为空") + private String name; + + @Schema(description = "响应时间(小时)", requiredMode = Schema.RequiredMode.REQUIRED) + @NotNull(message = "响应时间(小时)不能为空") + private BigDecimal responseHours; + + @Schema(description = "收费模式0:固定收费 1:浮动收费", requiredMode = Schema.RequiredMode.REQUIRED) + @NotNull(message = "收费模式0:固定收费 1:浮动收费不能为空") + private Integer chargeMode; + + @Schema(description = "浮动百分比") + private BigDecimal floatingPercentage; + + @Schema(description = "价格或上浮百分比", example = "17810") + private BigDecimal price; + + @Schema(description = "创建时间") + private LocalDateTime createdAt; + + @Schema(description = "最后更新时间") + private LocalDateTime updatedAt; + +} \ No newline at end of file diff --git a/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/vo/prodprop/ProdPropPageReqVO.java b/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/vo/prodprop/ProdPropPageReqVO.java new file mode 100644 index 0000000..1bb805d --- /dev/null +++ b/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/vo/prodprop/ProdPropPageReqVO.java @@ -0,0 +1,30 @@ +package com.tashow.cloud.productapi.api.product.vo.prodprop; + +import com.tashow.cloud.common.pojo.PageParam; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; + +@Schema(description = "管理后台 - 商品属性分页 Request VO") +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class ProdPropPageReqVO extends PageParam { + + @Schema(description = "属性名称", example = "李四") + private String propName; + + @Schema(description = "ProdPropRule 1:销售属性(规格); 2:参数属性;") + private Integer rule; + + @Schema(description = "店铺id", example = "2806") + private Long shopId; + + @Schema(description = "商品id", example = "21671") + private Integer prodId; + + @Schema(description = "是否删除0否1是") + private Integer deleted; + +} \ No newline at end of file diff --git a/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/vo/prodprop/ProdPropRespVO.java b/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/vo/prodprop/ProdPropRespVO.java new file mode 100644 index 0000000..0917922 --- /dev/null +++ b/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/vo/prodprop/ProdPropRespVO.java @@ -0,0 +1,37 @@ +package com.tashow.cloud.productapi.api.product.vo.prodprop; + +import com.alibaba.excel.annotation.ExcelIgnoreUnannotated; +import com.alibaba.excel.annotation.ExcelProperty; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +@Schema(description = "管理后台 - 商品属性 Response VO") +@Data +@ExcelIgnoreUnannotated +public class ProdPropRespVO { + + @Schema(description = "属性id", requiredMode = Schema.RequiredMode.REQUIRED, example = "14271") + @ExcelProperty("属性id") + private Long propId; + + @Schema(description = "属性名称", example = "李四") + @ExcelProperty("属性名称") + private String propName; + + @Schema(description = "ProdPropRule 1:销售属性(规格); 2:参数属性;") + @ExcelProperty("ProdPropRule 1:销售属性(规格); 2:参数属性;") + private Integer rule; + + @Schema(description = "店铺id", example = "2806") + @ExcelProperty("店铺id") + private Long shopId; + + @Schema(description = "商品id", example = "21671") + @ExcelProperty("商品id") + private Integer prodId; + + @Schema(description = "是否删除0否1是") + @ExcelProperty("是否删除0否1是") + private Integer deleted; + +} \ No newline at end of file diff --git a/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/vo/prodprop/ProdPropSaveReqVO.java b/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/vo/prodprop/ProdPropSaveReqVO.java new file mode 100644 index 0000000..43cc4e5 --- /dev/null +++ b/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/vo/prodprop/ProdPropSaveReqVO.java @@ -0,0 +1,45 @@ +package com.tashow.cloud.productapi.api.product.vo.prodprop; + +import com.baomidou.mybatisplus.annotation.TableField; +import com.tashow.cloud.productapi.api.product.dto.ProdPropValueDO; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotEmpty; +import lombok.Data; + +import java.util.List; + +@Schema(description = "管理后台 - 商品属性新增/修改 Request VO") +@Data +public class ProdPropSaveReqVO { + private Long id; + + @Schema(description = "属性id", requiredMode = Schema.RequiredMode.REQUIRED, example = "14271") + private Long propId; + + @Schema(description = "属性名称", example = "李四") + private String propName; + + @Schema(description = "ProdPropRule 1:销售属性(规格); 2:参数属性;") + private Integer rule; + + @Schema(description = "店铺id", example = "2806") + private Long shopId; + + @Schema(description = "商品id(添加的时候不传)", example = "21671") + private Integer prodId; + + @Schema(description = "是否删除0否1是") + private Integer deleted; + + /** + * isExist 是否新增 0否1是 + */ + @TableField(exist=false) + private Integer isExist; + /** + * 属性值 + */ + @TableField(exist=false) + @NotEmpty(message="规格属性值不能为空") + private List prodPropValues; +} \ No newline at end of file diff --git a/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/vo/prodpropvalue/ProPageReqVO.java b/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/vo/prodpropvalue/ProPageReqVO.java new file mode 100644 index 0000000..bdf0406 --- /dev/null +++ b/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/vo/prodpropvalue/ProPageReqVO.java @@ -0,0 +1,25 @@ +package com.tashow.cloud.productapi.api.product.vo.prodpropvalue; + +import com.tashow.cloud.common.pojo.PageParam; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; + +@Schema(description = "") +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class ProPageReqVO extends PageParam { + + /** + * 属性规格名称 + */ + private String propValue; + + /** + * 商品id + */ + private Long prodId; + +} \ No newline at end of file diff --git a/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/vo/prodpropvalue/ProPropRecycleBinVO.java b/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/vo/prodpropvalue/ProPropRecycleBinVO.java new file mode 100644 index 0000000..8ed8587 --- /dev/null +++ b/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/vo/prodpropvalue/ProPropRecycleBinVO.java @@ -0,0 +1,32 @@ +package com.tashow.cloud.productapi.api.product.vo.prodpropvalue; + +import com.fasterxml.jackson.annotation.JsonFormat; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.util.Date; + +@Data +public class ProPropRecycleBinVO { + @Schema(description = "规格值id") + private Long id; + + /** + * 属性规格名称 + */ + private String propValue; + /** + * 关联规格属性id + */ + private Long propId; + + /** + * 还剩多少天 + */ + private Long remainingDays; + /** + * 删除时间 + */ + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") + private Date deleteTime; +} diff --git a/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/vo/prodpropvalue/ProdPropValuePageReqVO.java b/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/vo/prodpropvalue/ProdPropValuePageReqVO.java new file mode 100644 index 0000000..f0a69b3 --- /dev/null +++ b/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/vo/prodpropvalue/ProdPropValuePageReqVO.java @@ -0,0 +1,24 @@ +package com.tashow.cloud.productapi.api.product.vo.prodpropvalue; + +import com.tashow.cloud.common.pojo.PageParam; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; + +@Schema(description = "管理后台 - 属性规则分页 Request VO") +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class ProdPropValuePageReqVO extends PageParam { + + @Schema(description = "属性值名称") + private String propValue; + + @Schema(description = "属性ID", example = "28282") + private Long propId; + + @Schema(description = "是否删除0否1是") + private Integer deleted; + +} \ No newline at end of file diff --git a/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/vo/prodpropvalue/ProdPropValueRespVO.java b/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/vo/prodpropvalue/ProdPropValueRespVO.java new file mode 100644 index 0000000..eba9eb2 --- /dev/null +++ b/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/vo/prodpropvalue/ProdPropValueRespVO.java @@ -0,0 +1,29 @@ +package com.tashow.cloud.productapi.api.product.vo.prodpropvalue; + +import com.alibaba.excel.annotation.ExcelIgnoreUnannotated; +import com.alibaba.excel.annotation.ExcelProperty; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +@Schema(description = "管理后台 - 属性规则 Response VO") +@Data +@ExcelIgnoreUnannotated +public class ProdPropValueRespVO { + + @Schema(description = "属性值ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "13799") + @ExcelProperty("属性值ID") + private Long valueId; + + @Schema(description = "属性值名称") + @ExcelProperty("属性值名称") + private String propValue; + + @Schema(description = "属性ID", example = "28282") + @ExcelProperty("属性ID") + private Long propId; + + @Schema(description = "是否删除0否1是") + @ExcelProperty("是否删除0否1是") + private Boolean deleted; + +} \ No newline at end of file diff --git a/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/vo/prodpropvalue/ProdPropValueSaveReqVO.java b/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/vo/prodpropvalue/ProdPropValueSaveReqVO.java new file mode 100644 index 0000000..6cefb94 --- /dev/null +++ b/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/vo/prodpropvalue/ProdPropValueSaveReqVO.java @@ -0,0 +1,22 @@ +package com.tashow.cloud.productapi.api.product.vo.prodpropvalue; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +@Schema(description = "管理后台 - 属性规则新增/修改 Request VO") +@Data +public class ProdPropValueSaveReqVO { + + @Schema(description = "属性值ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "13799") + private Long valueId; + + @Schema(description = "属性值名称") + private String propValue; + + @Schema(description = "属性ID", example = "28282") + private Long propId; + + @Schema(description = "是否删除0否1是") + private Boolean deleted; + +} \ No newline at end of file diff --git a/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/vo/prodreservationconfig/ProdReservationConfigPageReqVO.java b/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/vo/prodreservationconfig/ProdReservationConfigPageReqVO.java new file mode 100644 index 0000000..3344637 --- /dev/null +++ b/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/vo/prodreservationconfig/ProdReservationConfigPageReqVO.java @@ -0,0 +1,67 @@ +package com.tashow.cloud.productapi.api.product.vo.prodreservationconfig; + +import com.tashow.cloud.common.pojo.PageParam; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; + +import java.time.LocalDateTime; +import java.util.List; + +@Schema(description = "管理后台 - 商品预约配置分页 Request VO") +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class ProdReservationConfigPageReqVO extends PageParam { + + @Schema(description = "关联的商品ID", example = "19369") + private Long prodId; + + @Schema(description = "预约时段设置") + private List reservationTimeSlots; + + @Schema(description = "需提前多少小时预约") + private Integer advanceHours; + + @Schema(description = "预约日期范围 7天 10天 15天 30天") + private Integer reservationDateRange; + + @Schema(description = "是否允许更改预约时间 1可以 0不可以") + private Boolean allowChange; + + @Schema(description = "更改预约时间的时间规则(如服务开始前1小时可更改)") + private Integer changeTimeRule; + + @Schema(description = "允许更改预约时间的最大次数") + private Integer maxChangeTimes; + + @Schema(description = "黑名自定义日期") + private List blacklistedDates; + + /** + * '是否开启黑名单自定义0关1开' + */ + private Integer isBlacklisted; + + /** + * '是否开启黑名单指定日期0关1开' + */ + private Integer isBlackAppoint; + + @Schema(description = "黑名单指定日期") + private List blackAppointDates; + + @Schema(description = "法定节假日是否开启0:关闭1开启") + private Boolean blackHappy; + + @Schema(description = "固定休息日周末是否开启0关闭1开启") + private Boolean blackWeekend; + + @Schema(description = "配置创建时间") + private LocalDateTime createdAt; + + @Schema(description = "配置最后更新时间") + private LocalDateTime updatedAt; + +} \ No newline at end of file diff --git a/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/vo/prodreservationconfig/ProdReservationConfigRespVO.java b/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/vo/prodreservationconfig/ProdReservationConfigRespVO.java new file mode 100644 index 0000000..affc982 --- /dev/null +++ b/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/vo/prodreservationconfig/ProdReservationConfigRespVO.java @@ -0,0 +1,82 @@ +package com.tashow.cloud.productapi.api.product.vo.prodreservationconfig; + +import com.alibaba.excel.annotation.ExcelIgnoreUnannotated; +import com.alibaba.excel.annotation.ExcelProperty; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.time.LocalDateTime; +import java.util.List; + +@Schema(description = "管理后台 - 商品预约配置 Response VO") +@Data +@ExcelIgnoreUnannotated +public class ProdReservationConfigRespVO { + + @Schema(description = "预约配置的唯一标识符", requiredMode = Schema.RequiredMode.REQUIRED, example = "20606") + @ExcelProperty("预约配置的唯一标识符") + private Long id; + + @Schema(description = "关联的商品ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "19369") + @ExcelProperty("关联的商品ID") + private Long prodId; + + @Schema(description = "预约时段设置", requiredMode = Schema.RequiredMode.REQUIRED) + @ExcelProperty("预约时段设置") + private List reservationTimeSlots; + + @Schema(description = "需提前多少小时预约", requiredMode = Schema.RequiredMode.REQUIRED) + @ExcelProperty("需提前多少小时预约") + private Integer advanceHours; + + @Schema(description = "预约日期范围 7天 10天 15天 30天", requiredMode = Schema.RequiredMode.REQUIRED) + @ExcelProperty("预约日期范围 7天 10天 15天 30天") + private Integer reservationDateRange; + + @Schema(description = "是否允许更改预约时间 1可以 0不可以", requiredMode = Schema.RequiredMode.REQUIRED) + @ExcelProperty("是否允许更改预约时间 1可以 0不可以") + private Boolean allowChange; + + @Schema(description = "更改预约时间的时间规则(如服务开始前1小时可更改)") + @ExcelProperty("更改预约时间的时间规则(如服务开始前1小时可更改)") + private Integer changeTimeRule; + + @Schema(description = "允许更改预约时间的最大次数") + @ExcelProperty("允许更改预约时间的最大次数") + private Integer maxChangeTimes; + + @Schema(description = "黑名自定义日期") + @ExcelProperty("黑名自定义日期") + private List blacklistedDates; + + /** + * '是否开启黑名单自定义0关1开' + */ + private Integer isBlacklisted; + + /** + * '是否开启黑名单指定日期0关1开' + */ + private Integer isBlackAppoint; + + @Schema(description = "黑名单指定日期") + @ExcelProperty("黑名单指定日期") + private List blackAppointDates; + + @Schema(description = "法定节假日是否开启0:关闭1开启") + @ExcelProperty("法定节假日是否开启0:关闭1开启") + private Boolean blackHappy; + + @Schema(description = "固定休息日周末是否开启0关闭1开启") + @ExcelProperty("固定休息日周末是否开启0关闭1开启") + private Boolean blackWeekend; + + @Schema(description = "配置创建时间") + @ExcelProperty("配置创建时间") + private LocalDateTime createdAt; + + @Schema(description = "配置最后更新时间") + @ExcelProperty("配置最后更新时间") + private LocalDateTime updatedAt; + +} \ No newline at end of file diff --git a/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/vo/prodreservationconfig/ProdReservationConfigSaveReqVO.java b/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/vo/prodreservationconfig/ProdReservationConfigSaveReqVO.java new file mode 100644 index 0000000..6b4af4a --- /dev/null +++ b/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/vo/prodreservationconfig/ProdReservationConfigSaveReqVO.java @@ -0,0 +1,72 @@ +package com.tashow.cloud.productapi.api.product.vo.prodreservationconfig; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +import java.time.LocalDateTime; +import java.util.List; + +@Schema(description = "管理后台 - 商品预约配置新增/修改 Request VO") +@Data +public class ProdReservationConfigSaveReqVO { + + @Schema(description = "预约配置的唯一标识符", requiredMode = Schema.RequiredMode.REQUIRED, example = "20606") + private Long id; + + @Schema(description = "关联的商品ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "19369") + @NotNull(message = "关联的商品ID不能为空") + private Long prodId; + + @Schema(description = "预约时段设置", requiredMode = Schema.RequiredMode.REQUIRED) + @NotEmpty(message = "预约时段设置不能为空") + private List reservationTimeSlots; + + @Schema(description = "需提前多少小时预约", requiredMode = Schema.RequiredMode.REQUIRED) + @NotNull(message = "需提前多少小时预约不能为空") + private Integer advanceHours; + + @Schema(description = "预约日期范围 7天 10天 15天 30天", requiredMode = Schema.RequiredMode.REQUIRED) + @NotNull(message = "预约日期范围 7天 10天 15天 30天不能为空") + private Integer reservationDateRange; + + @Schema(description = "是否允许更改预约时间 1可以 0不可以", requiredMode = Schema.RequiredMode.REQUIRED) + @NotNull(message = "是否允许更改预约时间 1可以 0不可以不能为空") + private Boolean allowChange; + + @Schema(description = "更改预约时间的时间规则(如服务开始前1小时可更改)") + private Integer changeTimeRule; + + @Schema(description = "允许更改预约时间的最大次数") + private Integer maxChangeTimes; + + @Schema(description = "黑名自定义日期") + private List blacklistedDates; + + /** + * '是否开启黑名单自定义0关1开' + */ + private Integer isBlacklisted; + + /** + * '是否开启黑名单指定日期0关1开' + */ + private Integer isBlackAppoint; + + @Schema(description = "黑名单指定日期") + private List blackAppointDates; + + @Schema(description = "法定节假日是否开启0:关闭1开启") + private Boolean blackHappy; + + @Schema(description = "固定休息日周末是否开启0关闭1开启") + private Boolean blackWeekend; + + @Schema(description = "配置创建时间") + private LocalDateTime createdAt; + + @Schema(description = "配置最后更新时间") + private LocalDateTime updatedAt; + +} \ No newline at end of file diff --git a/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/vo/prodreservationconfig/ProdReservationInfoReqVO.java b/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/vo/prodreservationconfig/ProdReservationInfoReqVO.java new file mode 100644 index 0000000..e8b1088 --- /dev/null +++ b/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/vo/prodreservationconfig/ProdReservationInfoReqVO.java @@ -0,0 +1,84 @@ +package com.tashow.cloud.productapi.api.product.vo.prodreservationconfig; + +import com.alibaba.excel.annotation.ExcelIgnoreUnannotated; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.tashow.cloud.productapi.api.product.vo.prodadditionalfeedates.ProdAdditionalFeeBlackVO; +import com.tashow.cloud.productapi.general.StringListTypeHandler; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; + +@Schema(description = "管理后台 - 商品紧急响应服务设置 Response VO") +@Data +@ExcelIgnoreUnannotated +public class ProdReservationInfoReqVO { + + /** + * 预约配置的唯一标识符 + */ + @TableId + private Long id; + /** + * 关联的商品ID + */ + private Long prodId; + /** + * 预约时段设置 + */ + @TableField(typeHandler = StringListTypeHandler.class) + private List reservationTimeSlots; + /** + * 需提前多少小时预约 + */ + private Integer advanceHours; + /** + * 预约日期范围 7天 10天 15天 30天 + */ + private Integer reservationDateRange; + /** + * 是否允许更改预约时间 1可以 0不可以 + */ + private Integer allowChange; + + /** + * 时间段 + */ + private Integer timeSlot; + + /** + * 更改预约时间的时间规则(如服务开始前1小时可更改) + */ + private Integer changeTimeRule; + /** + * 允许更改预约时间的最大次数 + */ + private Integer maxChangeTimes; + + /** + * 预约时间区间设置 + */ + @TableField(exist=false) + private TimeBookVO timeBook; + + public TimeBookVO getTimeBook() { + if (this.timeBook == null) { + this.timeBook = new TimeBookVO(); + this.timeBook.setTimeSlot(this.timeSlot); + this.timeBook.setReservationTimeSlots(this.reservationTimeSlots); + } + return this.timeBook; + } + + public void setTimeBook(TimeBookVO timeBook) { + this.timeBook = timeBook; + } + + @Schema(description = "预约黑名单日期设置") + public List prodReservationBlackList = new ArrayList<>(); + +} \ No newline at end of file diff --git a/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/vo/prodreservationconfig/ProdReservationInfoVO.java b/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/vo/prodreservationconfig/ProdReservationInfoVO.java new file mode 100644 index 0000000..de8fd53 --- /dev/null +++ b/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/vo/prodreservationconfig/ProdReservationInfoVO.java @@ -0,0 +1,103 @@ +package com.tashow.cloud.productapi.api.product.vo.prodreservationconfig; + +import com.alibaba.excel.annotation.ExcelIgnoreUnannotated; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.tashow.cloud.productapi.api.product.dto.ProdEmergencyResponseIntervalsDO; +import com.tashow.cloud.productapi.api.product.vo.prodadditionalfeedates.ProdAdditionalFeeBlackVO; +import com.tashow.cloud.productapi.general.StringListTypeHandler; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; + +@Schema(description = "管理后台 - 商品紧急响应服务设置 Response VO") +@Data +@ExcelIgnoreUnannotated +public class ProdReservationInfoVO { + + /** + * 预约配置的唯一标识符 + */ + @TableId + private Long id; + /** + * 关联的商品ID + */ + private Long prodId; + /** + * 预约时段设置 + */ + @TableField(typeHandler = StringListTypeHandler.class) + private List reservationTimeSlots; + /** + * 需提前多少小时预约 + */ + private Integer advanceHours; + /** + * 预约日期范围 7天 10天 15天 30天 + */ + private Integer reservationDateRange; + /** + * 是否允许更改预约时间 1可以 0不可以 + */ + private Integer allowChange; + + /** + * 时间段 + */ + private Integer timeSlot; + + /** + * 更改预约时间的时间规则(如服务开始前1小时可更改) + */ + private Integer changeTimeRule; + /** + * 允许更改预约时间的最大次数 + */ + private Integer maxChangeTimes; + + /** + * 预约时间区间设置 + */ + @TableField(exist=false) + private TimeBookVO timeBook; + + public TimeBookVO getTimeBook() { + if (this.timeBook == null) { + this.timeBook = new TimeBookVO(); + this.timeBook.setTimeSlot(this.timeSlot); + this.timeBook.setReservationTimeSlots(this.reservationTimeSlots); + } + return this.timeBook; + } + + public void setTimeBook(TimeBookVO timeBook) { + this.timeBook = timeBook; + } + + @Schema(description = "预约黑名单日期设置") + public List prodReservationBlackList; + + +/* public List getProdReservationBlackList() { + if (prodReservationBlackList == null || prodReservationBlackList.isEmpty()) { + return prodReservationBlackList; + } + return prodReservationBlackList.stream() + .filter(black -> black != null && !black.isEmpty()) + .collect(Collectors.toList()); + } + + public void setProdReservationBlackList(List prodReservationBlackList) { + this.prodReservationBlackList = prodReservationBlackList; + }*/ + + + + + +} \ No newline at end of file diff --git a/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/vo/prodreservationconfig/TimeBookVO.java b/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/vo/prodreservationconfig/TimeBookVO.java new file mode 100644 index 0000000..a33c770 --- /dev/null +++ b/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/vo/prodreservationconfig/TimeBookVO.java @@ -0,0 +1,23 @@ +package com.tashow.cloud.productapi.api.product.vo.prodreservationconfig; + +import com.alibaba.excel.annotation.ExcelIgnoreUnannotated; +import com.alibaba.excel.annotation.ExcelProperty; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.util.List; + +@Schema(description = "管理后台 - 商品预约配置 Response VO") +@Data +@ExcelIgnoreUnannotated +public class TimeBookVO { + + /** + * 预约时段设置 + */ + private List reservationTimeSlots; + /** + * 时间段 + */ + private Integer timeSlot; +} \ No newline at end of file diff --git a/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/vo/prodservicearearelevance/ProdServiceAreaRelevancePageReqVO.java b/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/vo/prodservicearearelevance/ProdServiceAreaRelevancePageReqVO.java new file mode 100644 index 0000000..7eb3f03 --- /dev/null +++ b/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/vo/prodservicearearelevance/ProdServiceAreaRelevancePageReqVO.java @@ -0,0 +1,15 @@ +package com.tashow.cloud.productapi.api.product.vo.prodservicearearelevance; + +import com.tashow.cloud.common.pojo.PageParam; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; + +@Schema(description = "管理后台 - 商品与服务区域关联分页 Request VO") +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class ProdServiceAreaRelevancePageReqVO extends PageParam { + +} \ No newline at end of file diff --git a/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/vo/prodservicearearelevance/ProdServiceAreaRelevanceRespVO.java b/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/vo/prodservicearearelevance/ProdServiceAreaRelevanceRespVO.java new file mode 100644 index 0000000..001a867 --- /dev/null +++ b/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/vo/prodservicearearelevance/ProdServiceAreaRelevanceRespVO.java @@ -0,0 +1,21 @@ +package com.tashow.cloud.productapi.api.product.vo.prodservicearearelevance; + +import com.alibaba.excel.annotation.ExcelIgnoreUnannotated; +import com.alibaba.excel.annotation.ExcelProperty; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +@Schema(description = "管理后台 - 商品与服务区域关联 Response VO") +@Data +@ExcelIgnoreUnannotated +public class ProdServiceAreaRelevanceRespVO { + + @Schema(description = "关联的商品ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "23588") + @ExcelProperty("关联的商品ID") + private Long prodId; + + @Schema(description = "关联的服务区域ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "28293") + @ExcelProperty("关联的服务区域ID") + private Long areaId; + +} \ No newline at end of file diff --git a/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/vo/prodservicearearelevance/ProdServiceAreaRelevanceSaveReqVO.java b/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/vo/prodservicearearelevance/ProdServiceAreaRelevanceSaveReqVO.java new file mode 100644 index 0000000..d3d8da6 --- /dev/null +++ b/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/vo/prodservicearearelevance/ProdServiceAreaRelevanceSaveReqVO.java @@ -0,0 +1,16 @@ +package com.tashow.cloud.productapi.api.product.vo.prodservicearearelevance; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +@Schema(description = "管理后台 - 商品与服务区域关联新增/修改 Request VO") +@Data +public class ProdServiceAreaRelevanceSaveReqVO { + + @Schema(description = "关联的商品ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "23588") + private Long prodId; + + @Schema(description = "关联的服务区域ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "28293") + private Long areaId; + +} \ No newline at end of file diff --git a/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/vo/prodserviceareas/ProdServiceAreasInfoVO.java b/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/vo/prodserviceareas/ProdServiceAreasInfoVO.java new file mode 100644 index 0000000..b2089ae --- /dev/null +++ b/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/vo/prodserviceareas/ProdServiceAreasInfoVO.java @@ -0,0 +1,36 @@ +package com.tashow.cloud.productapi.api.product.vo.prodserviceareas; + +import com.alibaba.excel.annotation.ExcelIgnoreUnannotated; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.math.BigDecimal; +import java.util.List; + +@Schema(description = "管理后台 - 服务区域 Response VO") +@Data +@ExcelIgnoreUnannotated +public class ProdServiceAreasInfoVO { + + /** + * 超区规则的唯一标识符 + */ + private Long id; + /** + * 关联的商品ID + */ + private Long prodId; + /** + * 超区规则类型(0:拒单、2:接单并收取超区费、3:接单并免超区费) + */ + private Integer ruleType; + /** + * 超区费用(仅在rule_type为accept_with_fee时有效) + */ + private BigDecimal fee; + + + @Schema(description = "服务区域地址名称") + private List areaNameList; + +} \ No newline at end of file diff --git a/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/vo/prodserviceareas/ProdServiceAreasPageReqVO.java b/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/vo/prodserviceareas/ProdServiceAreasPageReqVO.java new file mode 100644 index 0000000..691a166 --- /dev/null +++ b/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/vo/prodserviceareas/ProdServiceAreasPageReqVO.java @@ -0,0 +1,27 @@ +package com.tashow.cloud.productapi.api.product.vo.prodserviceareas; + +import com.tashow.cloud.common.pojo.PageParam; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; +import org.springframework.format.annotation.DateTimeFormat; + +import java.time.LocalDateTime; + +import static com.tashow.cloud.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND; + +@Schema(description = "管理后台 - 服务区域分页 Request VO") +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class ProdServiceAreasPageReqVO extends PageParam { + + @Schema(description = "服务区域名称(如台江区、鼓楼区)", example = "芋艿") + private String areaName; + + @Schema(description = "区域创建时间") + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) + private LocalDateTime[] createTime; + +} \ No newline at end of file diff --git a/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/vo/prodserviceareas/ProdServiceAreasRespVO.java b/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/vo/prodserviceareas/ProdServiceAreasRespVO.java new file mode 100644 index 0000000..3cbbd92 --- /dev/null +++ b/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/vo/prodserviceareas/ProdServiceAreasRespVO.java @@ -0,0 +1,27 @@ +package com.tashow.cloud.productapi.api.product.vo.prodserviceareas; + +import com.alibaba.excel.annotation.ExcelIgnoreUnannotated; +import com.alibaba.excel.annotation.ExcelProperty; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.time.LocalDateTime; + +@Schema(description = "管理后台 - 服务区域 Response VO") +@Data +@ExcelIgnoreUnannotated +public class ProdServiceAreasRespVO { + + @Schema(description = "服务区域的唯一标识符", requiredMode = Schema.RequiredMode.REQUIRED, example = "26474") + @ExcelProperty("服务区域的唯一标识符") + private Long id; + + @Schema(description = "服务区域名称(如台江区、鼓楼区)", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋艿") + @ExcelProperty("服务区域名称(如台江区、鼓楼区)") + private String areaName; + + @Schema(description = "区域创建时间") + @ExcelProperty("区域创建时间") + private LocalDateTime createTime; + +} \ No newline at end of file diff --git a/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/vo/prodserviceareas/ProdServiceAreasSaveReqVO.java b/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/vo/prodserviceareas/ProdServiceAreasSaveReqVO.java new file mode 100644 index 0000000..a78387b --- /dev/null +++ b/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/vo/prodserviceareas/ProdServiceAreasSaveReqVO.java @@ -0,0 +1,18 @@ +package com.tashow.cloud.productapi.api.product.vo.prodserviceareas; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotEmpty; +import lombok.Data; + +@Schema(description = "管理后台 - 服务区域新增/修改 Request VO") +@Data +public class ProdServiceAreasSaveReqVO { + + @Schema(description = "服务区域的唯一标识符", requiredMode = Schema.RequiredMode.REQUIRED, example = "26474") + private Long id; + + @Schema(description = "服务区域名称(如台江区、鼓楼区)", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋艿") + @NotEmpty(message = "服务区域名称(如台江区、鼓楼区)不能为空") + private String areaName; + +} \ No newline at end of file diff --git a/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/vo/prodserviceoverarearules/ProdServiceOverAreaRulesPageReqVO.java b/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/vo/prodserviceoverarearules/ProdServiceOverAreaRulesPageReqVO.java new file mode 100644 index 0000000..df89b7d --- /dev/null +++ b/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/vo/prodserviceoverarearules/ProdServiceOverAreaRulesPageReqVO.java @@ -0,0 +1,33 @@ +package com.tashow.cloud.productapi.api.product.vo.prodserviceoverarearules; + +import com.tashow.cloud.common.pojo.PageParam; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; + +import java.math.BigDecimal; +import java.time.LocalDateTime; + +@Schema(description = "管理后台 - 超区规则分页 Request VO") +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class ProdServiceOverAreaRulesPageReqVO extends PageParam { + + @Schema(description = "关联的商品ID", example = "20133") + private Long prodId; + + @Schema(description = "超区规则类型(0:拒单、2:接单并收取超区费、3:接单并免超区费)", example = "1") + private Boolean ruleType; + + @Schema(description = "超区费用(仅在rule_type为accept_with_fee时有效)") + private BigDecimal fee; + + @Schema(description = "规则创建时间") + private LocalDateTime createdAt; + + @Schema(description = "规则最后更新时间") + private LocalDateTime updatedAt; + +} \ No newline at end of file diff --git a/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/vo/prodserviceoverarearules/ProdServiceOverAreaRulesRespVO.java b/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/vo/prodserviceoverarearules/ProdServiceOverAreaRulesRespVO.java new file mode 100644 index 0000000..051e8bc --- /dev/null +++ b/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/vo/prodserviceoverarearules/ProdServiceOverAreaRulesRespVO.java @@ -0,0 +1,40 @@ +package com.tashow.cloud.productapi.api.product.vo.prodserviceoverarearules; + +import com.alibaba.excel.annotation.ExcelIgnoreUnannotated; +import com.alibaba.excel.annotation.ExcelProperty; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.math.BigDecimal; +import java.time.LocalDateTime; + +@Schema(description = "管理后台 - 超区规则 Response VO") +@Data +@ExcelIgnoreUnannotated +public class ProdServiceOverAreaRulesRespVO { + + @Schema(description = "超区规则的唯一标识符", requiredMode = Schema.RequiredMode.REQUIRED, example = "32421") + @ExcelProperty("超区规则的唯一标识符") + private Long id; + + @Schema(description = "关联的商品ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "20133") + @ExcelProperty("关联的商品ID") + private Long prodId; + + @Schema(description = "超区规则类型(0:拒单、2:接单并收取超区费、3:接单并免超区费)", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + @ExcelProperty("超区规则类型(0:拒单、2:接单并收取超区费、3:接单并免超区费)") + private Boolean ruleType; + + @Schema(description = "超区费用(仅在rule_type为accept_with_fee时有效)") + @ExcelProperty("超区费用(仅在rule_type为accept_with_fee时有效)") + private BigDecimal fee; + + @Schema(description = "规则创建时间") + @ExcelProperty("规则创建时间") + private LocalDateTime createdAt; + + @Schema(description = "规则最后更新时间") + @ExcelProperty("规则最后更新时间") + private LocalDateTime updatedAt; + +} \ No newline at end of file diff --git a/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/vo/prodserviceoverarearules/ProdServiceOverAreaRulesSaveReqVO.java b/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/vo/prodserviceoverarearules/ProdServiceOverAreaRulesSaveReqVO.java new file mode 100644 index 0000000..2a27e6f --- /dev/null +++ b/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/vo/prodserviceoverarearules/ProdServiceOverAreaRulesSaveReqVO.java @@ -0,0 +1,34 @@ +package com.tashow.cloud.productapi.api.product.vo.prodserviceoverarearules; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +import java.math.BigDecimal; +import java.time.LocalDateTime; + +@Schema(description = "管理后台 - 超区规则新增/修改 Request VO") +@Data +public class ProdServiceOverAreaRulesSaveReqVO { + + @Schema(description = "超区规则的唯一标识符", requiredMode = Schema.RequiredMode.REQUIRED, example = "32421") + private Long id; + + @Schema(description = "关联的商品ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "20133") + @NotNull(message = "关联的商品ID不能为空") + private Long prodId; + + @Schema(description = "超区规则类型(0:拒单、2:接单并收取超区费、3:接单并免超区费)", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + @NotNull(message = "超区规则类型(0:拒单、2:接单并收取超区费、3:接单并免超区费)不能为空") + private Boolean ruleType; + + @Schema(description = "超区费用(仅在rule_type为accept_with_fee时有效)") + private BigDecimal fee; + + @Schema(description = "规则创建时间") + private LocalDateTime createdAt; + + @Schema(description = "规则最后更新时间") + private LocalDateTime updatedAt; + +} \ No newline at end of file diff --git a/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/vo/prodtags/ProdTagsPageReqVO.java b/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/vo/prodtags/ProdTagsPageReqVO.java new file mode 100644 index 0000000..7481290 --- /dev/null +++ b/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/vo/prodtags/ProdTagsPageReqVO.java @@ -0,0 +1,24 @@ +package com.tashow.cloud.productapi.api.product.vo.prodtags; + +import com.tashow.cloud.common.pojo.PageParam; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; +import org.springframework.format.annotation.DateTimeFormat; + +import java.time.LocalDateTime; + +import static com.tashow.cloud.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND; + +@Schema(description = "管理后台 - 商品和标签管理分页 Request VO") +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class ProdTagsPageReqVO extends PageParam { + + @Schema(description = "创建时间") + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) + private LocalDateTime[] createTime; + +} \ No newline at end of file diff --git a/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/vo/prodtags/ProdTagsRespVO.java b/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/vo/prodtags/ProdTagsRespVO.java new file mode 100644 index 0000000..4bf33d3 --- /dev/null +++ b/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/vo/prodtags/ProdTagsRespVO.java @@ -0,0 +1,27 @@ +package com.tashow.cloud.productapi.api.product.vo.prodtags; + +import com.alibaba.excel.annotation.ExcelIgnoreUnannotated; +import com.alibaba.excel.annotation.ExcelProperty; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.time.LocalDateTime; + +@Schema(description = "管理后台 - 商品和标签管理 Response VO") +@Data +@ExcelIgnoreUnannotated +public class ProdTagsRespVO { + + @Schema(description = "商品id", requiredMode = Schema.RequiredMode.REQUIRED, example = "8574") + @ExcelProperty("商品id") + private Long productId; + + @Schema(description = "标签id", requiredMode = Schema.RequiredMode.REQUIRED, example = "24317") + @ExcelProperty("标签id") + private Long tagId; + + @Schema(description = "创建时间") + @ExcelProperty("创建时间") + private LocalDateTime createTime; + +} \ No newline at end of file diff --git a/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/vo/prodtags/ProdTagsSaveReqVO.java b/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/vo/prodtags/ProdTagsSaveReqVO.java new file mode 100644 index 0000000..235fcb5 --- /dev/null +++ b/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/vo/prodtags/ProdTagsSaveReqVO.java @@ -0,0 +1,16 @@ +package com.tashow.cloud.productapi.api.product.vo.prodtags; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +@Schema(description = "管理后台 - 商品和标签管理新增/修改 Request VO") +@Data +public class ProdTagsSaveReqVO { + + @Schema(description = "商品id", requiredMode = Schema.RequiredMode.REQUIRED, example = "8574") + private Long productId; + + @Schema(description = "标签id", requiredMode = Schema.RequiredMode.REQUIRED, example = "24317") + private Long tagId; + +} \ No newline at end of file diff --git a/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/vo/productorderlimit/ProductOrderLimitPageReqVO.java b/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/vo/productorderlimit/ProductOrderLimitPageReqVO.java new file mode 100644 index 0000000..e9133f0 --- /dev/null +++ b/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/vo/productorderlimit/ProductOrderLimitPageReqVO.java @@ -0,0 +1,32 @@ +package com.tashow.cloud.productapi.api.product.vo.productorderlimit; + +import com.tashow.cloud.common.pojo.PageParam; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; + +import java.time.LocalDateTime; + +@Schema(description = "管理后台 - 商品接单上限设置分页 Request VO") +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class ProductOrderLimitPageReqVO extends PageParam { + + @Schema(description = "关联的商品ID", example = "10171") + private Long prodId; + + @Schema(description = "限制单位'0:按自然天',1:'按自然周',2:'按自然月'") + private Boolean limitUnit; + + @Schema(description = "上限阈值") + private Integer maxOrders; + + @Schema(description = "配置创建时间") + private LocalDateTime createdAt; + + @Schema(description = "配置最后更新时间") + private LocalDateTime updatedAt; + +} \ No newline at end of file diff --git a/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/vo/productorderlimit/ProductOrderLimitRespVO.java b/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/vo/productorderlimit/ProductOrderLimitRespVO.java new file mode 100644 index 0000000..4cada44 --- /dev/null +++ b/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/vo/productorderlimit/ProductOrderLimitRespVO.java @@ -0,0 +1,39 @@ +package com.tashow.cloud.productapi.api.product.vo.productorderlimit; + +import com.alibaba.excel.annotation.ExcelIgnoreUnannotated; +import com.alibaba.excel.annotation.ExcelProperty; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.time.LocalDateTime; + +@Schema(description = "管理后台 - 商品接单上限设置 Response VO") +@Data +@ExcelIgnoreUnannotated +public class ProductOrderLimitRespVO { + + @Schema(description = "接单上限配置的唯一标识符", requiredMode = Schema.RequiredMode.REQUIRED, example = "10334") + @ExcelProperty("接单上限配置的唯一标识符") + private Long id; + + @Schema(description = "关联的商品ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "10171") + @ExcelProperty("关联的商品ID") + private Long prodId; + + @Schema(description = "限制单位'0:按自然天',1:'按自然周',2:'按自然月'", requiredMode = Schema.RequiredMode.REQUIRED) + @ExcelProperty("限制单位'0:按自然天',1:'按自然周',2:'按自然月'") + private Boolean limitUnit; + + @Schema(description = "上限阈值", requiredMode = Schema.RequiredMode.REQUIRED) + @ExcelProperty("上限阈值") + private Integer maxOrders; + + @Schema(description = "配置创建时间") + @ExcelProperty("配置创建时间") + private LocalDateTime createdAt; + + @Schema(description = "配置最后更新时间") + @ExcelProperty("配置最后更新时间") + private LocalDateTime updatedAt; + +} \ No newline at end of file diff --git a/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/vo/productorderlimit/ProductOrderLimitSaveReqVO.java b/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/vo/productorderlimit/ProductOrderLimitSaveReqVO.java new file mode 100644 index 0000000..1518f1b --- /dev/null +++ b/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/vo/productorderlimit/ProductOrderLimitSaveReqVO.java @@ -0,0 +1,34 @@ +package com.tashow.cloud.productapi.api.product.vo.productorderlimit; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +import java.time.LocalDateTime; + +@Schema(description = "管理后台 - 商品接单上限设置新增/修改 Request VO") +@Data +public class ProductOrderLimitSaveReqVO { + + @Schema(description = "接单上限配置的唯一标识符", requiredMode = Schema.RequiredMode.REQUIRED, example = "10334") + private Long id; + + @Schema(description = "关联的商品ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "10171") + @NotNull(message = "关联的商品ID不能为空") + private Long prodId; + + @Schema(description = "限制单位'0:按自然天',1:'按自然周',2:'按自然月'", requiredMode = Schema.RequiredMode.REQUIRED) + @NotNull(message = "限制单位'0:按自然天',1:'按自然周',2:'按自然月'不能为空") + private Boolean limitUnit; + + @Schema(description = "上限阈值", requiredMode = Schema.RequiredMode.REQUIRED) + @NotNull(message = "上限阈值不能为空") + private Integer maxOrders; + + @Schema(description = "配置创建时间") + private LocalDateTime createdAt; + + @Schema(description = "配置最后更新时间") + private LocalDateTime updatedAt; + +} \ No newline at end of file diff --git a/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/vo/prodweightrangeprices/ProdWeightRangePricesPageReqVO.java b/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/vo/prodweightrangeprices/ProdWeightRangePricesPageReqVO.java new file mode 100644 index 0000000..3fb0f4e --- /dev/null +++ b/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/vo/prodweightrangeprices/ProdWeightRangePricesPageReqVO.java @@ -0,0 +1,29 @@ +package com.tashow.cloud.productapi.api.product.vo.prodweightrangeprices; + +import com.tashow.cloud.common.pojo.PageParam; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; + +import java.math.BigDecimal; + +@Schema(description = "管理后台 - 体重区间价格分页 Request VO") +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class ProdWeightRangePricesPageReqVO extends PageParam { + + @Schema(description = "关联的体重配置ID", example = "6754") + private Long prodId; + + @Schema(description = "体重区间") + private String weightRange; + + @Schema(description = "价格", example = "11575") + private BigDecimal price; + + @Schema(description = "是否启用该规则0否1是") + private Integer isEnabled; + +} \ No newline at end of file diff --git a/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/vo/prodweightrangeprices/ProdWeightRangePricesRespVO.java b/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/vo/prodweightrangeprices/ProdWeightRangePricesRespVO.java new file mode 100644 index 0000000..9661f5b --- /dev/null +++ b/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/vo/prodweightrangeprices/ProdWeightRangePricesRespVO.java @@ -0,0 +1,36 @@ +package com.tashow.cloud.productapi.api.product.vo.prodweightrangeprices; + +import com.alibaba.excel.annotation.ExcelIgnoreUnannotated; +import com.alibaba.excel.annotation.ExcelProperty; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.math.BigDecimal; + +@Schema(description = "管理后台 - 体重区间价格 Response VO") +@Data +@ExcelIgnoreUnannotated +public class ProdWeightRangePricesRespVO { + + @Schema(description = "体重区间价格的唯一标识符", requiredMode = Schema.RequiredMode.REQUIRED, example = "26157") + @ExcelProperty("体重区间价格的唯一标识符") + private Long id; + + @Schema(description = "关联的体重配置ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "6754") + @ExcelProperty("关联的体重配置ID") + private Long prodId; + + @Schema(description = "体重区间", requiredMode = Schema.RequiredMode.REQUIRED) + @ExcelProperty("体重区间") + private String weightRange; + + @Schema(description = "价格", requiredMode = Schema.RequiredMode.REQUIRED, example = "11575") + @ExcelProperty("价格") + private BigDecimal price; + + @Schema(description = "是否启用该规则0否1是", requiredMode = Schema.RequiredMode.REQUIRED) + @ExcelProperty("是否启用该规则0否1是") + private Integer isEnabled; + + +} \ No newline at end of file diff --git a/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/vo/prodweightrangeprices/ProdWeightRangePricesSaveInfoVO.java b/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/vo/prodweightrangeprices/ProdWeightRangePricesSaveInfoVO.java new file mode 100644 index 0000000..5c6766e --- /dev/null +++ b/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/vo/prodweightrangeprices/ProdWeightRangePricesSaveInfoVO.java @@ -0,0 +1,23 @@ +package com.tashow.cloud.productapi.api.product.vo.prodweightrangeprices; + +import com.tashow.cloud.productapi.api.product.dto.ProdWeightRangePricesDO; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +import java.math.BigDecimal; +import java.util.List; + +@Schema(description = "管理后台 - 体重区间价格新增/修改 Request VO") +@Data +public class ProdWeightRangePricesSaveInfoVO { + + @Schema(description = "体重是否收费0否1是") + private Integer isWeightCharge; + + @Schema(description = "体重配置") + public List prodWeightConfigList; + + +} \ No newline at end of file diff --git a/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/vo/prodweightrangeprices/ProdWeightRangePricesSaveReqVO.java b/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/vo/prodweightrangeprices/ProdWeightRangePricesSaveReqVO.java new file mode 100644 index 0000000..52e8bd6 --- /dev/null +++ b/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/vo/prodweightrangeprices/ProdWeightRangePricesSaveReqVO.java @@ -0,0 +1,34 @@ +package com.tashow.cloud.productapi.api.product.vo.prodweightrangeprices; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +import java.math.BigDecimal; + +@Schema(description = "管理后台 - 体重区间价格新增/修改 Request VO") +@Data +public class ProdWeightRangePricesSaveReqVO { + + @Schema(description = "体重区间价格的唯一标识符", requiredMode = Schema.RequiredMode.REQUIRED, example = "26157") + private Long id; + + @Schema(description = "关联的体重配置ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "6754") + @NotNull(message = "关联的体重配置ID不能为空") + private Long prodId; + + @Schema(description = "体重区间", requiredMode = Schema.RequiredMode.REQUIRED) + @NotEmpty(message = "体重区间不能为空") + private String weightRange; + + @Schema(description = "价格", requiredMode = Schema.RequiredMode.REQUIRED, example = "11575") + @NotNull(message = "价格不能为空") + private BigDecimal price; + + @Schema(description = "是否启用该规则0否1是", requiredMode = Schema.RequiredMode.REQUIRED) + @NotNull(message = "是否启用该规则0否1是不能为空") + private Integer isEnabled; + + +} \ No newline at end of file diff --git a/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/vo/shopdetail/ShopDetailPageReqVO.java b/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/vo/shopdetail/ShopDetailPageReqVO.java new file mode 100644 index 0000000..4f7d3ec --- /dev/null +++ b/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/vo/shopdetail/ShopDetailPageReqVO.java @@ -0,0 +1,98 @@ +package com.tashow.cloud.productapi.api.product.vo.shopdetail; + +import com.tashow.cloud.common.pojo.PageParam; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; +import org.springframework.format.annotation.DateTimeFormat; + +import java.math.BigDecimal; +import java.time.LocalDateTime; + +import static com.tashow.cloud.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND; + +@Schema(description = "管理后台 - 店铺信息分页 Request VO") +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class ShopDetailPageReqVO extends PageParam { + + @Schema(description = "店铺名称(数字、中文,英文(可混合,不可有特殊字符),可修改)、不唯一", example = "芋艿") + private String shopName; + + @Schema(description = "店长用户id", example = "15607") + private String userId; + + @Schema(description = "店铺类型", example = "2") + private Integer shopType; + + @Schema(description = "店铺简介(可修改)") + private String intro; + + @Schema(description = "店铺公告(可修改)") + private String shopNotice; + + @Schema(description = "店铺行业(餐饮、生鲜果蔬、鲜花等)") + private Integer shopIndustry; + + @Schema(description = "店长") + private String shopOwner; + + @Schema(description = "店铺绑定的手机(登录账号:唯一)") + private String mobile; + + @Schema(description = "店铺联系电话") + private String tel; + + @Schema(description = "店铺所在纬度(可修改)") + private String shopLat; + + @Schema(description = "店铺所在经度(可修改)") + private String shopLng; + + @Schema(description = "店铺详细地址") + private String shopAddress; + + @Schema(description = "店铺所在省份(描述)") + private String province; + + @Schema(description = "店铺所在城市(描述)") + private String city; + + @Schema(description = "店铺所在区域(描述)") + private String area; + + @Schema(description = "店铺省市区代码,用于回显") + private String pcaCode; + + @Schema(description = "店铺logo(可修改)") + private String shopLogo; + + @Schema(description = "店铺相册") + private String shopPhotos; + + @Schema(description = "每天营业时间段(可修改)") + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) + private String[] openTime; + + @Schema(description = "店铺状态(-1:未开通 0: 停业中 1:营业中),可修改", example = "2") + private Integer shopStatus; + + @Schema(description = "0:商家承担运费; 1:买家承担运费", example = "2") + private Integer transportType; + + @Schema(description = "固定运费") + private BigDecimal fixedFreight; + + @Schema(description = "满X包邮") + private BigDecimal fullFreeShipping; + + @Schema(description = "创建时间") + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) + private LocalDateTime[] createTime; + + @Schema(description = "分销开关(0:开启 1:关闭)") + private Integer isDistribution; + +} \ No newline at end of file diff --git a/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/vo/shopdetail/ShopDetailRespVO.java b/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/vo/shopdetail/ShopDetailRespVO.java new file mode 100644 index 0000000..e2f0804 --- /dev/null +++ b/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/vo/shopdetail/ShopDetailRespVO.java @@ -0,0 +1,120 @@ +package com.tashow.cloud.productapi.api.product.vo.shopdetail; + +import com.alibaba.excel.annotation.ExcelIgnoreUnannotated; +import com.alibaba.excel.annotation.ExcelProperty; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.math.BigDecimal; +import java.time.LocalDateTime; + +@Schema(description = "管理后台 - 店铺信息 Response VO") +@Data +@ExcelIgnoreUnannotated +public class ShopDetailRespVO { + + @Schema(description = "店铺id", requiredMode = Schema.RequiredMode.REQUIRED, example = "5101") + @ExcelProperty("店铺id") + private Long shopId; + + @Schema(description = "店铺名称(数字、中文,英文(可混合,不可有特殊字符),可修改)、不唯一", example = "芋艿") + @ExcelProperty("店铺名称(数字、中文,英文(可混合,不可有特殊字符),可修改)、不唯一") + private String shopName; + + @Schema(description = "店长用户id", example = "15607") + @ExcelProperty("店长用户id") + private String userId; + + @Schema(description = "店铺类型", example = "2") + @ExcelProperty("店铺类型") + private Integer shopType; + + @Schema(description = "店铺简介(可修改)") + @ExcelProperty("店铺简介(可修改)") + private String intro; + + @Schema(description = "店铺公告(可修改)") + @ExcelProperty("店铺公告(可修改)") + private String shopNotice; + + @Schema(description = "店铺行业(餐饮、生鲜果蔬、鲜花等)") + @ExcelProperty("店铺行业(餐饮、生鲜果蔬、鲜花等)") + private Integer shopIndustry; + + @Schema(description = "店长") + @ExcelProperty("店长") + private String shopOwner; + + @Schema(description = "店铺绑定的手机(登录账号:唯一)") + @ExcelProperty("店铺绑定的手机(登录账号:唯一)") + private String mobile; + + @Schema(description = "店铺联系电话") + @ExcelProperty("店铺联系电话") + private String tel; + + @Schema(description = "店铺所在纬度(可修改)") + @ExcelProperty("店铺所在纬度(可修改)") + private String shopLat; + + @Schema(description = "店铺所在经度(可修改)") + @ExcelProperty("店铺所在经度(可修改)") + private String shopLng; + + @Schema(description = "店铺详细地址") + @ExcelProperty("店铺详细地址") + private String shopAddress; + + @Schema(description = "店铺所在省份(描述)") + @ExcelProperty("店铺所在省份(描述)") + private String province; + + @Schema(description = "店铺所在城市(描述)") + @ExcelProperty("店铺所在城市(描述)") + private String city; + + @Schema(description = "店铺所在区域(描述)") + @ExcelProperty("店铺所在区域(描述)") + private String area; + + @Schema(description = "店铺省市区代码,用于回显") + @ExcelProperty("店铺省市区代码,用于回显") + private String pcaCode; + + @Schema(description = "店铺logo(可修改)") + @ExcelProperty("店铺logo(可修改)") + private String shopLogo; + + @Schema(description = "店铺相册") + @ExcelProperty("店铺相册") + private String shopPhotos; + + @Schema(description = "每天营业时间段(可修改)") + @ExcelProperty("每天营业时间段(可修改)") + private String openTime; + + @Schema(description = "店铺状态(-1:未开通 0: 停业中 1:营业中),可修改", example = "2") + @ExcelProperty("店铺状态(-1:未开通 0: 停业中 1:营业中),可修改") + private Integer shopStatus; + + @Schema(description = "0:商家承担运费; 1:买家承担运费", example = "2") + @ExcelProperty("0:商家承担运费; 1:买家承担运费") + private Integer transportType; + + @Schema(description = "固定运费") + @ExcelProperty("固定运费") + private BigDecimal fixedFreight; + + @Schema(description = "满X包邮") + @ExcelProperty("满X包邮") + private BigDecimal fullFreeShipping; + + @Schema(description = "创建时间") + @ExcelProperty("创建时间") + private LocalDateTime createTime; + + @Schema(description = "分销开关(0:开启 1:关闭)") + @ExcelProperty("分销开关(0:开启 1:关闭)") + private Integer isDistribution; + +} \ No newline at end of file diff --git a/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/vo/shopdetail/ShopDetailSaveReqVO.java b/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/vo/shopdetail/ShopDetailSaveReqVO.java new file mode 100644 index 0000000..d353971 --- /dev/null +++ b/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/vo/shopdetail/ShopDetailSaveReqVO.java @@ -0,0 +1,87 @@ +package com.tashow.cloud.productapi.api.product.vo.shopdetail; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.math.BigDecimal; + +@Schema(description = "管理后台 - 店铺信息新增/修改 Request VO") +@Data +public class ShopDetailSaveReqVO { + + @Schema(description = "店铺id", requiredMode = Schema.RequiredMode.REQUIRED, example = "5101") + private Long shopId; + + @Schema(description = "店铺名称(数字、中文,英文(可混合,不可有特殊字符),可修改)、不唯一", example = "芋艿") + private String shopName; + + @Schema(description = "店长用户id", example = "15607") + private String userId; + + @Schema(description = "店铺类型", example = "2") + private Integer shopType; + + @Schema(description = "店铺简介(可修改)") + private String intro; + + @Schema(description = "店铺公告(可修改)") + private String shopNotice; + + @Schema(description = "店铺行业(餐饮、生鲜果蔬、鲜花等)") + private Integer shopIndustry; + + @Schema(description = "店长") + private String shopOwner; + + @Schema(description = "店铺绑定的手机(登录账号:唯一)") + private String mobile; + + @Schema(description = "店铺联系电话") + private String tel; + + @Schema(description = "店铺所在纬度(可修改)") + private String shopLat; + + @Schema(description = "店铺所在经度(可修改)") + private String shopLng; + + @Schema(description = "店铺详细地址") + private String shopAddress; + + @Schema(description = "店铺所在省份(描述)") + private String province; + + @Schema(description = "店铺所在城市(描述)") + private String city; + + @Schema(description = "店铺所在区域(描述)") + private String area; + + @Schema(description = "店铺省市区代码,用于回显") + private String pcaCode; + + @Schema(description = "店铺logo(可修改)") + private String shopLogo; + + @Schema(description = "店铺相册") + private String shopPhotos; + + @Schema(description = "每天营业时间段(可修改)") + private String openTime; + + @Schema(description = "店铺状态(-1:未开通 0: 停业中 1:营业中),可修改", example = "2") + private Integer shopStatus; + + @Schema(description = "0:商家承担运费; 1:买家承担运费", example = "2") + private Integer transportType; + + @Schema(description = "固定运费") + private BigDecimal fixedFreight; + + @Schema(description = "满X包邮") + private BigDecimal fullFreeShipping; + + @Schema(description = "分销开关(0:开启 1:关闭)") + private Integer isDistribution; + +} \ No newline at end of file diff --git a/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/vo/sku/SkuExtendVO.java b/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/vo/sku/SkuExtendVO.java new file mode 100644 index 0000000..cea9a38 --- /dev/null +++ b/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/vo/sku/SkuExtendVO.java @@ -0,0 +1,74 @@ +package com.tashow.cloud.productapi.api.product.vo.sku; + +import com.tashow.cloud.productapi.api.product.dto.SkuServiceDeliverDO; +import com.tashow.cloud.productapi.api.product.dto.SkuServiceDetailsDO; +import com.tashow.cloud.productapi.api.product.dto.SkuServiceMaterialDO; +import com.tashow.cloud.productapi.api.product.dto.SkuServiceTransportDO; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.util.List; + +@Schema(description = "sku扩展服务配置 VO") +@Data +public class SkuExtendVO { + + @Schema(description = "表单名称") + private String skuFormName; + + @Schema(description = "遗体接运车辆配置0关1开") + private Integer transportCarSwitch; + @Schema(description = "遗体接运扩展服务配置") + public List transportCarList; + @Schema(description = "遗体接运服务物料") + public List transportCarMaterialList; + + @Schema(description = "遗体运输目的地配置0关1开") + private Integer trafficSwitch; + @Schema(description = "遗体运输目的地配置") + public List trafficList; + @Schema(description = "遗体运输目的地物料") + public List trafficMaterialList; + + @Schema(description = "遗体清洁配置0关1开") + private Integer cleanSwitch; + @Schema(description = "遗体清洁配置") + public List cleanList; + @Schema(description = "遗体清洁物料") + public List cleanMaterialList; + + @Schema(description = "追思告别配置0关1开") + private Integer reflectionSwitch; + @Schema(description = "追思告别配置") + public List reflectionList; + @Schema(description = "追思告别物料") + public List reflectionMaterialList; + + @Schema(description = "遗体火化配置0关1开") + private Integer cremationSwitch; + @Schema(description = "遗体火化配置") + public List cremationList; + @Schema(description = "遗体火化物料") + public List cremationMaterialList; + + @Schema(description = "骨灰处理配置0关1开") + private Integer ashProcessingSwitch; + @Schema(description = "骨灰处理配置") + public List ashProcessingList; + @Schema(description = "骨灰处理配送方式配置") + public List ashProcessingDeliverList; + @Schema(description = "骨灰处理物料") + public List ashProcessingMaterialList; + + @Schema(description = "骨灰装殓配置0关1开") + private Integer boneashSwitch; + @Schema(description = "骨灰装殓配置") + public List boneashList; + + @Schema(description = "纪念品配置0关1开") + private Integer souvenirSwitch; + @Schema(description = "纪念品配置") + public List souvenirList; + @Schema(description = "纪念品配送方式配置") + public List souvenirDeliverList; +} \ No newline at end of file diff --git a/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/vo/sku/SkuPageReqVO.java b/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/vo/sku/SkuPageReqVO.java new file mode 100644 index 0000000..662c667 --- /dev/null +++ b/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/vo/sku/SkuPageReqVO.java @@ -0,0 +1,24 @@ +package com.tashow.cloud.productapi.api.product.vo.sku; + +import com.tashow.cloud.common.pojo.PageParam; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; + +@Schema(description = "管理后台 - 单品SKU分页 Request VO") +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class SkuPageReqVO extends PageParam { + + @Schema(description = "商品ID", example = "18784") + private Long prodId; + + @Schema(description = "销售属性组合字符串") + private String properties; + + @Schema(description = "skuID", example = "18784") + private Long skuId; + +} \ No newline at end of file diff --git a/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/vo/sku/SkuPropInfoVO.java b/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/vo/sku/SkuPropInfoVO.java new file mode 100644 index 0000000..2b03b8f --- /dev/null +++ b/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/vo/sku/SkuPropInfoVO.java @@ -0,0 +1,26 @@ +package com.tashow.cloud.productapi.api.product.vo.sku; + +import com.tashow.cloud.productapi.api.product.dto.ProdPropDO; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.util.List; + +@Data +public class SkuPropInfoVO { + @Schema(description = "产品id") + private Long prodId; + + /** + * 是否显示失效规格值 0否1是 + */ + private Integer isExpire; + + /** + * 是否显示禁用规格值 0否1是 + */ + private Integer isDisable; + + @Schema(description = "规格") + public List prodPropSaveReqVO; +} diff --git a/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/vo/sku/SkuPropVO.java b/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/vo/sku/SkuPropVO.java new file mode 100644 index 0000000..c91cd39 --- /dev/null +++ b/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/vo/sku/SkuPropVO.java @@ -0,0 +1,31 @@ +package com.tashow.cloud.productapi.api.product.vo.sku; + +import com.tashow.cloud.productapi.api.product.dto.SkuDO; +import com.tashow.cloud.productapi.api.product.vo.prodprop.ProdPropSaveReqVO; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.util.List; + +@Data +public class SkuPropVO { + @Schema(description = "产品id") + private Long prodId; + + /** + * 是否显示失效规格值 0否1是 + */ + private Integer isExpire = 1; + + /** + * 是否显示禁用规格值 0否1是 + */ + private Integer isDisable =1; + + + @Schema(description = "sku列表") + public List skuList; + + @Schema(description = "规格") + public List prodPropSaveReqVO; +} diff --git a/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/vo/sku/SkuRecycleBinVO.java b/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/vo/sku/SkuRecycleBinVO.java new file mode 100644 index 0000000..32ba1bc --- /dev/null +++ b/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/vo/sku/SkuRecycleBinVO.java @@ -0,0 +1,171 @@ +package com.tashow.cloud.productapi.api.product.vo.sku; + +import com.alibaba.excel.annotation.ExcelProperty; +import com.fasterxml.jackson.annotation.JsonFormat; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.math.BigDecimal; +import java.time.LocalDateTime; +import java.util.Date; + +@Data +public class SkuRecycleBinVO { + @Schema(description = "单品ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "32230") + private Long skuId; + + /** + * 属性规格名称 + */ + private String properties; + /** + * 是否显示失效规格值 0否1是 + */ + private String skuName; + + /** + * 还剩多少天 + */ + private Long remainingDays; + /** + * 删除时间 + */ + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") + private Date deleteTime; + + + + + @Schema(description = "商品ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "18784") + @ExcelProperty("商品ID") + private Long prodId; + + @Schema(description = "别名") + @ExcelProperty("别名") + private String alias; + + @Schema(description = "当前价格", example = "32405") + @ExcelProperty("价格") + private BigDecimal price; + + /** + * 基准价 + */ + private BigDecimal basePrice; + + @Schema(description = "最低价格", example = "5040") + @ExcelProperty("最低价格") + private BigDecimal minPrice; + + @Schema(description = "最高价格", example = "11547") + @ExcelProperty("最高价格") + private BigDecimal maxPrice; + + @Schema(description = "成本价", example = "28062") + @ExcelProperty("成本价") + private BigDecimal originalPrice; + + @Schema(description = "市场价", example = "11547") + @ExcelProperty("市场价") + private BigDecimal marketPrice; + + @Schema(description = "单位") + @ExcelProperty("单位") + private String unit; + + @Schema(description = "0:主服务1:待定", example = "1") + @ExcelProperty("0:主服务1:待定") + private Integer type; + + @Schema(description = "概述") + @ExcelProperty("概述") + private String overview; + + @Schema(description = "库存") + @ExcelProperty("库存") + private Integer stocks; + + + @Schema(description = "总库存是0 无线库存是1") + @ExcelProperty("总库存是0 无线库存是1") + private Integer stocksFlg; + + /** + * 锁定库存数 + */ + @Schema(description = "锁定库存数") + @ExcelProperty("锁定库存数") + private Integer stocksLockNum; + + @Schema(description = "预警库存") + @ExcelProperty("预警库存") + private Integer warnStocks; + + @Schema(description = "库存扣款时机0:付款扣1:下单扣", example = "1") + @ExcelProperty("库存扣款时机0:付款扣1:下单扣") + private Boolean stocksType; + + @Schema(description = "sku编码") + @ExcelProperty("sku编码") + private String skuCode; + + @Schema(description = "商品条形码", example = "14390") + @ExcelProperty("商品条形码") + private String modelId; + + @Schema(description = "sku图片") + @ExcelProperty("sku图片") + private String pic; + + + @Schema(description = "商品名称", example = "芋艿") + @ExcelProperty("商品名称") + private String prodName; + + @Schema(description = "版本号", requiredMode = Schema.RequiredMode.REQUIRED) + @ExcelProperty("版本号") + private Integer version; + + @Schema(description = "商品重量") + @ExcelProperty("商品重量") + private Double weight; + + @Schema(description = "商品体积") + @ExcelProperty("商品体积") + private Double volume; + + @Schema(description = "0 禁用 1 启用", example = "1") + @ExcelProperty("0 禁用 1 启用") + private Integer status; + + @Schema(description = "0 正常 1 已被删除") + @ExcelProperty("0 正常 1 已被删除") + private Integer isDelete; + + @Schema(description = "最小购买数量") + @ExcelProperty("最小购买数量") + private Integer moq; + + @Schema(description = "创建时间") + @ExcelProperty("创建时间") + private LocalDateTime createTime; + /** + * 是否上下架0下架1上架 + */ + private Integer isShelf; + + /** + * 是否默认规则0否1是 + */ + private Integer isSpecs; + + /** + * 服务内容 + */ + private String serviceContent; + /** + * 规格id 多个用逗号分隔(1,2,3) + */ + private String propIds; + +} diff --git a/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/vo/sku/SkuRespVO.java b/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/vo/sku/SkuRespVO.java new file mode 100644 index 0000000..762ed29 --- /dev/null +++ b/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/vo/sku/SkuRespVO.java @@ -0,0 +1,159 @@ +package com.tashow.cloud.productapi.api.product.vo.sku; + +import com.alibaba.excel.annotation.ExcelIgnoreUnannotated; +import com.alibaba.excel.annotation.ExcelProperty; +import com.baomidou.mybatisplus.annotation.TableField; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.math.BigDecimal; +import java.time.LocalDateTime; + +@Schema(description = "管理后台 - 单品SKU Response VO") +@Data +@ExcelIgnoreUnannotated +public class SkuRespVO { + + @Schema(description = "单品ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "32230") + @ExcelProperty("单品ID") + private Long skuId; + + @Schema(description = "商品ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "18784") + @ExcelProperty("商品ID") + private Long prodId; + + @Schema(description = "销售属性组合字符串 格式是p1:v1;p2:v2") + @ExcelProperty("销售属性组合字符串 格式是p1:v1;p2:v2") + private String properties; + + @Schema(description = "别名") + @ExcelProperty("别名") + private String alias; + + @Schema(description = "当前价格", example = "32405") + @ExcelProperty("价格") + private BigDecimal price; + + /** + * 基准价 + */ + private BigDecimal basePrice; + + @Schema(description = "最低价格", example = "5040") + @ExcelProperty("最低价格") + private BigDecimal minPrice; + + @Schema(description = "最高价格", example = "11547") + @ExcelProperty("最高价格") + private BigDecimal maxPrice; + + @Schema(description = "成本价", example = "28062") + @ExcelProperty("成本价") + private BigDecimal originalPrice; + + @Schema(description = "市场价", example = "11547") + @ExcelProperty("市场价") + private BigDecimal marketPrice; + + @Schema(description = "单位") + @ExcelProperty("单位") + private String unit; + + @Schema(description = "0:主服务1:待定", example = "1") + @ExcelProperty("0:主服务1:待定") + private Integer type; + + @Schema(description = "概述") + @ExcelProperty("概述") + private String overview; + + @Schema(description = "库存") + @ExcelProperty("库存") + private Integer stocks; + + + @Schema(description = "总库存是0 无线库存是1") + @ExcelProperty("总库存是0 无线库存是1") + private Integer stocksFlg; + + /** + * 锁定库存数 + */ + @Schema(description = "锁定库存数") + @ExcelProperty("锁定库存数") + private Integer stocksLockNum; + + @Schema(description = "预警库存") + @ExcelProperty("预警库存") + private Integer warnStocks; + + @Schema(description = "库存扣款时机0:付款扣1:下单扣", example = "1") + @ExcelProperty("库存扣款时机0:付款扣1:下单扣") + private Boolean stocksType; + + @Schema(description = "sku编码") + @ExcelProperty("sku编码") + private String skuCode; + + @Schema(description = "商品条形码", example = "14390") + @ExcelProperty("商品条形码") + private String modelId; + + @Schema(description = "sku图片") + @ExcelProperty("sku图片") + private String pic; + + @Schema(description = "sku名称", example = "张三") + @ExcelProperty("sku名称") + private String skuName; + + @Schema(description = "商品名称", example = "芋艿") + @ExcelProperty("商品名称") + private String prodName; + + @Schema(description = "版本号", requiredMode = Schema.RequiredMode.REQUIRED) + @ExcelProperty("版本号") + private Integer version; + + @Schema(description = "商品重量") + @ExcelProperty("商品重量") + private Double weight; + + @Schema(description = "商品体积") + @ExcelProperty("商品体积") + private Double volume; + + @Schema(description = "0 禁用 1 启用", example = "1") + @ExcelProperty("0 禁用 1 启用") + private Integer status; + + @Schema(description = "0 正常 1 已被删除") + @ExcelProperty("0 正常 1 已被删除") + private Integer isDelete; + + @Schema(description = "最小购买数量") + @ExcelProperty("最小购买数量") + private Integer moq; + + @Schema(description = "创建时间") + @ExcelProperty("创建时间") + private LocalDateTime createTime; + /** + * 是否上下架0下架1上架 + */ + private Integer isShelf; + + /** + * 是否默认规则0否1是 + */ + private Integer isSpecs; + + /** + * 服务内容 + */ + private String serviceContent; + /** + * 规格id 多个用逗号分隔(1,2,3) + */ + private String propIds; +} \ No newline at end of file diff --git a/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/vo/sku/SkuSaveReqVO.java b/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/vo/sku/SkuSaveReqVO.java new file mode 100644 index 0000000..9c33d7c --- /dev/null +++ b/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/vo/sku/SkuSaveReqVO.java @@ -0,0 +1,122 @@ +package com.tashow.cloud.productapi.api.product.vo.sku; + +import com.alibaba.excel.annotation.ExcelProperty; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +import java.math.BigDecimal; + +@Schema(description = "管理后台 - 单品SKU新增/修改 Request VO") +@Data +public class SkuSaveReqVO { + + @Schema(description = "单品ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "32230") + private Long skuId; + + @Schema(description = "商品ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "18784") + private Long prodId; + + @Schema(description = "销售属性组合字符串 格式是p1:v1;p2:v2") + private String properties; + + @Schema(description = "别名") + private String alias; + + @Schema(description = "当前价格", example = "32405") + private BigDecimal price; + + /** + * 基准价 + */ + private BigDecimal basePrice; + + @Schema(description = "最低价格", example = "5040") + private BigDecimal minPrice; + + @Schema(description = "最高价格", example = "11547") + private BigDecimal maxPrice; + + @Schema(description = "成本价", example = "28062") + private BigDecimal originalPrice; + + @Schema(description = "市场价", example = "11547") + private BigDecimal marketPrice; + + @Schema(description = "单位") + private String unit; + + @Schema(description = "0:主服务1:待定", example = "1") + private Integer type; + + @Schema(description = "概述") + private String overview; + + @Schema(description = "库存") + private Integer stocks; + + @Schema(description = "总库存是0 无线库存是1") + private Integer stocksFlg; + /** + * 锁定库存数 + */ + @Schema(description = "锁定库存数") + private Integer stocksLockNum; + + @Schema(description = "预警库存") + private Integer warnStocks; + + @Schema(description = "库存扣款时机0:付款扣1:下单扣", example = "1") + private Boolean stocksType; + + @Schema(description = "sku编码") + private String skuCode; + + @Schema(description = "商品条形码", example = "14390") + private String modelId; + + @Schema(description = "sku图片") + private String pic; + + @Schema(description = "sku名称", example = "张三") + private String skuName; + + @Schema(description = "商品名称", example = "芋艿") + private String prodName; + + @Schema(description = "版本号", requiredMode = Schema.RequiredMode.REQUIRED) + private Integer version; + + @Schema(description = "商品重量") + private Double weight; + + @Schema(description = "商品体积") + private Double volume; + + @Schema(description = "0 禁用 1 启用", example = "1") + private Integer status; + + @Schema(description = "0 正常 1 已被删除") + private Integer isDelete; + + @Schema(description = "最小购买数量") + private Integer moq; + /** + * 是否默认规则0否1是 + */ + private Integer isSpecs; + + /** + * 扩展服务表单id + */ + private Long formId; + + /** + * 服务内容 + */ + private String serviceContent; + /** + * 规格id 多个用逗号分隔(1,2,3) + */ + private String propIds; +} \ No newline at end of file diff --git a/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/vo/sku/SkuServiceExtendVO.java b/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/vo/sku/SkuServiceExtendVO.java new file mode 100644 index 0000000..c273839 --- /dev/null +++ b/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/vo/sku/SkuServiceExtendVO.java @@ -0,0 +1,41 @@ +package com.tashow.cloud.productapi.api.product.vo.sku; + +import com.tashow.cloud.productapi.api.product.dto.SkuServiceDeliverDO; +import com.tashow.cloud.productapi.api.product.dto.SkuServiceDetailsDO; +import com.tashow.cloud.productapi.api.product.dto.SkuServiceMaterialDO; +import com.tashow.cloud.productapi.api.product.dto.SkuServiceTransportDO; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.util.List; + +@Schema(description = "sku扩展服务表单 VO") +@Data +public class SkuServiceExtendVO { + + @Schema(description = "id") + private Long id; + + @Schema(description = "表单名称") + private String name; + + @Schema(description = "服务名称") + private String serviceName; + + @Schema(description = "类型0:遗体运输1:遗体运输物料") + private String type; + + @Schema(description = "服务名称") + private Integer isEnabled; + + @Schema(description = "服务详情") + public List skuServiceDetailsDOList; + @Schema(description = "服务物料") + public List skuServiceMaterialDOList; + + @Schema(description = "服务遗体运输") + public List skuServiceTransportDOList; + + @Schema(description = "服务交付方式") + public List skuServiceDeliverDOList; +} \ No newline at end of file diff --git a/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/vo/skuservicedeliver/SkuServiceDeliverPageReqVO.java b/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/vo/skuservicedeliver/SkuServiceDeliverPageReqVO.java new file mode 100644 index 0000000..435b059 --- /dev/null +++ b/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/vo/skuservicedeliver/SkuServiceDeliverPageReqVO.java @@ -0,0 +1,44 @@ +package com.tashow.cloud.productapi.api.product.vo.skuservicedeliver; + +import com.tashow.cloud.common.pojo.PageParam; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; + +import java.math.BigDecimal; + +@Schema(description = "管理后台 - 服务交付方式分页 Request VO") +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class SkuServiceDeliverPageReqVO extends PageParam { + + @Schema(description = "关联的扩展服务ID", example = "27072") + private Long serviceId; + + @Schema(description = "交互方式0:快递物流 1:到店自提 2:商家自送", example = "2") + private Boolean type; + + @Schema(description = "价格", example = "23915") + private BigDecimal price; + + @Schema(description = "是否收费0:免费1收费") + private Boolean isCharge; + + @Schema(description = "详细地址") + private String address; + + @Schema(description = "省") + private String province; + + @Schema(description = "市") + private String city; + + @Schema(description = "区") + private String area; + + @Schema(description = "电话号码") + private String tel; + +} \ No newline at end of file diff --git a/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/vo/skuservicedeliver/SkuServiceDeliverRespVO.java b/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/vo/skuservicedeliver/SkuServiceDeliverRespVO.java new file mode 100644 index 0000000..09d3353 --- /dev/null +++ b/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/vo/skuservicedeliver/SkuServiceDeliverRespVO.java @@ -0,0 +1,55 @@ +package com.tashow.cloud.productapi.api.product.vo.skuservicedeliver; + +import com.alibaba.excel.annotation.ExcelIgnoreUnannotated; +import com.alibaba.excel.annotation.ExcelProperty; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.math.BigDecimal; + +@Schema(description = "管理后台 - 服务交付方式 Response VO") +@Data +@ExcelIgnoreUnannotated +public class SkuServiceDeliverRespVO { + + @Schema(description = "服务遗体运输唯一标识符", requiredMode = Schema.RequiredMode.REQUIRED, example = "12984") + @ExcelProperty("服务遗体运输唯一标识符") + private Long id; + + @Schema(description = "关联的扩展服务ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "27072") + @ExcelProperty("关联的扩展服务ID") + private Long serviceId; + + @Schema(description = "交互方式0:快递物流 1:到店自提 2:商家自送", example = "2") + @ExcelProperty("交互方式0:快递物流 1:到店自提 2:商家自送") + private Boolean type; + + @Schema(description = "价格", example = "23915") + @ExcelProperty("价格") + private BigDecimal price; + + @Schema(description = "是否收费0:免费1收费") + @ExcelProperty("是否收费0:免费1收费") + private Boolean isCharge; + + @Schema(description = "详细地址", requiredMode = Schema.RequiredMode.REQUIRED) + @ExcelProperty("详细地址") + private String address; + + @Schema(description = "省") + @ExcelProperty("省") + private String province; + + @Schema(description = "市") + @ExcelProperty("市") + private String city; + + @Schema(description = "区") + @ExcelProperty("区") + private String area; + + @Schema(description = "电话号码", requiredMode = Schema.RequiredMode.REQUIRED) + @ExcelProperty("电话号码") + private String tel; + +} \ No newline at end of file diff --git a/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/vo/skuservicedeliver/SkuServiceDeliverSaveReqVO.java b/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/vo/skuservicedeliver/SkuServiceDeliverSaveReqVO.java new file mode 100644 index 0000000..e431f23 --- /dev/null +++ b/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/vo/skuservicedeliver/SkuServiceDeliverSaveReqVO.java @@ -0,0 +1,47 @@ +package com.tashow.cloud.productapi.api.product.vo.skuservicedeliver; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +import java.math.BigDecimal; + +@Schema(description = "管理后台 - 服务交付方式新增/修改 Request VO") +@Data +public class SkuServiceDeliverSaveReqVO { + + @Schema(description = "服务遗体运输唯一标识符", requiredMode = Schema.RequiredMode.REQUIRED, example = "12984") + private Long id; + + @Schema(description = "关联的扩展服务ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "27072") + @NotNull(message = "关联的扩展服务ID不能为空") + private Long serviceId; + + @Schema(description = "交互方式0:快递物流 1:到店自提 2:商家自送", example = "2") + private Boolean type; + + @Schema(description = "价格", example = "23915") + private BigDecimal price; + + @Schema(description = "是否收费0:免费1收费") + private Boolean isCharge; + + @Schema(description = "详细地址", requiredMode = Schema.RequiredMode.REQUIRED) + @NotEmpty(message = "详细地址不能为空") + private String address; + + @Schema(description = "省") + private String province; + + @Schema(description = "市") + private String city; + + @Schema(description = "区") + private String area; + + @Schema(description = "电话号码", requiredMode = Schema.RequiredMode.REQUIRED) + @NotEmpty(message = "电话号码不能为空") + private String tel; + +} \ No newline at end of file diff --git a/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/vo/skuservicedetails/SkuServiceDetailsPageReqVO.java b/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/vo/skuservicedetails/SkuServiceDetailsPageReqVO.java new file mode 100644 index 0000000..b0fc298 --- /dev/null +++ b/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/vo/skuservicedetails/SkuServiceDetailsPageReqVO.java @@ -0,0 +1,62 @@ +package com.tashow.cloud.productapi.api.product.vo.skuservicedetails; + +import com.tashow.cloud.common.pojo.PageParam; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; + +import java.math.BigDecimal; + +@Schema(description = "管理后台 - 服务详情分页 Request VO") +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class SkuServiceDetailsPageReqVO extends PageParam { + + @Schema(description = "关联的扩展服务ID", example = "21602") + private Long serviceId; + + @Schema(description = "图片") + private String pic; + + @Schema(description = "名称", example = "张三") + private String name; + + @Schema(description = "价格", example = "16929") + private BigDecimal price; + + @Schema(description = "是否收费0:免费1收费") + private Boolean isCharge; + + @Schema(description = "是否默认值0否1是") + private Boolean isDefault; + + @Schema(description = "类型:0:配置信息1:交付方式", example = "2") + private Boolean type; + + @Schema(description = "描述") + private String describeContent; + + /** + * 地点 + */ + private String adress; + + /** + * 触发节点名称 + */ + private String triggerName; + + + /** + * 触发节点id(或关联节点) + */ + private Long triggerId; + + + /** + * 是否并行0串行1并行 + */ + private Integer isParallel; +} \ No newline at end of file diff --git a/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/vo/skuservicedetails/SkuServiceDetailsRespVO.java b/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/vo/skuservicedetails/SkuServiceDetailsRespVO.java new file mode 100644 index 0000000..e7209f7 --- /dev/null +++ b/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/vo/skuservicedetails/SkuServiceDetailsRespVO.java @@ -0,0 +1,72 @@ +package com.tashow.cloud.productapi.api.product.vo.skuservicedetails; + +import com.alibaba.excel.annotation.ExcelIgnoreUnannotated; +import com.alibaba.excel.annotation.ExcelProperty; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.math.BigDecimal; + +@Schema(description = "管理后台 - 服务详情 Response VO") +@Data +@ExcelIgnoreUnannotated +public class SkuServiceDetailsRespVO { + + @Schema(description = "服务详情的唯一标识符", requiredMode = Schema.RequiredMode.REQUIRED, example = "17139") + @ExcelProperty("服务详情的唯一标识符") + private Long id; + + @Schema(description = "关联的扩展服务ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "21602") + @ExcelProperty("关联的扩展服务ID") + private Long serviceId; + + @Schema(description = "图片") + @ExcelProperty("图片") + private String pic; + + @Schema(description = "名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "张三") + @ExcelProperty("名称") + private String name; + + @Schema(description = "价格", example = "16929") + @ExcelProperty("价格") + private BigDecimal price; + + @Schema(description = "是否收费0:免费1收费") + @ExcelProperty("是否收费0:免费1收费") + private Boolean isCharge; + + @Schema(description = "是否默认值0否1是") + @ExcelProperty("是否默认值0否1是") + private Boolean isDefault; + + @Schema(description = "类型:0:配置信息1:交付方式", example = "2") + @ExcelProperty("类型:0:配置信息1:交付方式") + private Boolean type; + + @Schema(description = "描述") + @ExcelProperty("描述") + private String describeContent; + + /** + * 地点 + */ + private String adress; + + /** + * 触发节点名称 + */ + private String triggerName; + + + /** + * 触发节点id(或关联节点) + */ + private Long triggerId; + + + /** + * 是否并行0串行1并行 + */ + private Integer isParallel; +} \ No newline at end of file diff --git a/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/vo/skuservicedetails/SkuServiceDetailsSaveReqVO.java b/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/vo/skuservicedetails/SkuServiceDetailsSaveReqVO.java new file mode 100644 index 0000000..72e359c --- /dev/null +++ b/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/vo/skuservicedetails/SkuServiceDetailsSaveReqVO.java @@ -0,0 +1,64 @@ +package com.tashow.cloud.productapi.api.product.vo.skuservicedetails; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +import java.math.BigDecimal; + +@Schema(description = "管理后台 - 服务详情新增/修改 Request VO") +@Data +public class SkuServiceDetailsSaveReqVO { + + @Schema(description = "服务详情的唯一标识符", requiredMode = Schema.RequiredMode.REQUIRED, example = "17139") + private Long id; + + @Schema(description = "关联的扩展服务ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "21602") + @NotNull(message = "关联的扩展服务ID不能为空") + private Long serviceId; + + @Schema(description = "图片") + private String pic; + + @Schema(description = "名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "张三") + @NotEmpty(message = "名称不能为空") + private String name; + + @Schema(description = "价格", example = "16929") + private BigDecimal price; + + @Schema(description = "是否收费0:免费1收费") + private Boolean isCharge; + + @Schema(description = "是否默认值0否1是") + private Boolean isDefault; + + @Schema(description = "类型:0:配置信息1:交付方式", example = "2") + private Boolean type; + + @Schema(description = "描述") + private String describeContent; + /** + * 地点 + */ + private String adress; + + /** + * 触发节点名称 + */ + private String triggerName; + + + /** + * 触发节点id(或关联节点) + */ + private Long triggerId; + + + /** + * 是否并行0串行1并行 + */ + private Integer isParallel; + +} \ No newline at end of file diff --git a/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/vo/skuservicematerial/SkuServiceMaterialPageReqVO.java b/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/vo/skuservicematerial/SkuServiceMaterialPageReqVO.java new file mode 100644 index 0000000..fb32419 --- /dev/null +++ b/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/vo/skuservicematerial/SkuServiceMaterialPageReqVO.java @@ -0,0 +1,25 @@ +package com.tashow.cloud.productapi.api.product.vo.skuservicematerial; + +import com.tashow.cloud.common.pojo.PageParam; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; + +@Schema(description = "管理后台 - 服务物料详情分页 Request VO") +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class SkuServiceMaterialPageReqVO extends PageParam { + + @Schema(description = "关联的扩展服务ID", example = "21102") + private Long serviceId; + + @Schema(description = "名称", example = "王五") + private String name; + + @Schema(description = "描述") + private String describeContent; + + +} \ No newline at end of file diff --git a/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/vo/skuservicematerial/SkuServiceMaterialRespVO.java b/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/vo/skuservicematerial/SkuServiceMaterialRespVO.java new file mode 100644 index 0000000..8535534 --- /dev/null +++ b/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/vo/skuservicematerial/SkuServiceMaterialRespVO.java @@ -0,0 +1,30 @@ +package com.tashow.cloud.productapi.api.product.vo.skuservicematerial; + +import com.alibaba.excel.annotation.ExcelIgnoreUnannotated; +import com.alibaba.excel.annotation.ExcelProperty; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +@Schema(description = "管理后台 - 服务物料详情 Response VO") +@Data +@ExcelIgnoreUnannotated +public class SkuServiceMaterialRespVO { + + @Schema(description = "服务物料的唯一标识符", requiredMode = Schema.RequiredMode.REQUIRED, example = "21371") + @ExcelProperty("服务物料的唯一标识符") + private Long id; + + @Schema(description = "关联的扩展服务ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "21102") + @ExcelProperty("关联的扩展服务ID") + private Long serviceId; + + @Schema(description = "名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "王五") + @ExcelProperty("名称") + private String name; + + @Schema(description = "描述") + @ExcelProperty("描述") + private String describeContent; + + +} \ No newline at end of file diff --git a/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/vo/skuservicematerial/SkuServiceMaterialSaveReqVO.java b/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/vo/skuservicematerial/SkuServiceMaterialSaveReqVO.java new file mode 100644 index 0000000..142a28f --- /dev/null +++ b/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/vo/skuservicematerial/SkuServiceMaterialSaveReqVO.java @@ -0,0 +1,26 @@ +package com.tashow.cloud.productapi.api.product.vo.skuservicematerial; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +@Schema(description = "管理后台 - 服务物料详情新增/修改 Request VO") +@Data +public class SkuServiceMaterialSaveReqVO { + + @Schema(description = "服务物料的唯一标识符", requiredMode = Schema.RequiredMode.REQUIRED, example = "21371") + private Long id; + + @Schema(description = "关联的扩展服务ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "21102") + @NotNull(message = "关联的扩展服务ID不能为空") + private Long serviceId; + + @Schema(description = "名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "王五") + @NotEmpty(message = "名称不能为空") + private String name; + + @Schema(description = "描述") + private String describeContent; + +} \ No newline at end of file diff --git a/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/vo/skuservicesform/SkuServicesFormPageReqVO.java b/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/vo/skuservicesform/SkuServicesFormPageReqVO.java new file mode 100644 index 0000000..fe86b67 --- /dev/null +++ b/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/vo/skuservicesform/SkuServicesFormPageReqVO.java @@ -0,0 +1,24 @@ +package com.tashow.cloud.productapi.api.product.vo.skuservicesform; + +import com.tashow.cloud.common.pojo.PageParam; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; + +@Schema(description = "管理后台 - 商品SKU扩展服务表单分页 Request VO") +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class SkuServicesFormPageReqVO extends PageParam { + + @Schema(description = "表单名称", example = "赵六") + private String name; + + @Schema(description = "服务名称", example = "芋艿") + private String serviceName; + + @Schema(description = "是否启用该服务") + private Boolean isEnabled; + +} \ No newline at end of file diff --git a/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/vo/skuservicesform/SkuServicesFormRespVO.java b/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/vo/skuservicesform/SkuServicesFormRespVO.java new file mode 100644 index 0000000..6bcd051 --- /dev/null +++ b/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/vo/skuservicesform/SkuServicesFormRespVO.java @@ -0,0 +1,30 @@ +package com.tashow.cloud.productapi.api.product.vo.skuservicesform; + +import com.alibaba.excel.annotation.ExcelIgnoreUnannotated; +import com.alibaba.excel.annotation.ExcelProperty; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +@Schema(description = "管理后台 - 商品SKU扩展服务表单 Response VO") +@Data +@ExcelIgnoreUnannotated +public class SkuServicesFormRespVO { + + @Schema(description = "扩展服务的唯一标识符", requiredMode = Schema.RequiredMode.REQUIRED, example = "24347") + @ExcelProperty("扩展服务的唯一标识符") + private Long id; + + @Schema(description = "表单名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "赵六") + @ExcelProperty("表单名称") + private String name; + + @Schema(description = "服务名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋艿") + @ExcelProperty("服务名称") + private String serviceName; + + @Schema(description = "是否启用该服务", requiredMode = Schema.RequiredMode.REQUIRED) + @ExcelProperty("是否启用该服务") + private Boolean isEnabled; + + +} \ No newline at end of file diff --git a/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/vo/skuservicesform/SkuServicesFormSaveReqVO.java b/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/vo/skuservicesform/SkuServicesFormSaveReqVO.java new file mode 100644 index 0000000..766e334 --- /dev/null +++ b/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/vo/skuservicesform/SkuServicesFormSaveReqVO.java @@ -0,0 +1,28 @@ +package com.tashow.cloud.productapi.api.product.vo.skuservicesform; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +@Schema(description = "管理后台 - 商品SKU扩展服务表单新增/修改 Request VO") +@Data +public class SkuServicesFormSaveReqVO { + + @Schema(description = "扩展服务的唯一标识符", requiredMode = Schema.RequiredMode.REQUIRED, example = "24347") + private Long id; + + @Schema(description = "表单名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "赵六") + @NotEmpty(message = "表单名称不能为空") + private String name; + + + @Schema(description = "服务名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋艿") + @NotEmpty(message = "服务名称不能为空") + private String serviceName; + + @Schema(description = "是否启用该服务", requiredMode = Schema.RequiredMode.REQUIRED) + @NotNull(message = "是否启用该服务不能为空") + private Boolean isEnabled; + +} \ No newline at end of file diff --git a/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/vo/skuservicetransport/SkuServiceTransportPageReqVO.java b/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/vo/skuservicetransport/SkuServiceTransportPageReqVO.java new file mode 100644 index 0000000..f9870f2 --- /dev/null +++ b/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/vo/skuservicetransport/SkuServiceTransportPageReqVO.java @@ -0,0 +1,37 @@ +package com.tashow.cloud.productapi.api.product.vo.skuservicetransport; + +import com.tashow.cloud.common.pojo.PageParam; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; + +@Schema(description = "管理后台 - 服务遗体运输分页 Request VO") +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class SkuServiceTransportPageReqVO extends PageParam { + + @Schema(description = "关联的扩展服务ID", example = "1035") + private Long serviceId; + + @Schema(description = "联系人") + private String contacts; + + @Schema(description = "详细地址") + private String address; + + @Schema(description = "省") + private String province; + + @Schema(description = "市") + private String city; + + @Schema(description = "区") + private String area; + + @Schema(description = "电话号码") + private String tel; + + +} \ No newline at end of file diff --git a/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/vo/skuservicetransport/SkuServiceTransportRespVO.java b/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/vo/skuservicetransport/SkuServiceTransportRespVO.java new file mode 100644 index 0000000..3098371 --- /dev/null +++ b/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/vo/skuservicetransport/SkuServiceTransportRespVO.java @@ -0,0 +1,46 @@ +package com.tashow.cloud.productapi.api.product.vo.skuservicetransport; + +import com.alibaba.excel.annotation.ExcelIgnoreUnannotated; +import com.alibaba.excel.annotation.ExcelProperty; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +@Schema(description = "管理后台 - 服务遗体运输 Response VO") +@Data +@ExcelIgnoreUnannotated +public class SkuServiceTransportRespVO { + + @Schema(description = "服务遗体运输唯一标识符", requiredMode = Schema.RequiredMode.REQUIRED, example = "5277") + @ExcelProperty("服务遗体运输唯一标识符") + private Long id; + + @Schema(description = "关联的扩展服务ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1035") + @ExcelProperty("关联的扩展服务ID") + private Long serviceId; + + @Schema(description = "联系人", requiredMode = Schema.RequiredMode.REQUIRED) + @ExcelProperty("联系人") + private String contacts; + + @Schema(description = "详细地址", requiredMode = Schema.RequiredMode.REQUIRED) + @ExcelProperty("详细地址") + private String address; + + @Schema(description = "省") + @ExcelProperty("省") + private String province; + + @Schema(description = "市") + @ExcelProperty("市") + private String city; + + @Schema(description = "区") + @ExcelProperty("区") + private String area; + + @Schema(description = "电话号码", requiredMode = Schema.RequiredMode.REQUIRED) + @ExcelProperty("电话号码") + private String tel; + + +} \ No newline at end of file diff --git a/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/vo/skuservicetransport/SkuServiceTransportSaveReqVO.java b/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/vo/skuservicetransport/SkuServiceTransportSaveReqVO.java new file mode 100644 index 0000000..18e9dba --- /dev/null +++ b/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/api/product/vo/skuservicetransport/SkuServiceTransportSaveReqVO.java @@ -0,0 +1,41 @@ +package com.tashow.cloud.productapi.api.product.vo.skuservicetransport; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +@Schema(description = "管理后台 - 服务遗体运输新增/修改 Request VO") +@Data +public class SkuServiceTransportSaveReqVO { + + @Schema(description = "服务遗体运输唯一标识符", requiredMode = Schema.RequiredMode.REQUIRED, example = "5277") + private Long id; + + @Schema(description = "关联的扩展服务ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1035") + @NotNull(message = "关联的扩展服务ID不能为空") + private Long serviceId; + + @Schema(description = "联系人", requiredMode = Schema.RequiredMode.REQUIRED) + @NotEmpty(message = "联系人不能为空") + private String contacts; + + @Schema(description = "详细地址", requiredMode = Schema.RequiredMode.REQUIRED) + @NotEmpty(message = "详细地址不能为空") + private String address; + + @Schema(description = "省") + private String province; + + @Schema(description = "市") + private String city; + + @Schema(description = "区") + private String area; + + @Schema(description = "电话号码", requiredMode = Schema.RequiredMode.REQUIRED) + @NotEmpty(message = "电话号码不能为空") + private String tel; + + +} \ No newline at end of file diff --git a/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/enums/ApiConstants.java b/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/enums/ApiConstants.java new file mode 100644 index 0000000..0fd09af --- /dev/null +++ b/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/enums/ApiConstants.java @@ -0,0 +1,24 @@ +package com.tashow.cloud.productapi.enums; + + +import com.tashow.cloud.common.enums.RpcConstants; + +/** + * API 相关的枚举 + * + * @author 芋道源码 + */ +public class ApiConstants { + + /** + * 服务名 + * + * 注意,需要保证和 spring.application.name 保持一致 + */ + public static final String NAME = "product-server"; + + public static final String PREFIX = RpcConstants.RPC_API_PREFIX + "/infra"; + + public static final String VERSION = "1.0.0"; + +} diff --git a/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/enums/BaseEnum.java b/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/enums/BaseEnum.java new file mode 100644 index 0000000..fb8a208 --- /dev/null +++ b/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/enums/BaseEnum.java @@ -0,0 +1,42 @@ +package com.tashow.cloud.productapi.enums; + +import lombok.Getter; + +/** + * @Author LGF + * @create 2020/10/26 16:36 + */ +public enum BaseEnum { + /** + * 基础 枚举 + */ + + YES_ONE(1, "是"), + NO_ZERO(0, "否"), + + ENABLE_ZERO(0, "启用"), + FORBIDDEN_ONE(1, "禁用"), + + YES_BINDING(1, "已绑定"), + NO_BINDING(0, "未绑定"), + + NO(1, "删除"), + YES(0, "正常"), + + HIDE(3, "隐藏"), + + NO_TWO(2, "否"), + ; + + @Getter + Integer key; + @Getter + String value; + ; + + BaseEnum(Integer key, String value) { + this.key = key; + this.value = value; + } + +} diff --git a/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/enums/DictTypeConstants.java b/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/enums/DictTypeConstants.java new file mode 100644 index 0000000..2a68d4f --- /dev/null +++ b/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/enums/DictTypeConstants.java @@ -0,0 +1,20 @@ +package com.tashow.cloud.productapi.enums; + +/** + * Infra 字典类型的枚举类 + * + * @author 芋道源码 + */ +public interface DictTypeConstants { + + String JOB_STATUS = "product_job_status"; // 定时任务状态的枚举 + String JOB_LOG_STATUS = "product_job_log_status"; // 定时任务日志状态的枚举 + + String API_ERROR_LOG_PROCESS_STATUS = "product_api_error_log_process_status"; // API 错误日志的处理状态的枚举 + + String CONFIG_TYPE = "product_config_type"; // 参数配置类型 + String BOOLEAN_STRING = "product_boolean_string"; // Boolean 是否类型 + + String OPERATE_TYPE = "product_operate_type"; // 操作类型 + +} diff --git a/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/enums/ErrorCodeConstants.java b/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/enums/ErrorCodeConstants.java new file mode 100644 index 0000000..7f26c2e --- /dev/null +++ b/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/enums/ErrorCodeConstants.java @@ -0,0 +1,35 @@ +package com.tashow.cloud.productapi.enums; + + +import com.tashow.cloud.common.exception.ErrorCode; + +/** + * Infra 错误码枚举类 + * + * infra 系统,使用 1-001-000-000 段 + */ +public interface ErrorCodeConstants { + + ErrorCode CATEGORY_NOT_EXISTS = new ErrorCode(10001, "产品类目不存在"); + ErrorCode PROD_NOT_EXISTS = new ErrorCode(10002, "商品不存在"); + ErrorCode PROD_ADDITIONAL_FEE_DATES_NOT_EXISTS = new ErrorCode(10003, "特殊日期附加费用规则不存在"); + ErrorCode PROD_ADDITIONAL_FEE_PERIODS_NOT_EXISTS = new ErrorCode(10004, "特殊时段附加费用规则不存在"); + ErrorCode PROD_EMERGENCY_RESPONSE_NOT_EXISTS = new ErrorCode(10005, "商品紧急响应服务设置不存在"); + ErrorCode PROD_EMERGENCY_RESPONSE_INTERVALS_NOT_EXISTS = new ErrorCode(10006, "紧急响应时间区间设置不存在"); + ErrorCode PROD_PROP_NOT_EXISTS = new ErrorCode(10007, "商品属性不存在"); + ErrorCode PROD_PROP_VALUE_NOT_EXISTS = new ErrorCode(10008, "属性规则不存在"); + ErrorCode PROD_RESERVATION_CONFIG_NOT_EXISTS = new ErrorCode(10009, "商品预约配置不存在"); + ErrorCode PROD_SERVICE_AREA_RELEVANCE_NOT_EXISTS = new ErrorCode(10010, "商品与服务区域关联不存在"); + ErrorCode PROD_SERVICE_AREAS_NOT_EXISTS = new ErrorCode(10011, "服务区域不存在"); + ErrorCode PROD_SERVICE_OVER_AREA_RULES_NOT_EXISTS = new ErrorCode(10012, "超区规则不存在"); + ErrorCode PROD_TAGS_NOT_EXISTS = new ErrorCode(10013, "商品和标签管理不存在"); + ErrorCode PRODUCT_ORDER_LIMIT_NOT_EXISTS = new ErrorCode(10014, "商品接单上限设置不存在"); + ErrorCode PROD_WEIGHT_RANGE_PRICES_NOT_EXISTS = new ErrorCode(10015, "体重区间价格不存在"); + ErrorCode SHOP_DETAIL_NOT_EXISTS = new ErrorCode(10016, "店铺信息不存在"); + ErrorCode SKU_NOT_EXISTS = new ErrorCode(10017, "单品SKU不存在"); + ErrorCode SKU_SERVICE_DELIVER_NOT_EXISTS = new ErrorCode(10018, "服务交付方式不存在"); + ErrorCode SKU_SERVICE_MATERIAL_NOT_EXISTS = new ErrorCode(10019, "服务物料详情不存在"); + ErrorCode SKU_SERVICES_FORM_NOT_EXISTS = new ErrorCode(10021, "商品SKU扩展服务表单不存在"); + ErrorCode SKU_SERVICE_TRANSPORT_NOT_EXISTS = new ErrorCode(10022, "服务遗体运输不存在"); + ErrorCode SKU_SERVICE_DETAILS_NOT_EXISTS = new ErrorCode(10023, "服务详情不存在"); +} diff --git a/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/enums/ProdPropRule.java b/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/enums/ProdPropRule.java new file mode 100644 index 0000000..b5b1e42 --- /dev/null +++ b/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/enums/ProdPropRule.java @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved. + * + * https://www.mall4j.com/ + * + * 未经允许,不可做商业用途! + * + * 版权所有,侵权必究! + */ + +package com.tashow.cloud.productapi.enums; + +/** + * 商品规格参数、属性类型 + * @author lgh + */ +public enum ProdPropRule { + + // 规格属性 (用于商品商品发布时,关联sku) + SPEC(1), + + // 规格参数(用于商品搜索时,与分类关联搜索) + ATTRIBUTE(2); + + private Integer num; + + public Integer value() { + return num; + } + + ProdPropRule(Integer num){ + this.num = num; + } + + public static ProdPropRule instance(Integer value) { + ProdPropRule[] enums = values(); + for (ProdPropRule statusEnum : enums) { + if (statusEnum.value().equals(value)) { + return statusEnum; + } + } + return null; + } +} diff --git a/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/enums/ServiceTypeEnum.java b/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/enums/ServiceTypeEnum.java new file mode 100644 index 0000000..bb33549 --- /dev/null +++ b/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/enums/ServiceTypeEnum.java @@ -0,0 +1,45 @@ +package com.tashow.cloud.productapi.enums; + +public enum ServiceTypeEnum { + TRANSPORT_CAR_CONFIG(1, "接运车辆配置"), + TRANSPORT_CAR_MATERIAL(2, "接运车辆服务物料"), + BODY_TRANSPORT_CONFIG(3, "遗体运输目的地配置"), + BODY_TRANSPORT_MATERIAL(4, "遗体运输目的地物料"), + BODY_CLEAN_CONFIG(5, "遗体清洁配置"), + BODY_CLEAN_MATERIAL(6, "遗体清洁物料"), + MEMORIAL_CONFIG(7, "追思告别配置"), + MEMORIAL_MATERIAL(8, "追思告别物料"), + CREMATION_CONFIG(9, "遗体火化配置"), + CREMATION_MATERIAL(10, "遗体火化物料"), + ASH_PROCESSING_CONFIG(11, "骨灰处理配置"), + ASH_PROCESSING_DELIVERY(12, "骨灰处理配送方式"), + ASH_PROCESSING_MATERIAL(13, "骨灰处理物料"), + BONE_ASH_CONFIG(14, "骨灰装殓配置"), + SOUVENIR_CONFIG(15, "纪念品配置"), + SOUVENIR_DELIVERY(16, "纪念品配送方式"); + + private final int code; + private final String description; + + ServiceTypeEnum(int code, String description) { + this.code = code; + this.description = description; + } + + public int getCode() { + return code; + } + + public String getDescription() { + return description; + } + + public static ServiceTypeEnum getByCode(int code) { + for (ServiceTypeEnum type : ServiceTypeEnum.values()) { + if (type.getCode() == code) { + return type; + } + } + return null; + } +} \ No newline at end of file diff --git a/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/general/StringListTypeHandler.java b/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/general/StringListTypeHandler.java new file mode 100644 index 0000000..8a5a612 --- /dev/null +++ b/tashow-feign/tashow-product-api/src/main/java/com/tashow/cloud/productapi/general/StringListTypeHandler.java @@ -0,0 +1,58 @@ +package com.tashow.cloud.productapi.general; + +import org.apache.ibatis.type.BaseTypeHandler; +import org.apache.ibatis.type.JdbcType; +import org.apache.ibatis.type.MappedJdbcTypes; +import org.apache.ibatis.type.MappedTypes; + +import java.sql.CallableStatement; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +/** + * 处理 List 与数据库逗号分隔字符串之间的转换 + */ +@MappedTypes(List.class) +@MappedJdbcTypes(JdbcType.VARCHAR) +public class StringListTypeHandler extends BaseTypeHandler> { + + @Override + public void setNonNullParameter(PreparedStatement ps, int i, List parameter, JdbcType jdbcType) throws SQLException { + // 将 List 转为逗号分隔的字符串 + StringBuilder sb = new StringBuilder(); + for (int j = 0; j < parameter.size(); j++) { + if (j > 0) sb.append(","); + sb.append(parameter.get(j)); + } + ps.setString(i, sb.toString()); + } + + @Override + public List getNullableResult(ResultSet rs, String columnName) throws SQLException { + String str = rs.getString(columnName); + return parseStringToList(str); + } + + @Override + public List getNullableResult(ResultSet rs, int columnIndex) throws SQLException { + String str = rs.getString(columnIndex); + return parseStringToList(str); + } + + @Override + public List getNullableResult(CallableStatement cs, int columnIndex) throws SQLException { + String str = cs.getString(columnIndex); + return parseStringToList(str); + } + + private List parseStringToList(String str) { + if (str == null || str.trim().length() == 0) { + return Collections.emptyList(); + } + return Arrays.asList(str.split(",")); + } +} diff --git a/tashow-feign/tashow-system-api/pom.xml b/tashow-feign/tashow-system-api/pom.xml new file mode 100644 index 0000000..e90a0ea --- /dev/null +++ b/tashow-feign/tashow-system-api/pom.xml @@ -0,0 +1,41 @@ + + + + com.tashow.cloud + tashow-feign + ${revision} + + 4.0.0 + tashow-system-api + jar + + ${project.artifactId} + + system 模块 API,暴露给其它模块调用 + + + + + com.tashow.cloud + tashow-common + + + + + org.springframework.boot + spring-boot-starter-validation + true + + + + + org.springframework.cloud + spring-cloud-starter-openfeign + true + + + + + diff --git a/tashow-feign/tashow-system-api/src/main/java/com/tashow/cloud/systemapi/api/dept/DeptApi.java b/tashow-feign/tashow-system-api/src/main/java/com/tashow/cloud/systemapi/api/dept/DeptApi.java new file mode 100644 index 0000000..a0c7094 --- /dev/null +++ b/tashow-feign/tashow-system-api/src/main/java/com/tashow/cloud/systemapi/api/dept/DeptApi.java @@ -0,0 +1,46 @@ +package com.tashow.cloud.systemapi.api.dept; + +import com.tashow.cloud.common.pojo.CommonResult; +import com.tashow.cloud.common.util.collection.CollectionUtils; +import com.tashow.cloud.systemapi.api.dept.dto.DeptRespDTO; +import com.tashow.cloud.systemapi.enums.ApiConstants; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import org.springframework.cloud.openfeign.FeignClient; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestParam; + +@FeignClient(name = ApiConstants.NAME) // TODO 芋艿:fallbackFactory = +/** RPC 服务 - 部门 */ +public interface DeptApi { + + String PREFIX = ApiConstants.PREFIX + "/dept"; + + @GetMapping(PREFIX + "/get") + /** 获得部门信息 */ + CommonResult getDept(@RequestParam("id") Long id); + + @GetMapping(PREFIX + "/list") + /** 获得部门信息数组 */ + CommonResult> getDeptList(@RequestParam("ids") Collection ids); + + @GetMapping(PREFIX + "/valid") + /** 校验部门是否合法 */ + CommonResult validateDeptList(@RequestParam("ids") Collection ids); + + /** + * 获得指定编号的部门 Map + * + * @param ids 部门编号数组 + * @return 部门 Map + */ + default Map getDeptMap(Collection ids) { + List list = getDeptList(ids).getCheckedData(); + return CollectionUtils.convertMap(list, DeptRespDTO::getId); + } + + @GetMapping(PREFIX + "/list-child") + /** 获得指定部门的所有子部门 */ + CommonResult> getChildDeptList(@RequestParam("id") Long id); +} diff --git a/tashow-feign/tashow-system-api/src/main/java/com/tashow/cloud/systemapi/api/dept/PostApi.java b/tashow-feign/tashow-system-api/src/main/java/com/tashow/cloud/systemapi/api/dept/PostApi.java new file mode 100644 index 0000000..1d88bdd --- /dev/null +++ b/tashow-feign/tashow-system-api/src/main/java/com/tashow/cloud/systemapi/api/dept/PostApi.java @@ -0,0 +1,38 @@ +package com.tashow.cloud.systemapi.api.dept; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.map.MapUtil; +import com.tashow.cloud.common.pojo.CommonResult; +import com.tashow.cloud.common.util.collection.CollectionUtils; +import com.tashow.cloud.systemapi.api.dept.dto.PostRespDTO; +import com.tashow.cloud.systemapi.enums.ApiConstants; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import org.springframework.cloud.openfeign.FeignClient; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestParam; + +@FeignClient(name = ApiConstants.NAME) // TODO 芋艿:fallbackFactory = +/** RPC 服务 - 岗位 */ +public interface PostApi { + + String PREFIX = ApiConstants.PREFIX + "/post"; + + @GetMapping(PREFIX + "/valid") + /** 校验岗位是否合法 */ + CommonResult validPostList(@RequestParam("ids") Collection ids); + + @GetMapping(PREFIX + "/list") + /** 获得岗位列表 */ + CommonResult> getPostList(@RequestParam("ids") Collection ids); + + default Map getPostMap(Collection ids) { + if (CollUtil.isEmpty(ids)) { + return MapUtil.empty(); + } + + List list = getPostList(ids).getData(); + return CollectionUtils.convertMap(list, PostRespDTO::getId); + } +} diff --git a/tashow-feign/tashow-system-api/src/main/java/com/tashow/cloud/systemapi/api/dept/dto/DeptRespDTO.java b/tashow-feign/tashow-system-api/src/main/java/com/tashow/cloud/systemapi/api/dept/dto/DeptRespDTO.java new file mode 100644 index 0000000..b3375a7 --- /dev/null +++ b/tashow-feign/tashow-system-api/src/main/java/com/tashow/cloud/systemapi/api/dept/dto/DeptRespDTO.java @@ -0,0 +1,23 @@ +package com.tashow.cloud.systemapi.api.dept.dto; + +import lombok.Data; + +/** RPC 服务 - 部门 Response DTO */ +@Data +public class DeptRespDTO { + + /** 部门编号" */ + private Long id; + + /** 部门名称", example = "研发部 */ + private String name; + + /** 父部门编号" */ + private Long parentId; + + /** 负责人的用户编号" */ + private Long leaderUserId; + + /** 部门状态" */ + private Integer status; // 参见 CommonStatusEnum 枚举 +} diff --git a/tashow-feign/tashow-system-api/src/main/java/com/tashow/cloud/systemapi/api/dept/dto/PostRespDTO.java b/tashow-feign/tashow-system-api/src/main/java/com/tashow/cloud/systemapi/api/dept/dto/PostRespDTO.java new file mode 100644 index 0000000..a3b4af3 --- /dev/null +++ b/tashow-feign/tashow-system-api/src/main/java/com/tashow/cloud/systemapi/api/dept/dto/PostRespDTO.java @@ -0,0 +1,28 @@ +package com.tashow.cloud.systemapi.api.dept.dto; + +import lombok.Data; + +/** + * 岗位 Response DTO + * + * @author 芋道源码 + */ +/** RPC 服务 - 岗位 Response DTO */ +@Data +public class PostRespDTO { + + /** 岗位编号" */ + private Long id; + + /** 岗位名称", example = "小土豆 */ + private String name; + + /** 岗位编码" */ + private String code; + + /** 岗位排序" */ + private Integer sort; + + /** 状态" */ + private Integer status; // 参见 CommonStatusEnum 枚举 +} diff --git a/tashow-feign/tashow-system-api/src/main/java/com/tashow/cloud/systemapi/api/dict/DictDataApi.java b/tashow-feign/tashow-system-api/src/main/java/com/tashow/cloud/systemapi/api/dict/DictDataApi.java new file mode 100644 index 0000000..d808e5d --- /dev/null +++ b/tashow-feign/tashow-system-api/src/main/java/com/tashow/cloud/systemapi/api/dict/DictDataApi.java @@ -0,0 +1,66 @@ +package com.tashow.cloud.systemapi.api.dict; + +import static com.tashow.cloud.common.util.collection.CollectionUtils.convertList; + +import cn.hutool.core.util.ObjUtil; +import cn.hutool.core.util.StrUtil; +import com.tashow.cloud.common.pojo.CommonResult; +import com.tashow.cloud.systemapi.api.dict.dto.DictDataRespDTO; +import com.tashow.cloud.systemapi.enums.ApiConstants; +import java.util.Collection; +import java.util.List; +import org.springframework.cloud.openfeign.FeignClient; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestParam; + +@FeignClient(name = ApiConstants.NAME) // TODO 芋艿:fallbackFactory = +/** RPC 服务 - 字典数据 */ +public interface DictDataApi { + + String PREFIX = ApiConstants.PREFIX + "/dict-data"; + + @GetMapping(PREFIX + "/valid") + /** 校验字典数据们是否有效 */ + CommonResult validateDictDataList( + @RequestParam("dictType") String dictType, @RequestParam("values") Collection values); + + @GetMapping(PREFIX + "/get") + /** 获得指定的字典数据 */ + CommonResult getDictData( + @RequestParam("dictType") String dictType, @RequestParam("value") String value); + + /** + * 获得指定的字典标签,从缓存中 + * + * @param type 字典类型 + * @param value 字典数据值 + * @return 字典标签 + */ + default String getDictDataLabel(String type, Integer value) { + DictDataRespDTO dictData = getDictData(type, String.valueOf(value)).getData(); + if (ObjUtil.isNull(dictData)) { + return StrUtil.EMPTY; + } + return dictData.getLabel(); + } + + @GetMapping(PREFIX + "/parse") + /** 解析获得指定的字典数据 */ + CommonResult parseDictData( + @RequestParam("dictType") String dictType, @RequestParam("label") String label); + + @GetMapping(PREFIX + "/list") + /** 获得指定字典类型的字典数据列表 */ + CommonResult> getDictDataList(@RequestParam("dictType") String dictType); + + /** + * 获得字典数据标签列表 + * + * @param dictType 字典类型 + * @return 字典数据标签列表 + */ + default List getDictDataLabelList(String dictType) { + List list = getDictDataList(dictType).getData(); + return convertList(list, DictDataRespDTO::getLabel); + } +} diff --git a/tashow-feign/tashow-system-api/src/main/java/com/tashow/cloud/systemapi/api/dict/dto/DictDataRespDTO.java b/tashow-feign/tashow-system-api/src/main/java/com/tashow/cloud/systemapi/api/dict/dto/DictDataRespDTO.java new file mode 100644 index 0000000..179149c --- /dev/null +++ b/tashow-feign/tashow-system-api/src/main/java/com/tashow/cloud/systemapi/api/dict/dto/DictDataRespDTO.java @@ -0,0 +1,20 @@ +package com.tashow.cloud.systemapi.api.dict.dto; + +import lombok.Data; + +/** RPC 服务 - 字典数据 Response DTO */ +@Data +public class DictDataRespDTO { + + /** 字典标签", example = "芋道 */ + private String label; + + /** 字典值" */ + private String value; + + /** 字典类型" */ + private String dictType; + + /** 状态" */ + private Integer status; // 参见 CommonStatusEnum 枚举 +} diff --git a/tashow-feign/tashow-system-api/src/main/java/com/tashow/cloud/systemapi/api/logger/LoginLogApi.java b/tashow-feign/tashow-system-api/src/main/java/com/tashow/cloud/systemapi/api/logger/LoginLogApi.java new file mode 100644 index 0000000..bf644c0 --- /dev/null +++ b/tashow-feign/tashow-system-api/src/main/java/com/tashow/cloud/systemapi/api/logger/LoginLogApi.java @@ -0,0 +1,20 @@ +package com.tashow.cloud.systemapi.api.logger; + +import com.tashow.cloud.common.pojo.CommonResult; +import com.tashow.cloud.systemapi.api.logger.dto.LoginLogCreateReqDTO; +import com.tashow.cloud.systemapi.enums.ApiConstants; +import jakarta.validation.Valid; +import org.springframework.cloud.openfeign.FeignClient; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; + +@FeignClient(name = ApiConstants.NAME) // TODO 芋艿:fallbackFactory = +/** RPC 服务 - 登录日志 */ +public interface LoginLogApi { + + String PREFIX = ApiConstants.PREFIX + "/login-log"; + + @PostMapping(PREFIX + "/create") + /** 创建登录日志 */ + CommonResult createLoginLog(@Valid @RequestBody LoginLogCreateReqDTO reqDTO); +} diff --git a/tashow-feign/tashow-system-api/src/main/java/com/tashow/cloud/systemapi/api/logger/OperateLogApi.java b/tashow-feign/tashow-system-api/src/main/java/com/tashow/cloud/systemapi/api/logger/OperateLogApi.java new file mode 100644 index 0000000..baaedf1 --- /dev/null +++ b/tashow-feign/tashow-system-api/src/main/java/com/tashow/cloud/systemapi/api/logger/OperateLogApi.java @@ -0,0 +1,41 @@ +package com.tashow.cloud.systemapi.api.logger; + +import com.tashow.cloud.common.pojo.CommonResult; +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.systemapi.api.logger.dto.OperateLogCreateReqDTO; +import com.tashow.cloud.systemapi.api.logger.dto.OperateLogPageReqDTO; +import com.tashow.cloud.systemapi.api.logger.dto.OperateLogRespDTO; +import com.tashow.cloud.systemapi.enums.ApiConstants; +import jakarta.validation.Valid; +import org.springframework.cloud.openfeign.FeignClient; +import org.springframework.cloud.openfeign.SpringQueryMap; +import org.springframework.scheduling.annotation.Async; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; + +@FeignClient(name = ApiConstants.NAME) // TODO 芋艿:fallbackFactory = +/** RPC 服务 - 操作日志 */ +public interface OperateLogApi { + + String PREFIX = ApiConstants.PREFIX + "/operate-log"; + + @PostMapping(PREFIX + "/create") + /** 创建操作日志 */ + CommonResult createOperateLog(@Valid @RequestBody OperateLogCreateReqDTO createReqDTO); + + /** + * 【异步】创建操作日志 + * + * @param createReqDTO 请求 + */ + @Async + default void createOperateLogAsync(OperateLogCreateReqDTO createReqDTO) { + createOperateLog(createReqDTO).checkError(); + } + + @GetMapping(PREFIX + "/page") + /** 获取指定模块的指定数据的操作日志分页 */ + CommonResult> getOperateLogPage( + @SpringQueryMap OperateLogPageReqDTO pageReqDTO); +} diff --git a/tashow-feign/tashow-system-api/src/main/java/com/tashow/cloud/systemapi/api/logger/dto/LoginLogCreateReqDTO.java b/tashow-feign/tashow-system-api/src/main/java/com/tashow/cloud/systemapi/api/logger/dto/LoginLogCreateReqDTO.java new file mode 100644 index 0000000..d969c1b --- /dev/null +++ b/tashow-feign/tashow-system-api/src/main/java/com/tashow/cloud/systemapi/api/logger/dto/LoginLogCreateReqDTO.java @@ -0,0 +1,55 @@ +package com.tashow.cloud.systemapi.api.logger.dto; + + +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Size; +import lombok.Data; + +/** + * RPC 服务 - 登录日志创建 Request DTO + */ +@Data +public class LoginLogCreateReqDTO { + + /** + * 日志类型,参见 LoginLogTypeEnum 枚举类 + */ + @NotNull(message = "日志类型不能为空") + private Integer logType; + + /** + * 链路追踪编号" + */ + private String traceId; + + /** + * 用户类型 + */ + private Long userId; + @NotNull(message = "用户类型不能为空") + private Integer userType; + /** + * 用户账号 + */ + @Size(max = 30, message = "用户账号长度不能超过30个字符") + private String username; // 不再强制校验 username 非空,因为 Member 社交登录时,此时暂时没有 username(mobile)! + + /** + * 登录结果,参见 LoginResultEnum 枚举类" + */ + @NotNull(message = "登录结果不能为空") + private Integer result; + + /** + * 用户 IP" + */ + @NotEmpty(message = "用户 IP 不能为空") + private String userIp; + + /** + * 浏览器 UserAgent" + */ + private String userAgent; + +} diff --git a/tashow-feign/tashow-system-api/src/main/java/com/tashow/cloud/systemapi/api/logger/dto/OperateLogCreateReqDTO.java b/tashow-feign/tashow-system-api/src/main/java/com/tashow/cloud/systemapi/api/logger/dto/OperateLogCreateReqDTO.java new file mode 100644 index 0000000..03e5e77 --- /dev/null +++ b/tashow-feign/tashow-system-api/src/main/java/com/tashow/cloud/systemapi/api/logger/dto/OperateLogCreateReqDTO.java @@ -0,0 +1,74 @@ +package com.tashow.cloud.systemapi.api.logger.dto; + +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +/** + * RPC 服务 - 系统操作日志 Create Request DTO + */ +@Data +public class OperateLogCreateReqDTO { + + /** + * 链路追踪编号" + */ + private String traceId; + + /** + * 用户编号" + */ + @NotNull(message = "用户编号不能为空") + private Long userId; + /** + * 用户类型 + */ + @NotNull(message = "用户类型不能为空") + private Integer userType; + /** + * 操作模块类型", example = "订单 + */ + @NotEmpty(message = "操作模块类型不能为空") + private String type; + /** + * 操作名", example = "创建订单 + */ + @NotEmpty(message = "操作名不能为空") + private String subType; + /** + * 操作模块业务编号" + */ + @NotNull(message = "操作模块业务编号不能为空") + private Long bizId; + /** + * 操作内容 + */ + @NotEmpty(message = "操作内容不能为空") + private String action; + /** + * 拓展字段 + */ + private String extra; + + /** + * 请求方法名" + */ + @NotEmpty(message = "请求方法名不能为空") + private String requestMethod; + /** + * 请求地址", example = "/order/get + */ + @NotEmpty(message = "请求地址不能为空") + private String requestUrl; + /** + * 用户 IP" + */ + @NotEmpty(message = "用户 IP 不能为空") + private String userIp; + /** + * 浏览器 UserAgent" + */ + @NotEmpty(message = "浏览器 UA 不能为空") + private String userAgent; + +} diff --git a/tashow-feign/tashow-system-api/src/main/java/com/tashow/cloud/systemapi/api/logger/dto/OperateLogPageReqDTO.java b/tashow-feign/tashow-system-api/src/main/java/com/tashow/cloud/systemapi/api/logger/dto/OperateLogPageReqDTO.java new file mode 100644 index 0000000..907b4c9 --- /dev/null +++ b/tashow-feign/tashow-system-api/src/main/java/com/tashow/cloud/systemapi/api/logger/dto/OperateLogPageReqDTO.java @@ -0,0 +1,28 @@ +package com.tashow.cloud.systemapi.api.logger.dto; + +import com.tashow.cloud.common.pojo.PageParam; + +import lombok.Data; + +/** + * RPC 服务 - 操作日志分页 Request DTO + */ +@Data +public class OperateLogPageReqDTO extends PageParam { + + /** + * 模块类型", example = "订单 + */ + private String type; + + /** + * 模块数据编号" + */ + private Long bizId; + + /** + * 用户编号" + */ + private Long userId; + +} diff --git a/tashow-feign/tashow-system-api/src/main/java/com/tashow/cloud/systemapi/api/logger/dto/OperateLogRespDTO.java b/tashow-feign/tashow-system-api/src/main/java/com/tashow/cloud/systemapi/api/logger/dto/OperateLogRespDTO.java new file mode 100644 index 0000000..c9e4062 --- /dev/null +++ b/tashow-feign/tashow-system-api/src/main/java/com/tashow/cloud/systemapi/api/logger/dto/OperateLogRespDTO.java @@ -0,0 +1,83 @@ +package com.tashow.cloud.systemapi.api.logger.dto; + +import com.tashow.cloud.systemapi.api.user.AdminUserApi; +import com.fhs.core.trans.anno.Trans; +import com.fhs.core.trans.constant.TransType; +import com.fhs.core.trans.vo.VO; + +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * RPC 服务 - 系统操作日志 Response DTO + */ +@Data +public class OperateLogRespDTO implements VO { + + /** + * 日志编号" + */ + private Long id; + + /** + * 链路追踪编号" + */ + private String traceId; + /** + * 用户编号" + */ + @Trans(type = TransType.AUTO_TRANS, key = AdminUserApi.PREFIX, fields = "nickname", ref = "userName") + private Long userId; + /** + * 用户名称", example = "芋道 + */ + private String userName; + /** + * 用户类型 + */ + private Integer userType; + /** + * 操作模块类型", example = "订单 + */ + private String type; + /** + * 操作名", example = "创建订单 + */ + private String subType; + /** + * 操作模块业务编号" + */ + private Long bizId; + /** + * 操作内容 + */ + private String action; + /** + * 拓展字段 + */ + private String extra; + + /** + * 请求方法名" + */ + private String requestMethod; + /** + * 请求地址", example = "/order/get + */ + private String requestUrl; + /** + * 用户 IP" + */ + private String userIp; + /** + * 浏览器 UserAgent" + */ + private String userAgent; + + /** + * 创建时间 + */ + private LocalDateTime createTime; + +} diff --git a/tashow-feign/tashow-system-api/src/main/java/com/tashow/cloud/systemapi/api/mail/MailSendApi.java b/tashow-feign/tashow-system-api/src/main/java/com/tashow/cloud/systemapi/api/mail/MailSendApi.java new file mode 100644 index 0000000..bd6936c --- /dev/null +++ b/tashow-feign/tashow-system-api/src/main/java/com/tashow/cloud/systemapi/api/mail/MailSendApi.java @@ -0,0 +1,24 @@ +package com.tashow.cloud.systemapi.api.mail; + +import com.tashow.cloud.common.pojo.CommonResult; +import com.tashow.cloud.systemapi.api.mail.dto.MailSendSingleToUserReqDTO; +import com.tashow.cloud.systemapi.enums.ApiConstants; +import jakarta.validation.Valid; +import org.springframework.cloud.openfeign.FeignClient; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; + +@FeignClient(name = ApiConstants.NAME) // TODO 芋艿:fallbackFactory = +/** RPC 服务 - 邮件发送 */ +public interface MailSendApi { + + String PREFIX = ApiConstants.PREFIX + "/mail/send"; + + @PostMapping(PREFIX + "/send-single-admin") + /** 发送单条邮件给 Admin 用户", description = "在 mail 为空时,使用 userId 加载对应 Admin 的邮箱 */ + CommonResult sendSingleMailToAdmin(@Valid @RequestBody MailSendSingleToUserReqDTO reqDTO); + + @PostMapping(PREFIX + "/send-single-member") + /** 发送单条邮件给 Member 用户", description = "在 mail 为空时,使用 userId 加载对应 Member 的邮箱 */ + CommonResult sendSingleMailToMember(@Valid @RequestBody MailSendSingleToUserReqDTO reqDTO); +} diff --git a/tashow-feign/tashow-system-api/src/main/java/com/tashow/cloud/systemapi/api/mail/dto/MailSendSingleToUserReqDTO.java b/tashow-feign/tashow-system-api/src/main/java/com/tashow/cloud/systemapi/api/mail/dto/MailSendSingleToUserReqDTO.java new file mode 100644 index 0000000..c4d7381 --- /dev/null +++ b/tashow-feign/tashow-system-api/src/main/java/com/tashow/cloud/systemapi/api/mail/dto/MailSendSingleToUserReqDTO.java @@ -0,0 +1,37 @@ +package com.tashow.cloud.systemapi.api.mail.dto; + + +import lombok.Data; + +import jakarta.validation.constraints.Email; +import jakarta.validation.constraints.NotNull; +import java.util.Map; + +/** + * RPC 服务 - 邮件发送给 Admin 或者 Member 用户 Request DTO + */ +@Data +public class MailSendSingleToUserReqDTO { + + /** + * 用户编号 + */ + private Long userId; + /** + * 手机号" + */ + @Email + private String mail; + + /** + * 邮件模板编号" + */ + @NotNull(message = "邮件模板编号不能为空") + private String templateCode; + + /** + * 邮件模板参数 + */ + private Map templateParams; + +} diff --git a/tashow-feign/tashow-system-api/src/main/java/com/tashow/cloud/systemapi/api/notify/NotifyMessageSendApi.java b/tashow-feign/tashow-system-api/src/main/java/com/tashow/cloud/systemapi/api/notify/NotifyMessageSendApi.java new file mode 100644 index 0000000..e0b7288 --- /dev/null +++ b/tashow-feign/tashow-system-api/src/main/java/com/tashow/cloud/systemapi/api/notify/NotifyMessageSendApi.java @@ -0,0 +1,26 @@ +package com.tashow.cloud.systemapi.api.notify; + +import com.tashow.cloud.common.pojo.CommonResult; +import com.tashow.cloud.systemapi.api.notify.dto.NotifySendSingleToUserReqDTO; +import com.tashow.cloud.systemapi.enums.ApiConstants; +import jakarta.validation.Valid; +import org.springframework.cloud.openfeign.FeignClient; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; + +@FeignClient(name = ApiConstants.NAME) // TODO 芋艿:fallbackFactory = +/** RPC 服务 - 站内信发送 */ +public interface NotifyMessageSendApi { + + String PREFIX = ApiConstants.PREFIX + "/notify/send"; + + @PostMapping(PREFIX + "/send-single-admin") + /** 发送单条站内信给 Admin 用户 */ + CommonResult sendSingleMessageToAdmin( + @Valid @RequestBody NotifySendSingleToUserReqDTO reqDTO); + + @PostMapping(PREFIX + "/send-single-member") + /** 发送单条站内信给 Member 用户 */ + CommonResult sendSingleMessageToMember( + @Valid @RequestBody NotifySendSingleToUserReqDTO reqDTO); +} diff --git a/tashow-feign/tashow-system-api/src/main/java/com/tashow/cloud/systemapi/api/notify/dto/NotifySendSingleToUserReqDTO.java b/tashow-feign/tashow-system-api/src/main/java/com/tashow/cloud/systemapi/api/notify/dto/NotifySendSingleToUserReqDTO.java new file mode 100644 index 0000000..4fe349b --- /dev/null +++ b/tashow-feign/tashow-system-api/src/main/java/com/tashow/cloud/systemapi/api/notify/dto/NotifySendSingleToUserReqDTO.java @@ -0,0 +1,31 @@ +package com.tashow.cloud.systemapi.api.notify.dto; + + +import lombok.Data; + +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import java.util.Map; + +/** + * RPC 服务 - 站内信发送给 Admin 或者 Member 用户 Request DTO + */ +@Data +public class NotifySendSingleToUserReqDTO { + + /** + * 用户编号" + */ + @NotNull(message = "用户编号不能为空") + private Long userId; + + /** + * 站内信模板编号" + */ + @NotEmpty(message = "站内信模板编号不能为空") + private String templateCode; + /** + * 邮件模板参数 + */ + private Map templateParams; +} diff --git a/tashow-feign/tashow-system-api/src/main/java/com/tashow/cloud/systemapi/api/oauth2/OAuth2TokenApi.java b/tashow-feign/tashow-system-api/src/main/java/com/tashow/cloud/systemapi/api/oauth2/OAuth2TokenApi.java new file mode 100644 index 0000000..8465708 --- /dev/null +++ b/tashow-feign/tashow-system-api/src/main/java/com/tashow/cloud/systemapi/api/oauth2/OAuth2TokenApi.java @@ -0,0 +1,41 @@ +package com.tashow.cloud.systemapi.api.oauth2; + +import com.tashow.cloud.common.pojo.CommonResult; +import com.tashow.cloud.systemapi.api.oauth2.dto.OAuth2AccessTokenCheckRespDTO; +import com.tashow.cloud.systemapi.api.oauth2.dto.OAuth2AccessTokenCreateReqDTO; +import com.tashow.cloud.systemapi.api.oauth2.dto.OAuth2AccessTokenRespDTO; +import com.tashow.cloud.systemapi.enums.ApiConstants; +import jakarta.validation.Valid; +import org.springframework.cloud.openfeign.FeignClient; +import org.springframework.web.bind.annotation.*; + +@FeignClient(name = ApiConstants.NAME) // TODO 芋艿:fallbackFactory = +/** RPC 服务 - OAuth2.0 令牌 */ +public interface OAuth2TokenApi { + + String PREFIX = ApiConstants.PREFIX + "/oauth2/token"; + + /** 校验 Token 的 URL 地址,主要是提供给 Gateway 使用 */ + @SuppressWarnings("HttpUrlsUsage") + String URL_CHECK = "http://" + ApiConstants.NAME + PREFIX + "/check"; + + @PostMapping(PREFIX + "/create") + /** 创建访问令牌 */ + CommonResult createAccessToken( + @Valid @RequestBody OAuth2AccessTokenCreateReqDTO reqDTO); + + @GetMapping(PREFIX + "/check") + /** 校验访问令牌 */ + CommonResult checkAccessToken( + @RequestParam("accessToken") String accessToken); + + @DeleteMapping(PREFIX + "/remove") + /** 移除访问令牌 */ + CommonResult removeAccessToken( + @RequestParam("accessToken") String accessToken); + + @PutMapping(PREFIX + "/refresh") + /** 刷新访问令牌 */ + CommonResult refreshAccessToken( + @RequestParam("refreshToken") String refreshToken, @RequestParam("clientId") String clientId); +} diff --git a/tashow-feign/tashow-system-api/src/main/java/com/tashow/cloud/systemapi/api/oauth2/dto/OAuth2AccessTokenCheckRespDTO.java b/tashow-feign/tashow-system-api/src/main/java/com/tashow/cloud/systemapi/api/oauth2/dto/OAuth2AccessTokenCheckRespDTO.java new file mode 100644 index 0000000..7503ccc --- /dev/null +++ b/tashow-feign/tashow-system-api/src/main/java/com/tashow/cloud/systemapi/api/oauth2/dto/OAuth2AccessTokenCheckRespDTO.java @@ -0,0 +1,32 @@ +package com.tashow.cloud.systemapi.api.oauth2.dto; + +import java.io.Serializable; +import java.time.LocalDateTime; +import java.util.List; +import java.util.Map; +import lombok.Data; + +/** RPC 服务 - OAuth2 访问令牌的校验 Response DTO */ +@Data +public class OAuth2AccessTokenCheckRespDTO implements Serializable { + + /** 用户编号" */ + private Long userId; + + /** 用户类型,参见 UserTypeEnum 枚举" */ + private Integer userType; + + /** 用户信息 */ + private Map userInfo; + + /** 租户编号" */ + private Long tenantId; + + /** 授权范围的数组 */ + private List scopes; + + /** + * 过期时间 + */ + private LocalDateTime expiresTime; +} diff --git a/tashow-feign/tashow-system-api/src/main/java/com/tashow/cloud/systemapi/api/oauth2/dto/OAuth2AccessTokenCreateReqDTO.java b/tashow-feign/tashow-system-api/src/main/java/com/tashow/cloud/systemapi/api/oauth2/dto/OAuth2AccessTokenCreateReqDTO.java new file mode 100644 index 0000000..2bd2202 --- /dev/null +++ b/tashow-feign/tashow-system-api/src/main/java/com/tashow/cloud/systemapi/api/oauth2/dto/OAuth2AccessTokenCreateReqDTO.java @@ -0,0 +1,42 @@ +package com.tashow.cloud.systemapi.api.oauth2.dto; + +import com.tashow.cloud.common.enums.UserTypeEnum; +import com.tashow.cloud.common.validation.InEnum; + +import lombok.Data; + +import jakarta.validation.constraints.NotNull; +import java.io.Serializable; +import java.util.List; + +/** + * RPC 服务 - OAuth2 访问令牌创建 Request DTO + */ +@Data +public class OAuth2AccessTokenCreateReqDTO implements Serializable { + + /** + * 用户编号" + */ + @NotNull(message = "用户编号不能为空") + private Long userId; + + /** + * 用户类型,参见 UserTypeEnum 枚举" + */ + @NotNull(message = "用户类型不能为空") + @InEnum(value = UserTypeEnum.class, message = "用户类型必须是 {value}") + private Integer userType; + + /** + * 客户端编号" + */ + @NotNull(message = "客户端编号不能为空") + private String clientId; + + /** + * 授权范围的数组 + */ + private List scopes; + +} diff --git a/tashow-feign/tashow-system-api/src/main/java/com/tashow/cloud/systemapi/api/oauth2/dto/OAuth2AccessTokenRespDTO.java b/tashow-feign/tashow-system-api/src/main/java/com/tashow/cloud/systemapi/api/oauth2/dto/OAuth2AccessTokenRespDTO.java new file mode 100644 index 0000000..5ecd0fe --- /dev/null +++ b/tashow-feign/tashow-system-api/src/main/java/com/tashow/cloud/systemapi/api/oauth2/dto/OAuth2AccessTokenRespDTO.java @@ -0,0 +1,42 @@ +package com.tashow.cloud.systemapi.api.oauth2.dto; + + +import lombok.Data; +import lombok.experimental.Accessors; + +import java.io.Serializable; +import java.time.LocalDateTime; + +/** + * RPC 服务 - OAuth2 访问令牌的信息 Response DTO + */ +@Data +@Accessors(chain = true) +public class OAuth2AccessTokenRespDTO implements Serializable { + + /** + * 访问令牌" + */ + private String accessToken; + + /** + * 刷新令牌" + */ + private String refreshToken; + + /** + * 用户编号" + */ + private Long userId; + + /** + * 用户类型 + */ + private Integer userType; + + /** + * 过期时间 + */ + private LocalDateTime expiresTime; + +} diff --git a/tashow-feign/tashow-system-api/src/main/java/com/tashow/cloud/systemapi/api/package-info.java b/tashow-feign/tashow-system-api/src/main/java/com/tashow/cloud/systemapi/api/package-info.java new file mode 100644 index 0000000..e319fce --- /dev/null +++ b/tashow-feign/tashow-system-api/src/main/java/com/tashow/cloud/systemapi/api/package-info.java @@ -0,0 +1,4 @@ +/** + * System API 包,定义暴露给其它模块的 API + */ +package com.tashow.cloud.systemapi.api; diff --git a/tashow-feign/tashow-system-api/src/main/java/com/tashow/cloud/systemapi/api/permission/PermissionApi.java b/tashow-feign/tashow-system-api/src/main/java/com/tashow/cloud/systemapi/api/permission/PermissionApi.java new file mode 100644 index 0000000..0d0d311 --- /dev/null +++ b/tashow-feign/tashow-system-api/src/main/java/com/tashow/cloud/systemapi/api/permission/PermissionApi.java @@ -0,0 +1,37 @@ +package com.tashow.cloud.systemapi.api.permission; + +import com.tashow.cloud.common.pojo.CommonResult; +import com.tashow.cloud.systemapi.api.permission.dto.DeptDataPermissionRespDTO; +import com.tashow.cloud.systemapi.enums.ApiConstants; +import java.util.Collection; +import java.util.Set; +import org.springframework.cloud.openfeign.FeignClient; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestParam; + +@FeignClient(name = ApiConstants.NAME) // TODO 芋艿:fallbackFactory = +/** RPC 服务 - 权限 */ +public interface PermissionApi { + + String PREFIX = ApiConstants.PREFIX + "/permission"; + + @GetMapping(PREFIX + "/user-role-id-list-by-role-id") + /** 获得拥有多个角色的用户编号集合 */ + CommonResult> getUserRoleIdListByRoleIds( + @RequestParam("roleIds") Collection roleIds); + + @GetMapping(PREFIX + "/has-any-permissions") + /** 判断是否有权限,任一一个即可 */ + CommonResult hasAnyPermissions( + @RequestParam("userId") Long userId, @RequestParam("permissions") String... permissions); + + @GetMapping(PREFIX + "/has-any-roles") + /** 判断是否有角色,任一一个即可 */ + CommonResult hasAnyRoles( + @RequestParam("userId") Long userId, @RequestParam("roles") String... roles); + + @GetMapping(PREFIX + "/get-dept-data-permission") + /** 获得登陆用户的部门数据权限 */ + CommonResult getDeptDataPermission( + @RequestParam("userId") Long userId); +} diff --git a/tashow-feign/tashow-system-api/src/main/java/com/tashow/cloud/systemapi/api/permission/RoleApi.java b/tashow-feign/tashow-system-api/src/main/java/com/tashow/cloud/systemapi/api/permission/RoleApi.java new file mode 100644 index 0000000..c438f89 --- /dev/null +++ b/tashow-feign/tashow-system-api/src/main/java/com/tashow/cloud/systemapi/api/permission/RoleApi.java @@ -0,0 +1,19 @@ +package com.tashow.cloud.systemapi.api.permission; + +import com.tashow.cloud.common.pojo.CommonResult; +import com.tashow.cloud.systemapi.enums.ApiConstants; +import java.util.Collection; +import org.springframework.cloud.openfeign.FeignClient; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestParam; + +@FeignClient(name = ApiConstants.NAME) // TODO 芋艿:fallbackFactory = +/** RPC 服务 - 角色 */ +public interface RoleApi { + + String PREFIX = ApiConstants.PREFIX + "/role"; + + @GetMapping(PREFIX + "/valid") + /** 校验角色是否合法 */ + CommonResult validRoleList(@RequestParam("ids") Collection ids); +} diff --git a/tashow-feign/tashow-system-api/src/main/java/com/tashow/cloud/systemapi/api/permission/dto/DeptDataPermissionRespDTO.java b/tashow-feign/tashow-system-api/src/main/java/com/tashow/cloud/systemapi/api/permission/dto/DeptDataPermissionRespDTO.java new file mode 100644 index 0000000..584cd0d --- /dev/null +++ b/tashow-feign/tashow-system-api/src/main/java/com/tashow/cloud/systemapi/api/permission/dto/DeptDataPermissionRespDTO.java @@ -0,0 +1,36 @@ +package com.tashow.cloud.systemapi.api.permission.dto; + + +import lombok.Data; + +import java.util.HashSet; +import java.util.Set; + +/** + * RPC 服务 - 部门的数据权限 Response DTO + */ +@Data +public class DeptDataPermissionRespDTO { + + /** + * 是否可查看全部数据" + */ + private Boolean all; + + /** + * 是否可查看自己的数据" + */ + private Boolean self; + + /** + * 可查看的部门编号数组", example = "[1, 3] + */ + private Set deptIds; + + public DeptDataPermissionRespDTO() { + this.all = false; + this.self = false; + this.deptIds = new HashSet<>(); + } + +} diff --git a/tashow-feign/tashow-system-api/src/main/java/com/tashow/cloud/systemapi/api/sms/SmsCodeApi.java b/tashow-feign/tashow-system-api/src/main/java/com/tashow/cloud/systemapi/api/sms/SmsCodeApi.java new file mode 100644 index 0000000..d233a3a --- /dev/null +++ b/tashow-feign/tashow-system-api/src/main/java/com/tashow/cloud/systemapi/api/sms/SmsCodeApi.java @@ -0,0 +1,32 @@ +package com.tashow.cloud.systemapi.api.sms; + +import com.tashow.cloud.common.pojo.CommonResult; +import com.tashow.cloud.systemapi.api.sms.dto.code.SmsCodeSendReqDTO; +import com.tashow.cloud.systemapi.api.sms.dto.code.SmsCodeUseReqDTO; +import com.tashow.cloud.systemapi.api.sms.dto.code.SmsCodeValidateReqDTO; +import com.tashow.cloud.systemapi.enums.ApiConstants; +import jakarta.validation.Valid; +import org.springframework.cloud.openfeign.FeignClient; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; + +@FeignClient(name = ApiConstants.NAME) // TODO 芋艿:fallbackFactory = +/** RPC 服务 - 短信验证码 */ +public interface SmsCodeApi { + + String PREFIX = ApiConstants.PREFIX + "/oauth2/sms/code"; + + @PostMapping(PREFIX + "/send") + /** 创建短信验证码,并进行发送 */ + CommonResult sendSmsCode(@Valid @RequestBody SmsCodeSendReqDTO reqDTO); + + @PutMapping(PREFIX + "/use") + /** 验证短信验证码,并进行使用 */ + CommonResult useSmsCode(@Valid @RequestBody SmsCodeUseReqDTO reqDTO); + + @GetMapping(PREFIX + "/validate") + /** 检查验证码是否有效 */ + CommonResult validateSmsCode(@Valid @RequestBody SmsCodeValidateReqDTO reqDTO); +} diff --git a/tashow-feign/tashow-system-api/src/main/java/com/tashow/cloud/systemapi/api/sms/SmsSendApi.java b/tashow-feign/tashow-system-api/src/main/java/com/tashow/cloud/systemapi/api/sms/SmsSendApi.java new file mode 100644 index 0000000..5d7881c --- /dev/null +++ b/tashow-feign/tashow-system-api/src/main/java/com/tashow/cloud/systemapi/api/sms/SmsSendApi.java @@ -0,0 +1,24 @@ +package com.tashow.cloud.systemapi.api.sms; + +import com.tashow.cloud.common.pojo.CommonResult; +import com.tashow.cloud.systemapi.api.sms.dto.send.SmsSendSingleToUserReqDTO; +import com.tashow.cloud.systemapi.enums.ApiConstants; +import jakarta.validation.Valid; +import org.springframework.cloud.openfeign.FeignClient; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; + +@FeignClient(name = ApiConstants.NAME) // TODO 芋艿:fallbackFactory = +/** RPC 服务 - 短信发送 */ +public interface SmsSendApi { + + String PREFIX = ApiConstants.PREFIX + "/sms/send"; + + @PostMapping(PREFIX + "/send-single-admin") + /** 发送单条短信给 Admin 用户", description = "在 mobile 为空时,使用 userId 加载对应 Admin 的手机号 */ + CommonResult sendSingleSmsToAdmin(@Valid @RequestBody SmsSendSingleToUserReqDTO reqDTO); + + @PostMapping(PREFIX + "/send-single-member") + /** 发送单条短信给 Member 用户", description = "在 mobile 为空时,使用 userId 加载对应 Member 的手机号 */ + CommonResult sendSingleSmsToMember(@Valid @RequestBody SmsSendSingleToUserReqDTO reqDTO); +} diff --git a/tashow-feign/tashow-system-api/src/main/java/com/tashow/cloud/systemapi/api/sms/dto/code/SmsCodeSendReqDTO.java b/tashow-feign/tashow-system-api/src/main/java/com/tashow/cloud/systemapi/api/sms/dto/code/SmsCodeSendReqDTO.java new file mode 100644 index 0000000..97dfb12 --- /dev/null +++ b/tashow-feign/tashow-system-api/src/main/java/com/tashow/cloud/systemapi/api/sms/dto/code/SmsCodeSendReqDTO.java @@ -0,0 +1,38 @@ +package com.tashow.cloud.systemapi.api.sms.dto.code; + +import com.tashow.cloud.common.validation.InEnum; +import com.tashow.cloud.common.validation.Mobile; +import com.tashow.cloud.systemapi.enums.sms.SmsSceneEnum; + +import lombok.Data; + +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; + +/** + * RPC 服务 - 短信验证码的发送 Request DTO + */ +@Data +public class SmsCodeSendReqDTO { + + /** + * 手机号" + */ + @Mobile + @NotEmpty(message = "手机号不能为空") + private String mobile; + + /** + * 发送场景" + */ + @NotNull(message = "发送场景不能为空") + @InEnum(SmsSceneEnum.class) + + private Integer scene; + /** + * 发送 IP" + */ + @NotEmpty(message = "发送 IP 不能为空") + private String createIp; + +} diff --git a/tashow-feign/tashow-system-api/src/main/java/com/tashow/cloud/systemapi/api/sms/dto/code/SmsCodeUseReqDTO.java b/tashow-feign/tashow-system-api/src/main/java/com/tashow/cloud/systemapi/api/sms/dto/code/SmsCodeUseReqDTO.java new file mode 100644 index 0000000..a1ff0f5 --- /dev/null +++ b/tashow-feign/tashow-system-api/src/main/java/com/tashow/cloud/systemapi/api/sms/dto/code/SmsCodeUseReqDTO.java @@ -0,0 +1,46 @@ +package com.tashow.cloud.systemapi.api.sms.dto.code; + +import com.tashow.cloud.common.validation.InEnum; +import com.tashow.cloud.common.validation.Mobile; +import com.tashow.cloud.systemapi.enums.sms.SmsSceneEnum; +import com.tashow.cloud.systemapi.enums.sms.SmsSceneEnum; +import com.tashow.cloud.systemapi.enums.sms.SmsSceneEnum; + +import lombok.Data; + +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; + +/** + * RPC 服务 - 短信验证码的使用 Request DTO + */ +@Data +public class SmsCodeUseReqDTO { + + /** + * 手机号" + */ + @Mobile + @NotEmpty(message = "手机号不能为空") + private String mobile; + + /** + * 发送场景" + */ + @NotNull(message = "发送场景不能为空") + @InEnum(SmsSceneEnum.class) + private Integer scene; + + /** + * 验证码" + */ + @NotEmpty(message = "验证码") + private String code; + + /** + * 发送 IP" + */ + @NotEmpty(message = "使用 IP 不能为空") + private String usedIp; + +} diff --git a/tashow-feign/tashow-system-api/src/main/java/com/tashow/cloud/systemapi/api/sms/dto/code/SmsCodeValidateReqDTO.java b/tashow-feign/tashow-system-api/src/main/java/com/tashow/cloud/systemapi/api/sms/dto/code/SmsCodeValidateReqDTO.java new file mode 100644 index 0000000..3e31068 --- /dev/null +++ b/tashow-feign/tashow-system-api/src/main/java/com/tashow/cloud/systemapi/api/sms/dto/code/SmsCodeValidateReqDTO.java @@ -0,0 +1,40 @@ +package com.tashow.cloud.systemapi.api.sms.dto.code; + +import com.tashow.cloud.common.validation.InEnum; +import com.tashow.cloud.common.validation.Mobile; +import com.tashow.cloud.systemapi.enums.sms.SmsSceneEnum; +import com.tashow.cloud.systemapi.enums.sms.SmsSceneEnum; +import com.tashow.cloud.systemapi.enums.sms.SmsSceneEnum; + +import lombok.Data; + +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; + +/** + * RPC 服务 - 短信验证码的校验 Request DTO + */ +@Data +public class SmsCodeValidateReqDTO { + + /** + * 手机号" + */ + @Mobile + @NotEmpty(message = "手机号不能为空") + private String mobile; + + /** + * 发送场景" + */ + @NotNull(message = "发送场景不能为空") + @InEnum(SmsSceneEnum.class) + private Integer scene; + + /** + * 验证码" + */ + @NotEmpty(message = "验证码") + private String code; + +} diff --git a/tashow-feign/tashow-system-api/src/main/java/com/tashow/cloud/systemapi/api/sms/dto/send/SmsSendSingleToUserReqDTO.java b/tashow-feign/tashow-system-api/src/main/java/com/tashow/cloud/systemapi/api/sms/dto/send/SmsSendSingleToUserReqDTO.java new file mode 100644 index 0000000..b6937f0 --- /dev/null +++ b/tashow-feign/tashow-system-api/src/main/java/com/tashow/cloud/systemapi/api/sms/dto/send/SmsSendSingleToUserReqDTO.java @@ -0,0 +1,24 @@ +package com.tashow.cloud.systemapi.api.sms.dto.send; + +import com.tashow.cloud.common.validation.Mobile; +import jakarta.validation.constraints.NotEmpty; +import java.util.Map; +import lombok.Data; + +/** RPC 服务 - 短信发送给 Admin 或者 Member 用户 Request DTO */ +@Data +public class SmsSendSingleToUserReqDTO { + + /** 用户编号 */ + private Long userId; + + /** 手机号" */ + @Mobile private String mobile; + + /** 短信模板编号" */ + @NotEmpty(message = "短信模板编号不能为空") + private String templateCode; + + /** 短信模板参数 */ + private Map templateParams; +} diff --git a/tashow-feign/tashow-system-api/src/main/java/com/tashow/cloud/systemapi/api/social/SocialClientApi.java b/tashow-feign/tashow-system-api/src/main/java/com/tashow/cloud/systemapi/api/social/SocialClientApi.java new file mode 100644 index 0000000..1b0c24e --- /dev/null +++ b/tashow-feign/tashow-system-api/src/main/java/com/tashow/cloud/systemapi/api/social/SocialClientApi.java @@ -0,0 +1,51 @@ +package com.tashow.cloud.systemapi.api.social; + +import com.tashow.cloud.common.pojo.CommonResult; +import com.tashow.cloud.systemapi.api.social.dto.*; +import com.tashow.cloud.systemapi.enums.ApiConstants; +import jakarta.validation.Valid; +import java.util.List; +import org.springframework.cloud.openfeign.FeignClient; +import org.springframework.cloud.openfeign.SpringQueryMap; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestParam; + +@FeignClient(name = ApiConstants.NAME) // TODO 芋艿:fallbackFactory = +/** RPC 服务 - 社交应用 */ +public interface SocialClientApi { + + String PREFIX = ApiConstants.PREFIX + "/social-client"; + + @GetMapping(PREFIX + "/get-authorize-url") + /** 获得社交平台的授权 URL */ + CommonResult getAuthorizeUrl( + @RequestParam("socialType") Integer socialType, + @RequestParam("userType") Integer userType, + @RequestParam("redirectUri") String redirectUri); + + @GetMapping(PREFIX + "/create-wx-mp-jsapi-signature") + /** 创建微信公众号 JS SDK 初始化所需的签名 */ + CommonResult createWxMpJsapiSignature( + @RequestParam("userType") Integer userType, @RequestParam("url") String url); + + @GetMapping(PREFIX + "/create-wx-ma-phone-number-info") + /** 获得微信小程序的手机信息 */ + CommonResult getWxMaPhoneNumberInfo( + @RequestParam("userType") Integer userType, @RequestParam("phoneCode") String phoneCode); + + @GetMapping(PREFIX + "/get-wxa-qrcode") + /** 获得小程序二维码 */ + CommonResult getWxaQrcode(@SpringQueryMap SocialWxQrcodeReqDTO reqVO); + + @GetMapping(PREFIX + "/get-wxa-subscribe-template-list") + /** 获得微信小程订阅模板 */ + CommonResult> getWxaSubscribeTemplateList( + @RequestParam("userType") Integer userType); + + @PostMapping(PREFIX + "/send-wxa-subscribe-message") + /** 发送微信小程序订阅消息 */ + CommonResult sendWxaSubscribeMessage( + @Valid @RequestBody SocialWxaSubscribeMessageSendReqDTO reqDTO); +} diff --git a/tashow-feign/tashow-system-api/src/main/java/com/tashow/cloud/systemapi/api/social/SocialUserApi.java b/tashow-feign/tashow-system-api/src/main/java/com/tashow/cloud/systemapi/api/social/SocialUserApi.java new file mode 100644 index 0000000..ffab461 --- /dev/null +++ b/tashow-feign/tashow-system-api/src/main/java/com/tashow/cloud/systemapi/api/social/SocialUserApi.java @@ -0,0 +1,42 @@ +package com.tashow.cloud.systemapi.api.social; + +import com.tashow.cloud.common.pojo.CommonResult; +import com.tashow.cloud.systemapi.api.social.dto.SocialUserBindReqDTO; +import com.tashow.cloud.systemapi.api.social.dto.SocialUserRespDTO; +import com.tashow.cloud.systemapi.api.social.dto.SocialUserUnbindReqDTO; +import com.tashow.cloud.systemapi.enums.ApiConstants; +import jakarta.validation.Valid; +import org.springframework.cloud.openfeign.FeignClient; +import org.springframework.web.bind.annotation.*; + +@FeignClient(name = ApiConstants.NAME) // TODO 芋艿:fallbackFactory = +/** RPC 服务 - 社交用户 */ +public interface SocialUserApi { + + String PREFIX = ApiConstants.PREFIX + "/social-user"; + + @PostMapping(PREFIX + "/bind") + /** 绑定社交用户 */ + CommonResult bindSocialUser(@Valid @RequestBody SocialUserBindReqDTO reqDTO); + + @DeleteMapping(PREFIX + "/unbind") + /** 取消绑定社交用户 */ + CommonResult unbindSocialUser(@Valid @RequestBody SocialUserUnbindReqDTO reqDTO); + + @GetMapping(PREFIX + "/get-by-user-id") + /** 获得社交用户,基于 userId */ + CommonResult getSocialUserByUserId( + @RequestParam("userType") Integer userType, + @RequestParam("userId") Long userId, + @RequestParam("socialType") Integer socialType); + + @GetMapping(PREFIX + "/get-by-code") + /** 获得社交用 */ + // 在认证信息不正确的情况下,也会抛出 {@link ServiceException} 业务异常 + + CommonResult getSocialUserByCode( + @RequestParam("userType") Integer userType, + @RequestParam("socialType") Integer socialType, + @RequestParam("code") String code, + @RequestParam("state") String state); +} diff --git a/tashow-feign/tashow-system-api/src/main/java/com/tashow/cloud/systemapi/api/social/dto/SocialUserBindReqDTO.java b/tashow-feign/tashow-system-api/src/main/java/com/tashow/cloud/systemapi/api/social/dto/SocialUserBindReqDTO.java new file mode 100644 index 0000000..8384bed --- /dev/null +++ b/tashow-feign/tashow-system-api/src/main/java/com/tashow/cloud/systemapi/api/social/dto/SocialUserBindReqDTO.java @@ -0,0 +1,39 @@ +package com.tashow.cloud.systemapi.api.social.dto; + +import com.tashow.cloud.common.enums.UserTypeEnum; +import com.tashow.cloud.common.validation.InEnum; +import com.tashow.cloud.systemapi.enums.social.SocialTypeEnum; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** RPC 服务 - 取消绑定社交用户 Request DTO */ +@Data +@NoArgsConstructor +@AllArgsConstructor +public class SocialUserBindReqDTO { + + /** 用户编号" */ + @NotNull(message = "用户编号不能为空") + private Long userId; + + /** 用户类型" */ + @InEnum(UserTypeEnum.class) + @NotNull(message = "用户类型不能为空") + private Integer userType; + + /** 社交平台的类型" */ + @InEnum(SocialTypeEnum.class) + @NotNull(message = "社交平台的类型不能为空") + private Integer socialType; + + /** 授权码" */ + @NotEmpty(message = "授权码不能为空") + private String code; + + /** state" */ + @NotEmpty(message = "state 不能为空") + private String state; +} diff --git a/tashow-feign/tashow-system-api/src/main/java/com/tashow/cloud/systemapi/api/social/dto/SocialUserRespDTO.java b/tashow-feign/tashow-system-api/src/main/java/com/tashow/cloud/systemapi/api/social/dto/SocialUserRespDTO.java new file mode 100644 index 0000000..470512d --- /dev/null +++ b/tashow-feign/tashow-system-api/src/main/java/com/tashow/cloud/systemapi/api/social/dto/SocialUserRespDTO.java @@ -0,0 +1,24 @@ +package com.tashow.cloud.systemapi.api.social.dto; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** RPC 服务 - 社交用户 Response DTO */ +@Data +@NoArgsConstructor +@AllArgsConstructor +public class SocialUserRespDTO { + + /** 社交用户 openid" */ + private String openid; + + /** 社交用户的昵称", example = "芋道源码 */ + private String nickname; + + /** 社交用户的头像" */ + private String avatar; + + /** 关联的用户编号" */ + private Long userId; +} diff --git a/tashow-feign/tashow-system-api/src/main/java/com/tashow/cloud/systemapi/api/social/dto/SocialUserUnbindReqDTO.java b/tashow-feign/tashow-system-api/src/main/java/com/tashow/cloud/systemapi/api/social/dto/SocialUserUnbindReqDTO.java new file mode 100644 index 0000000..62d198a --- /dev/null +++ b/tashow-feign/tashow-system-api/src/main/java/com/tashow/cloud/systemapi/api/social/dto/SocialUserUnbindReqDTO.java @@ -0,0 +1,48 @@ +package com.tashow.cloud.systemapi.api.social.dto; + +import com.tashow.cloud.common.enums.UserTypeEnum; +import com.tashow.cloud.common.validation.InEnum; +import com.tashow.cloud.systemapi.enums.social.SocialTypeEnum; +import com.tashow.cloud.systemapi.enums.social.SocialTypeEnum; +import com.tashow.cloud.systemapi.enums.social.SocialTypeEnum; + +import lombok.AllArgsConstructor; +import lombok.Data; + +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import lombok.NoArgsConstructor; + +/** + * RPC 服务 - 取消绑定社交用户 Request DTO + */ +@Data +@AllArgsConstructor +@NoArgsConstructor +public class SocialUserUnbindReqDTO { + + /** + * 用户编号" + */ + @NotNull(message = "用户编号不能为空") + private Long userId; + /** + * 用户类型" + */ + @InEnum(UserTypeEnum.class) + @NotNull(message = "用户类型不能为空") + private Integer userType; + + /** + * 社交平台的类型" + */ + @InEnum(SocialTypeEnum.class) + @NotNull(message = "社交平台的类型不能为空") + private Integer socialType; + /** + * 社交平台的 openid" + */ + @NotEmpty(message = "社交平台的 openid 不能为空") + private String openid; + +} diff --git a/tashow-feign/tashow-system-api/src/main/java/com/tashow/cloud/systemapi/api/social/dto/SocialWxJsapiSignatureRespDTO.java b/tashow-feign/tashow-system-api/src/main/java/com/tashow/cloud/systemapi/api/social/dto/SocialWxJsapiSignatureRespDTO.java new file mode 100644 index 0000000..7bb1ec4 --- /dev/null +++ b/tashow-feign/tashow-system-api/src/main/java/com/tashow/cloud/systemapi/api/social/dto/SocialWxJsapiSignatureRespDTO.java @@ -0,0 +1,37 @@ +package com.tashow.cloud.systemapi.api.social.dto; + + +import lombok.Data; + +/** + * RPC 服务 - 微信公众号 JSAPI 签名 Response DTO + */ +@Data +public class SocialWxJsapiSignatureRespDTO { + + /** + * 微信公众号的 appId" + */ + private String appId; + + /** + * 匿名串" + */ + private String nonceStr; + + /** + * 时间戳" + */ + private Long timestamp; + + /** + * URL" + */ + private String url; + + /** + * 签名" + */ + private String signature; + +} diff --git a/tashow-feign/tashow-system-api/src/main/java/com/tashow/cloud/systemapi/api/social/dto/SocialWxPhoneNumberInfoRespDTO.java b/tashow-feign/tashow-system-api/src/main/java/com/tashow/cloud/systemapi/api/social/dto/SocialWxPhoneNumberInfoRespDTO.java new file mode 100644 index 0000000..bfc32b8 --- /dev/null +++ b/tashow-feign/tashow-system-api/src/main/java/com/tashow/cloud/systemapi/api/social/dto/SocialWxPhoneNumberInfoRespDTO.java @@ -0,0 +1,26 @@ +package com.tashow.cloud.systemapi.api.social.dto; + + +import lombok.Data; + +/** + * RPC 服务 - 微信小程序的手机信息 Response DTO + */ +@Data +public class SocialWxPhoneNumberInfoRespDTO { + + /** + * 用户绑定的手机号(国外手机号会有区号)" + */ + private String phoneNumber; + + /** + * 没有区号的手机号" + */ + private String purePhoneNumber; + /** + * 区号" + */ + private String countryCode; + +} diff --git a/tashow-feign/tashow-system-api/src/main/java/com/tashow/cloud/systemapi/api/social/dto/SocialWxQrcodeReqDTO.java b/tashow-feign/tashow-system-api/src/main/java/com/tashow/cloud/systemapi/api/social/dto/SocialWxQrcodeReqDTO.java new file mode 100644 index 0000000..cd02c98 --- /dev/null +++ b/tashow-feign/tashow-system-api/src/main/java/com/tashow/cloud/systemapi/api/social/dto/SocialWxQrcodeReqDTO.java @@ -0,0 +1,52 @@ +package com.tashow.cloud.systemapi.api.social.dto; + +import jakarta.validation.constraints.NotEmpty; +import lombok.Data; + +/** + * @see 获取不限制的小程序码 + */ +/** RPC 服务 - 获得获取小程序码 Request DTO */ +@Data +public class SocialWxQrcodeReqDTO { + + /** 页面路径不能携带参数(参数请放在scene字段里) */ + public static final String SCENE = ""; + + /** 二维码宽度 */ + public static final Integer WIDTH = 430; + + /** 自动配置线条颜色,如果颜色依然是黑色,则说明不建议配置主色调 */ + public static final Boolean AUTO_COLOR = true; + + /** 检查 page 是否存在 */ + public static final Boolean CHECK_PATH = true; + + /** + * 是否需要透明底色 + * + *

hyaline 为 true 时,生成透明底色的小程序码 + */ + public static final Boolean HYALINE = true; + + /** 场景" */ + @NotEmpty(message = "场景不能为空") + private String scene; + + /** 页面路径" */ + @NotEmpty(message = "页面路径不能为空") + private String path; + + /** 二维码宽度 */ + private Integer width; + + /** 是否需要透明底色 */ + private Boolean autoColor; + + /** 是否检查 page 是否存在 */ + private Boolean checkPath; + + /** 是否需要透明底色 */ + private Boolean hyaline; +} diff --git a/tashow-feign/tashow-system-api/src/main/java/com/tashow/cloud/systemapi/api/social/dto/SocialWxaSubscribeMessageSendReqDTO.java b/tashow-feign/tashow-system-api/src/main/java/com/tashow/cloud/systemapi/api/social/dto/SocialWxaSubscribeMessageSendReqDTO.java new file mode 100644 index 0000000..84009d3 --- /dev/null +++ b/tashow-feign/tashow-system-api/src/main/java/com/tashow/cloud/systemapi/api/social/dto/SocialWxaSubscribeMessageSendReqDTO.java @@ -0,0 +1,55 @@ +package com.tashow.cloud.systemapi.api.social.dto; + +import com.tashow.cloud.common.enums.UserTypeEnum; +import com.tashow.cloud.common.validation.InEnum; + +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +import java.util.HashMap; +import java.util.Map; + +/** + * RPC 服务 - 微信小程序订阅消息发送 Request DTO + */ +@Data +public class SocialWxaSubscribeMessageSendReqDTO { + + /** + * 用户编号" + */ + @NotNull(message = "用户编号不能为空") + private Long userId; + /** + * 用户类型" + */ + @InEnum(UserTypeEnum.class) + @NotNull(message = "用户类型不能为空") + private Integer userType; + + /** + * 消息模版标题", example = "模版标题 + */ + @NotEmpty(message = "消息模版标题不能为空") + private String templateTitle; + + /** + * 点击模板卡片后的跳转页面,仅限本小程序内的页面 + */ + private String page; // 支持带参数,(示例 index?foo=bar )。该字段不填则模板无跳转。 + + /** + * 模板内容的参数 + */ + private Map messages; + + public SocialWxaSubscribeMessageSendReqDTO addMessage(String key, String value) { + if (messages == null) { + messages = new HashMap<>(); + } + messages.put(key, value); + return this; + } + +} diff --git a/tashow-feign/tashow-system-api/src/main/java/com/tashow/cloud/systemapi/api/social/dto/SocialWxaSubscribeTemplateRespDTO.java b/tashow-feign/tashow-system-api/src/main/java/com/tashow/cloud/systemapi/api/social/dto/SocialWxaSubscribeTemplateRespDTO.java new file mode 100644 index 0000000..561b679 --- /dev/null +++ b/tashow-feign/tashow-system-api/src/main/java/com/tashow/cloud/systemapi/api/social/dto/SocialWxaSubscribeTemplateRespDTO.java @@ -0,0 +1,23 @@ +package com.tashow.cloud.systemapi.api.social.dto; + +import lombok.Data; + +/** RPC 服务 - 小程序订阅消息模版 Response DTO */ +@Data +public class SocialWxaSubscribeTemplateRespDTO { + + /** 模版编号" */ + private String id; + + /** 模版标题", example = "模版标题 */ + private String title; + + /** 模版内容", example = "模版内容 */ + private String content; + + /** 模板内容示例", example = "模版内容示例 */ + private String example; + + /** 模版类型" */ + private Integer type; // 2:为一次性订阅;3:为长期订阅 +} diff --git a/tashow-feign/tashow-system-api/src/main/java/com/tashow/cloud/systemapi/api/tenant/TenantApi.java b/tashow-feign/tashow-system-api/src/main/java/com/tashow/cloud/systemapi/api/tenant/TenantApi.java new file mode 100644 index 0000000..934901d --- /dev/null +++ b/tashow-feign/tashow-system-api/src/main/java/com/tashow/cloud/systemapi/api/tenant/TenantApi.java @@ -0,0 +1,23 @@ +package com.tashow.cloud.systemapi.api.tenant; + +import com.tashow.cloud.common.pojo.CommonResult; +import com.tashow.cloud.systemapi.enums.ApiConstants; +import java.util.List; +import org.springframework.cloud.openfeign.FeignClient; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestParam; + +@FeignClient(name = ApiConstants.NAME) // TODO 芋艿:fallbackFactory = +/** RPC 服务 - 多租户 */ +public interface TenantApi { + + String PREFIX = ApiConstants.PREFIX + "/tenant"; + + @GetMapping(PREFIX + "/id-list") + /** 获得所有租户编号 */ + CommonResult> getTenantIdList(); + + @GetMapping(PREFIX + "/valid") + /** 校验租户是否合法 */ + CommonResult validTenant(@RequestParam("id") Long id); +} diff --git a/tashow-feign/tashow-system-api/src/main/java/com/tashow/cloud/systemapi/api/user/AdminUserApi.java b/tashow-feign/tashow-system-api/src/main/java/com/tashow/cloud/systemapi/api/user/AdminUserApi.java new file mode 100644 index 0000000..ba5bd17 --- /dev/null +++ b/tashow-feign/tashow-system-api/src/main/java/com/tashow/cloud/systemapi/api/user/AdminUserApi.java @@ -0,0 +1,87 @@ +package com.tashow.cloud.systemapi.api.user; + +import static com.tashow.cloud.systemapi.api.user.AdminUserApi.PREFIX; + +import cn.hutool.core.convert.Convert; +import com.fhs.core.trans.anno.AutoTrans; +import com.tashow.cloud.common.pojo.CommonResult; +import com.tashow.cloud.common.trans.AutoTransable; +import com.tashow.cloud.common.util.collection.CollectionUtils; +import com.tashow.cloud.systemapi.api.user.dto.AdminUserRespDTO; +import com.tashow.cloud.systemapi.enums.ApiConstants; +import feign.FeignIgnore; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import org.springframework.cloud.openfeign.FeignClient; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestParam; + +@FeignClient(name = ApiConstants.NAME) // TODO 芋艿:fallbackFactory = +/** RPC 服务 - 管理员用户 */ +@AutoTrans( + namespace = PREFIX, + fields = {"nickname"}) +public interface AdminUserApi extends AutoTransable { + + String PREFIX = ApiConstants.PREFIX + "/user"; + + @GetMapping(PREFIX + "/get") + /** 通过用户 ID 查询用户 */ + CommonResult getUser(@RequestParam("id") Long id); + + @GetMapping(PREFIX + "/list-by-subordinate") + /** 通过用户 ID 查询用户下属 */ + CommonResult> getUserListBySubordinate(@RequestParam("id") Long id); + + @GetMapping(PREFIX + "/list") + /** 通过用户 ID 查询用户们 */ + CommonResult> getUserList(@RequestParam("ids") Collection ids); + + @GetMapping(PREFIX + "/list-by-dept-id") + /** 获得指定部门的用户数组 */ + CommonResult> getUserListByDeptIds( + @RequestParam("deptIds") Collection deptIds); + + @GetMapping(PREFIX + "/list-by-post-id") + /** 获得指定岗位的用户数组 */ + CommonResult> getUserListByPostIds( + @RequestParam("postIds") Collection postIds); + + /** + * 获得用户 Map + * + * @param ids 用户编号数组 + * @return 用户 Map + */ + default Map getUserMap(Collection ids) { + List users = getUserList(ids).getCheckedData(); + return CollectionUtils.convertMap(users, AdminUserRespDTO::getId); + } + + /** + * 校验用户是否有效。如下情况,视为无效: 1. 用户编号不存在 2. 用户被禁用 + * + * @param id 用户编号 + */ + default void validateUser(Long id) { + validateUserList(Collections.singleton(id)); + } + + @GetMapping(PREFIX + "/valid") + /** 校验用户们是否有效 */ + CommonResult validateUserList(@RequestParam("ids") Collection ids); + + @Override + @FeignIgnore + default List selectByIds(List ids) { + return getUserList(Convert.toList(Long.class, ids)).getCheckedData(); + } + + @Override + @FeignIgnore + default AdminUserRespDTO selectById(Object id) { + return getUser(Convert.toLong(id)).getCheckedData(); + } +} diff --git a/tashow-feign/tashow-system-api/src/main/java/com/tashow/cloud/systemapi/api/user/dto/AdminUserRespDTO.java b/tashow-feign/tashow-system-api/src/main/java/com/tashow/cloud/systemapi/api/user/dto/AdminUserRespDTO.java new file mode 100644 index 0000000..c93bafe --- /dev/null +++ b/tashow-feign/tashow-system-api/src/main/java/com/tashow/cloud/systemapi/api/user/dto/AdminUserRespDTO.java @@ -0,0 +1,50 @@ +package com.tashow.cloud.systemapi.api.user.dto; + +import com.fhs.core.trans.vo.VO; + +import lombok.Data; + +import java.util.Set; + +/** + * RPC 服务 - Admin 用户 Response DTO + */ +@Data +public class AdminUserRespDTO implements VO { + + /** + * 用户 ID" + */ + private Long id; + + /** + * 用户昵称", example = "小王 + */ + private String nickname; + + /** + * 帐号状态" + */ + private Integer status; // 参见 CommonStatusEnum 枚举 + + /** + * 部门编号" + */ + private Long deptId; + + /** + * 岗位编号数组", example = "[1, 3] + */ + private Set postIds; + + /** + * 手机号码" + */ + private String mobile; + + /** + * 用户头像" + */ + private String avatar; + +} diff --git a/tashow-feign/tashow-system-api/src/main/java/com/tashow/cloud/systemapi/enums/ApiConstants.java b/tashow-feign/tashow-system-api/src/main/java/com/tashow/cloud/systemapi/enums/ApiConstants.java new file mode 100644 index 0000000..e1fa9c7 --- /dev/null +++ b/tashow-feign/tashow-system-api/src/main/java/com/tashow/cloud/systemapi/enums/ApiConstants.java @@ -0,0 +1,24 @@ +package com.tashow.cloud.systemapi.enums; + + +import com.tashow.cloud.common.enums.RpcConstants; + +/** + * API 相关的枚举 + * + * @author 芋道源码 + */ +public class ApiConstants { + + /** + * 服务名 + * + * 注意,需要保证和 spring.application.name 保持一致 + */ + public static final String NAME = "system-server"; + + public static final String PREFIX = RpcConstants.RPC_API_PREFIX + "/system"; + + public static final String VERSION = "1.0.0"; + +} diff --git a/tashow-feign/tashow-system-api/src/main/java/com/tashow/cloud/systemapi/enums/DictTypeConstants.java b/tashow-feign/tashow-system-api/src/main/java/com/tashow/cloud/systemapi/enums/DictTypeConstants.java new file mode 100644 index 0000000..15e4af7 --- /dev/null +++ b/tashow-feign/tashow-system-api/src/main/java/com/tashow/cloud/systemapi/enums/DictTypeConstants.java @@ -0,0 +1,26 @@ +package com.tashow.cloud.systemapi.enums; + +/** + * System 字典类型的枚举类 + * + * @author 芋道源码 + */ +public interface DictTypeConstants { + + String USER_TYPE = "user_type"; // 用户类型 + String COMMON_STATUS = "common_status"; // 系统状态 + + // ========== SYSTEM 模块 ========== + + String USER_SEX = "system_user_sex"; // 用户性别 + String DATA_SCOPE = "system_data_scope"; // 数据范围 + + String LOGIN_TYPE = "system_login_type"; // 登录日志的类型 + String LOGIN_RESULT = "system_login_result"; // 登录结果 + + String SMS_CHANNEL_CODE = "system_sms_channel_code"; // 短信渠道编码 + String SMS_TEMPLATE_TYPE = "system_sms_template_type"; // 短信模板类型 + String SMS_SEND_STATUS = "system_sms_send_status"; // 短信发送状态 + String SMS_RECEIVE_STATUS = "system_sms_receive_status"; // 短信接收状态 + +} diff --git a/tashow-feign/tashow-system-api/src/main/java/com/tashow/cloud/systemapi/enums/ErrorCodeConstants.java b/tashow-feign/tashow-system-api/src/main/java/com/tashow/cloud/systemapi/enums/ErrorCodeConstants.java new file mode 100644 index 0000000..8b7c00c --- /dev/null +++ b/tashow-feign/tashow-system-api/src/main/java/com/tashow/cloud/systemapi/enums/ErrorCodeConstants.java @@ -0,0 +1,169 @@ +package com.tashow.cloud.systemapi.enums; + + +import com.tashow.cloud.common.exception.ErrorCode; + +/** + * System 错误码枚举类 + * + * system 系统,使用 1-002-000-000 段 + */ +public interface ErrorCodeConstants { + + // ========== AUTH 模块 1-002-000-000 ========== + ErrorCode AUTH_LOGIN_BAD_CREDENTIALS = new ErrorCode(1_002_000_000, "登录失败,账号密码不正确"); + ErrorCode AUTH_LOGIN_USER_DISABLED = new ErrorCode(1_002_000_001, "登录失败,账号被禁用"); + ErrorCode AUTH_LOGIN_CAPTCHA_CODE_ERROR = new ErrorCode(1_002_000_004, "验证码不正确,原因:{}"); + ErrorCode AUTH_THIRD_LOGIN_NOT_BIND = new ErrorCode(1_002_000_005, "未绑定账号,需要进行绑定"); + ErrorCode AUTH_MOBILE_NOT_EXISTS = new ErrorCode(1_002_000_007, "手机号不存在"); + ErrorCode AUTH_REGISTER_CAPTCHA_CODE_ERROR = new ErrorCode(1_002_000_008, "验证码不正确,原因:{}"); + + // ========== 菜单模块 1-002-001-000 ========== + ErrorCode MENU_NAME_DUPLICATE = new ErrorCode(1_002_001_000, "已经存在该名字的菜单"); + ErrorCode MENU_PARENT_NOT_EXISTS = new ErrorCode(1_002_001_001, "父菜单不存在"); + ErrorCode MENU_PARENT_ERROR = new ErrorCode(1_002_001_002, "不能设置自己为父菜单"); + ErrorCode MENU_NOT_EXISTS = new ErrorCode(1_002_001_003, "菜单不存在"); + ErrorCode MENU_EXISTS_CHILDREN = new ErrorCode(1_002_001_004, "存在子菜单,无法删除"); + ErrorCode MENU_PARENT_NOT_DIR_OR_MENU = new ErrorCode(1_002_001_005, "父菜单的类型必须是目录或者菜单"); + + // ========== 角色模块 1-002-002-000 ========== + ErrorCode ROLE_NOT_EXISTS = new ErrorCode(1_002_002_000, "角色不存在"); + ErrorCode ROLE_NAME_DUPLICATE = new ErrorCode(1_002_002_001, "已经存在名为【{}】的角色"); + ErrorCode ROLE_CODE_DUPLICATE = new ErrorCode(1_002_002_002, "已经存在标识为【{}】的角色"); + ErrorCode ROLE_CAN_NOT_UPDATE_SYSTEM_TYPE_ROLE = new ErrorCode(1_002_002_003, "不能操作类型为系统内置的角色"); + ErrorCode ROLE_IS_DISABLE = new ErrorCode(1_002_002_004, "名字为【{}】的角色已被禁用"); + ErrorCode ROLE_ADMIN_CODE_ERROR = new ErrorCode(1_002_002_005, "标识【{}】不能使用"); + + // ========== 用户模块 1-002-003-000 ========== + ErrorCode USER_USERNAME_EXISTS = new ErrorCode(1_002_003_000, "用户账号已经存在"); + ErrorCode USER_MOBILE_EXISTS = new ErrorCode(1_002_003_001, "手机号已经存在"); + ErrorCode USER_EMAIL_EXISTS = new ErrorCode(1_002_003_002, "邮箱已经存在"); + ErrorCode USER_NOT_EXISTS = new ErrorCode(1_002_003_003, "用户不存在"); + ErrorCode USER_IMPORT_LIST_IS_EMPTY = new ErrorCode(1_002_003_004, "导入用户数据不能为空!"); + ErrorCode USER_PASSWORD_FAILED = new ErrorCode(1_002_003_005, "用户密码校验失败"); + ErrorCode USER_IS_DISABLE = new ErrorCode(1_002_003_006, "名字为【{}】的用户已被禁用"); + ErrorCode USER_COUNT_MAX = new ErrorCode(1_002_003_008, "创建用户失败,原因:超过租户最大租户配额({})!"); + ErrorCode USER_IMPORT_INIT_PASSWORD = new ErrorCode(1_002_003_009, "初始密码不能为空"); + ErrorCode USER_MOBILE_NOT_EXISTS = new ErrorCode(1_002_003_010, "该手机号尚未注册"); + + // ========== 部门模块 1-002-004-000 ========== + ErrorCode DEPT_NAME_DUPLICATE = new ErrorCode(1_002_004_000, "已经存在该名字的部门"); + ErrorCode DEPT_PARENT_NOT_EXITS = new ErrorCode(1_002_004_001,"父级部门不存在"); + ErrorCode DEPT_NOT_FOUND = new ErrorCode(1_002_004_002, "当前部门不存在"); + ErrorCode DEPT_EXITS_CHILDREN = new ErrorCode(1_002_004_003, "存在子部门,无法删除"); + ErrorCode DEPT_PARENT_ERROR = new ErrorCode(1_002_004_004, "不能设置自己为父部门"); + ErrorCode DEPT_NOT_ENABLE = new ErrorCode(1_002_004_006, "部门({})不处于开启状态,不允许选择"); + ErrorCode DEPT_PARENT_IS_CHILD = new ErrorCode(1_002_004_007, "不能设置自己的子部门为父部门"); + + // ========== 岗位模块 1-002-005-000 ========== + ErrorCode POST_NOT_FOUND = new ErrorCode(1_002_005_000, "当前岗位不存在"); + ErrorCode POST_NOT_ENABLE = new ErrorCode(1_002_005_001, "岗位({}) 不处于开启状态,不允许选择"); + ErrorCode POST_NAME_DUPLICATE = new ErrorCode(1_002_005_002, "已经存在该名字的岗位"); + ErrorCode POST_CODE_DUPLICATE = new ErrorCode(1_002_005_003, "已经存在该标识的岗位"); + + // ========== 字典类型 1-002-006-000 ========== + ErrorCode DICT_TYPE_NOT_EXISTS = new ErrorCode(1_002_006_001, "当前字典类型不存在"); + ErrorCode DICT_TYPE_NOT_ENABLE = new ErrorCode(1_002_006_002, "字典类型不处于开启状态,不允许选择"); + ErrorCode DICT_TYPE_NAME_DUPLICATE = new ErrorCode(1_002_006_003, "已经存在该名字的字典类型"); + ErrorCode DICT_TYPE_TYPE_DUPLICATE = new ErrorCode(1_002_006_004, "已经存在该类型的字典类型"); + ErrorCode DICT_TYPE_HAS_CHILDREN = new ErrorCode(1_002_006_005, "无法删除,该字典类型还有字典数据"); + + // ========== 字典数据 1-002-007-000 ========== + ErrorCode DICT_DATA_NOT_EXISTS = new ErrorCode(1_002_007_001, "当前字典数据不存在"); + ErrorCode DICT_DATA_NOT_ENABLE = new ErrorCode(1_002_007_002, "字典数据({})不处于开启状态,不允许选择"); + ErrorCode DICT_DATA_VALUE_DUPLICATE = new ErrorCode(1_002_007_003, "已经存在该值的字典数据"); + + // ========== 通知公告 1-002-008-000 ========== + ErrorCode NOTICE_NOT_FOUND = new ErrorCode(1_002_008_001, "当前通知公告不存在"); + + // ========== 短信渠道 1-002-011-000 ========== + ErrorCode SMS_CHANNEL_NOT_EXISTS = new ErrorCode(1_002_011_000, "短信渠道不存在"); + ErrorCode SMS_CHANNEL_DISABLE = new ErrorCode(1_002_011_001, "短信渠道不处于开启状态,不允许选择"); + ErrorCode SMS_CHANNEL_HAS_CHILDREN = new ErrorCode(1_002_011_002, "无法删除,该短信渠道还有短信模板"); + + // ========== 短信模板 1-002-012-000 ========== + ErrorCode SMS_TEMPLATE_NOT_EXISTS = new ErrorCode(1_002_012_000, "短信模板不存在"); + ErrorCode SMS_TEMPLATE_CODE_DUPLICATE = new ErrorCode(1_002_012_001, "已经存在编码为【{}】的短信模板"); + ErrorCode SMS_TEMPLATE_API_ERROR = new ErrorCode(1_002_012_002, "短信 API 模板调用失败,原因是:{}"); + ErrorCode SMS_TEMPLATE_API_AUDIT_CHECKING = new ErrorCode(1_002_012_003, "短信 API 模版无法使用,原因:审批中"); + ErrorCode SMS_TEMPLATE_API_AUDIT_FAIL = new ErrorCode(1_002_012_004, "短信 API 模版无法使用,原因:审批不通过,{}"); + ErrorCode SMS_TEMPLATE_API_NOT_FOUND = new ErrorCode(1_002_012_005, "短信 API 模版无法使用,原因:模版不存在"); + + // ========== 短信发送 1-002-013-000 ========== + ErrorCode SMS_SEND_MOBILE_NOT_EXISTS = new ErrorCode(1_002_013_000, "手机号不存在"); + ErrorCode SMS_SEND_MOBILE_TEMPLATE_PARAM_MISS = new ErrorCode(1_002_013_001, "模板参数({})缺失"); + ErrorCode SMS_SEND_TEMPLATE_NOT_EXISTS = new ErrorCode(1_002_013_002, "短信模板不存在"); + + // ========== 短信验证码 1-002-014-000 ========== + ErrorCode SMS_CODE_NOT_FOUND = new ErrorCode(1_002_014_000, "验证码不存在"); + ErrorCode SMS_CODE_EXPIRED = new ErrorCode(1_002_014_001, "验证码已过期"); + ErrorCode SMS_CODE_USED = new ErrorCode(1_002_014_002, "验证码已使用"); + ErrorCode SMS_CODE_EXCEED_SEND_MAXIMUM_QUANTITY_PER_DAY = new ErrorCode(1_002_014_004, "超过每日短信发送数量"); + ErrorCode SMS_CODE_SEND_TOO_FAST = new ErrorCode(1_002_014_005, "短信发送过于频繁"); + + // ========== 租户信息 1-002-015-000 ========== + ErrorCode TENANT_NOT_EXISTS = new ErrorCode(1_002_015_000, "租户不存在"); + ErrorCode TENANT_DISABLE = new ErrorCode(1_002_015_001, "名字为【{}】的租户已被禁用"); + ErrorCode TENANT_EXPIRE = new ErrorCode(1_002_015_002, "名字为【{}】的租户已过期"); + ErrorCode TENANT_CAN_NOT_UPDATE_SYSTEM = new ErrorCode(1_002_015_003, "系统租户不能进行修改、删除等操作!"); + ErrorCode TENANT_NAME_DUPLICATE = new ErrorCode(1_002_015_004, "名字为【{}】的租户已存在"); + ErrorCode TENANT_WEBSITE_DUPLICATE = new ErrorCode(1_002_015_005, "域名为【{}】的租户已存在"); + + // ========== 租户套餐 1-002-016-000 ========== + ErrorCode TENANT_PACKAGE_NOT_EXISTS = new ErrorCode(1_002_016_000, "租户套餐不存在"); + ErrorCode TENANT_PACKAGE_USED = new ErrorCode(1_002_016_001, "租户正在使用该套餐,请给租户重新设置套餐后再尝试删除"); + ErrorCode TENANT_PACKAGE_DISABLE = new ErrorCode(1_002_016_002, "名字为【{}】的租户套餐已被禁用"); + ErrorCode TENANT_PACKAGE_NAME_DUPLICATE = new ErrorCode(1_002_016_003, "已经存在该名字的租户套餐"); + + // ========== 社交用户 1-002-018-000 ========== + ErrorCode SOCIAL_USER_AUTH_FAILURE = new ErrorCode(1_002_018_000, "社交授权失败,原因是:{}"); + ErrorCode SOCIAL_USER_NOT_FOUND = new ErrorCode(1_002_018_001, "社交授权失败,找不到对应的用户"); + + ErrorCode SOCIAL_CLIENT_WEIXIN_MINI_APP_PHONE_CODE_ERROR = new ErrorCode(1_002_018_200, "获得手机号失败"); + ErrorCode SOCIAL_CLIENT_WEIXIN_MINI_APP_QRCODE_ERROR = new ErrorCode(1_002_018_201, "获得小程序码失败"); + ErrorCode SOCIAL_CLIENT_WEIXIN_MINI_APP_SUBSCRIBE_TEMPLATE_ERROR = new ErrorCode(1_002_018_202, "获得小程序订阅消息模版失败"); + ErrorCode SOCIAL_CLIENT_WEIXIN_MINI_APP_SUBSCRIBE_MESSAGE_ERROR = new ErrorCode(1_002_018_203, "发送小程序订阅消息失败"); + ErrorCode SOCIAL_CLIENT_NOT_EXISTS = new ErrorCode(1_002_018_210, "社交客户端不存在"); + ErrorCode SOCIAL_CLIENT_UNIQUE = new ErrorCode(1_002_018_211, "社交客户端已存在配置"); + + + // ========== OAuth2 客户端 1-002-020-000 ========= + ErrorCode OAUTH2_CLIENT_NOT_EXISTS = new ErrorCode(1_002_020_000, "OAuth2 客户端不存在"); + ErrorCode OAUTH2_CLIENT_EXISTS = new ErrorCode(1_002_020_001, "OAuth2 客户端编号已存在"); + ErrorCode OAUTH2_CLIENT_DISABLE = new ErrorCode(1_002_020_002, "OAuth2 客户端已禁用"); + ErrorCode OAUTH2_CLIENT_AUTHORIZED_GRANT_TYPE_NOT_EXISTS = new ErrorCode(1_002_020_003, "不支持该授权类型"); + ErrorCode OAUTH2_CLIENT_SCOPE_OVER = new ErrorCode(1_002_020_004, "授权范围过大"); + ErrorCode OAUTH2_CLIENT_REDIRECT_URI_NOT_MATCH = new ErrorCode(1_002_020_005, "无效 redirect_uri: {}"); + ErrorCode OAUTH2_CLIENT_CLIENT_SECRET_ERROR = new ErrorCode(1_002_020_006, "无效 client_secret: {}"); + + // ========== OAuth2 授权 1-002-021-000 ========= + ErrorCode OAUTH2_GRANT_CLIENT_ID_MISMATCH = new ErrorCode(1_002_021_000, "client_id 不匹配"); + ErrorCode OAUTH2_GRANT_REDIRECT_URI_MISMATCH = new ErrorCode(1_002_021_001, "redirect_uri 不匹配"); + ErrorCode OAUTH2_GRANT_STATE_MISMATCH = new ErrorCode(1_002_021_002, "state 不匹配"); + + // ========== OAuth2 授权 1-002-022-000 ========= + ErrorCode OAUTH2_CODE_NOT_EXISTS = new ErrorCode(1_002_022_000, "code 不存在"); + ErrorCode OAUTH2_CODE_EXPIRE = new ErrorCode(1_002_022_001, "code 已过期"); + + // ========== 邮箱账号 1-002-023-000 ========== + ErrorCode MAIL_ACCOUNT_NOT_EXISTS = new ErrorCode(1_002_023_000, "邮箱账号不存在"); + ErrorCode MAIL_ACCOUNT_RELATE_TEMPLATE_EXISTS = new ErrorCode(1_002_023_001, "无法删除,该邮箱账号还有邮件模板"); + + // ========== 邮件模版 1-002-024-000 ========== + ErrorCode MAIL_TEMPLATE_NOT_EXISTS = new ErrorCode(1_002_024_000, "邮件模版不存在"); + ErrorCode MAIL_TEMPLATE_CODE_EXISTS = new ErrorCode(1_002_024_001, "邮件模版 code({}) 已存在"); + + // ========== 邮件发送 1-002-025-000 ========== + ErrorCode MAIL_SEND_TEMPLATE_PARAM_MISS = new ErrorCode(1_002_025_000, "模板参数({})缺失"); + ErrorCode MAIL_SEND_MAIL_NOT_EXISTS = new ErrorCode(1_002_025_001, "邮箱不存在"); + + // ========== 站内信模版 1-002-026-000 ========== + ErrorCode NOTIFY_TEMPLATE_NOT_EXISTS = new ErrorCode(1_002_026_000, "站内信模版不存在"); + ErrorCode NOTIFY_TEMPLATE_CODE_DUPLICATE = new ErrorCode(1_002_026_001, "已经存在编码为【{}】的站内信模板"); + + // ========== 站内信模版 1-002-027-000 ========== + + // ========== 站内信发送 1-002-028-000 ========== + ErrorCode NOTIFY_SEND_TEMPLATE_PARAM_MISS = new ErrorCode(1_002_028_000, "模板参数({})缺失"); + +} diff --git a/tashow-feign/tashow-system-api/src/main/java/com/tashow/cloud/systemapi/enums/LogRecordConstants.java b/tashow-feign/tashow-system-api/src/main/java/com/tashow/cloud/systemapi/enums/LogRecordConstants.java new file mode 100644 index 0000000..e1dd9a4 --- /dev/null +++ b/tashow-feign/tashow-system-api/src/main/java/com/tashow/cloud/systemapi/enums/LogRecordConstants.java @@ -0,0 +1,33 @@ +package com.tashow.cloud.systemapi.enums; + +/** + * System 操作日志枚举 + * 目的:统一管理,也减少 Service 里各种“复杂”字符串 + * + * @author 芋道源码 + */ +public interface LogRecordConstants { + + // ======================= SYSTEM_USER 用户 ======================= + + String SYSTEM_USER_TYPE = "SYSTEM 用户"; + String SYSTEM_USER_CREATE_SUB_TYPE = "创建用户"; + String SYSTEM_USER_CREATE_SUCCESS = "创建了用户【{{#user.nickname}}】"; + String SYSTEM_USER_UPDATE_SUB_TYPE = "更新用户"; + String SYSTEM_USER_UPDATE_SUCCESS = "更新了用户【{{#user.nickname}}】: {_DIFF{#updateReqVO}}"; + String SYSTEM_USER_DELETE_SUB_TYPE = "删除用户"; + String SYSTEM_USER_DELETE_SUCCESS = "删除了用户【{{#user.nickname}}】"; + String SYSTEM_USER_UPDATE_PASSWORD_SUB_TYPE = "重置用户密码"; + String SYSTEM_USER_UPDATE_PASSWORD_SUCCESS = "将用户【{{#user.nickname}}】的密码从【{{#user.password}}】重置为【{{#newPassword}}】"; + + // ======================= SYSTEM_ROLE 角色 ======================= + + String SYSTEM_ROLE_TYPE = "SYSTEM 角色"; + String SYSTEM_ROLE_CREATE_SUB_TYPE = "创建角色"; + String SYSTEM_ROLE_CREATE_SUCCESS = "创建了角色【{{#role.name}}】"; + String SYSTEM_ROLE_UPDATE_SUB_TYPE = "更新角色"; + String SYSTEM_ROLE_UPDATE_SUCCESS = "更新了角色【{{#role.name}}】: {_DIFF{#updateReqVO}}"; + String SYSTEM_ROLE_DELETE_SUB_TYPE = "删除角色"; + String SYSTEM_ROLE_DELETE_SUCCESS = "删除了角色【{{#role.name}}】"; + +} diff --git a/tashow-feign/tashow-system-api/src/main/java/com/tashow/cloud/systemapi/enums/common/SexEnum.java b/tashow-feign/tashow-system-api/src/main/java/com/tashow/cloud/systemapi/enums/common/SexEnum.java new file mode 100644 index 0000000..f01466d --- /dev/null +++ b/tashow-feign/tashow-system-api/src/main/java/com/tashow/cloud/systemapi/enums/common/SexEnum.java @@ -0,0 +1,27 @@ +package com.tashow.cloud.systemapi.enums.common; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * 性别的枚举值 + * + * @author 芋道源码 + */ +@Getter +@AllArgsConstructor +public enum SexEnum { + + /** 男 */ + MALE(1), + /** 女 */ + FEMALE(2), + /* 未知 */ + UNKNOWN(0); + + /** + * 性别 + */ + private final Integer sex; + +} diff --git a/tashow-feign/tashow-system-api/src/main/java/com/tashow/cloud/systemapi/enums/logger/LoginLogTypeEnum.java b/tashow-feign/tashow-system-api/src/main/java/com/tashow/cloud/systemapi/enums/logger/LoginLogTypeEnum.java new file mode 100644 index 0000000..8779293 --- /dev/null +++ b/tashow-feign/tashow-system-api/src/main/java/com/tashow/cloud/systemapi/enums/logger/LoginLogTypeEnum.java @@ -0,0 +1,27 @@ +package com.tashow.cloud.systemapi.enums.logger; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * 登录日志的类型枚举 + */ +@Getter +@AllArgsConstructor +public enum LoginLogTypeEnum { + + LOGIN_USERNAME(100), // 使用账号登录 + LOGIN_SOCIAL(101), // 使用社交登录 + LOGIN_MOBILE(103), // 使用手机登陆 + LOGIN_SMS(104), // 使用短信登陆 + + LOGOUT_SELF(200), // 自己主动登出 + LOGOUT_DELETE(202), // 强制退出 + ; + + /** + * 日志类型 + */ + private final Integer type; + +} diff --git a/tashow-feign/tashow-system-api/src/main/java/com/tashow/cloud/systemapi/enums/logger/LoginResultEnum.java b/tashow-feign/tashow-system-api/src/main/java/com/tashow/cloud/systemapi/enums/logger/LoginResultEnum.java new file mode 100644 index 0000000..fac1d52 --- /dev/null +++ b/tashow-feign/tashow-system-api/src/main/java/com/tashow/cloud/systemapi/enums/logger/LoginResultEnum.java @@ -0,0 +1,26 @@ +package com.tashow.cloud.systemapi.enums.logger; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * 登录结果的枚举类 + */ +@Getter +@AllArgsConstructor +public enum LoginResultEnum { + + SUCCESS(0), // 成功 + BAD_CREDENTIALS(10), // 账号或密码不正确 + USER_DISABLED(20), // 用户被禁用 + CAPTCHA_NOT_FOUND(30), // 图片验证码不存在 + CAPTCHA_CODE_ERROR(31), // 图片验证码不正确 + + ; + + /** + * 结果 + */ + private final Integer result; + +} diff --git a/tashow-feign/tashow-system-api/src/main/java/com/tashow/cloud/systemapi/enums/mail/MailSendStatusEnum.java b/tashow-feign/tashow-system-api/src/main/java/com/tashow/cloud/systemapi/enums/mail/MailSendStatusEnum.java new file mode 100644 index 0000000..477c3f7 --- /dev/null +++ b/tashow-feign/tashow-system-api/src/main/java/com/tashow/cloud/systemapi/enums/mail/MailSendStatusEnum.java @@ -0,0 +1,24 @@ +package com.tashow.cloud.systemapi.enums.mail; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * 邮件的发送状态枚举 + * + * @author wangjingyi + * @since 2022/4/10 13:39 + */ +@Getter +@AllArgsConstructor +public enum MailSendStatusEnum { + + INIT(0), // 初始化 + SUCCESS(10), // 发送成功 + FAILURE(20), // 发送失败 + IGNORE(30), // 忽略,即不发送 + ; + + private final int status; + +} diff --git a/tashow-feign/tashow-system-api/src/main/java/com/tashow/cloud/systemapi/enums/notice/NoticeTypeEnum.java b/tashow-feign/tashow-system-api/src/main/java/com/tashow/cloud/systemapi/enums/notice/NoticeTypeEnum.java new file mode 100644 index 0000000..bea5f6e --- /dev/null +++ b/tashow-feign/tashow-system-api/src/main/java/com/tashow/cloud/systemapi/enums/notice/NoticeTypeEnum.java @@ -0,0 +1,23 @@ +package com.tashow.cloud.systemapi.enums.notice; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * 通知类型 + * + * @author 芋道源码 + */ +@Getter +@AllArgsConstructor +public enum NoticeTypeEnum { + + NOTICE(1), + ANNOUNCEMENT(2); + + /** + * 类型 + */ + private final Integer type; + +} diff --git a/tashow-feign/tashow-system-api/src/main/java/com/tashow/cloud/systemapi/enums/notify/NotifyTemplateTypeEnum.java b/tashow-feign/tashow-system-api/src/main/java/com/tashow/cloud/systemapi/enums/notify/NotifyTemplateTypeEnum.java new file mode 100644 index 0000000..f3c8faf --- /dev/null +++ b/tashow-feign/tashow-system-api/src/main/java/com/tashow/cloud/systemapi/enums/notify/NotifyTemplateTypeEnum.java @@ -0,0 +1,26 @@ +package com.tashow.cloud.systemapi.enums.notify; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * 通知模板类型枚举 + * + * @author HUIHUI + */ +@Getter +@AllArgsConstructor +public enum NotifyTemplateTypeEnum { + + /** + * 系统消息 + */ + SYSTEM_MESSAGE(2), + /** + * 通知消息 + */ + NOTIFICATION_MESSAGE(1); + + private final Integer type; + +} diff --git a/tashow-feign/tashow-system-api/src/main/java/com/tashow/cloud/systemapi/enums/oauth2/OAuth2ClientConstants.java b/tashow-feign/tashow-system-api/src/main/java/com/tashow/cloud/systemapi/enums/oauth2/OAuth2ClientConstants.java new file mode 100644 index 0000000..e673bc5 --- /dev/null +++ b/tashow-feign/tashow-system-api/src/main/java/com/tashow/cloud/systemapi/enums/oauth2/OAuth2ClientConstants.java @@ -0,0 +1,12 @@ +package com.tashow.cloud.systemapi.enums.oauth2; + +/** + * OAuth2.0 客户端的通用枚举 + * + * @author 芋道源码 + */ +public interface OAuth2ClientConstants { + + String CLIENT_ID_DEFAULT = "default"; + +} diff --git a/tashow-feign/tashow-system-api/src/main/java/com/tashow/cloud/systemapi/enums/oauth2/OAuth2GrantTypeEnum.java b/tashow-feign/tashow-system-api/src/main/java/com/tashow/cloud/systemapi/enums/oauth2/OAuth2GrantTypeEnum.java new file mode 100644 index 0000000..801312d --- /dev/null +++ b/tashow-feign/tashow-system-api/src/main/java/com/tashow/cloud/systemapi/enums/oauth2/OAuth2GrantTypeEnum.java @@ -0,0 +1,29 @@ +package com.tashow.cloud.systemapi.enums.oauth2; + +import cn.hutool.core.util.ArrayUtil; +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * OAuth2 授权类型(模式)的枚举 + * + * @author 芋道源码 + */ +@AllArgsConstructor +@Getter +public enum OAuth2GrantTypeEnum { + + PASSWORD("password"), // 密码模式 + AUTHORIZATION_CODE("authorization_code"), // 授权码模式 + IMPLICIT("implicit"), // 简化模式 + CLIENT_CREDENTIALS("client_credentials"), // 客户端模式 + REFRESH_TOKEN("refresh_token"), // 刷新模式 + ; + + private final String grantType; + + public static OAuth2GrantTypeEnum getByGrantType(String grantType) { + return ArrayUtil.firstMatch(o -> o.getGrantType().equals(grantType), values()); + } + +} diff --git a/tashow-feign/tashow-system-api/src/main/java/com/tashow/cloud/systemapi/enums/permission/DataScopeEnum.java b/tashow-feign/tashow-system-api/src/main/java/com/tashow/cloud/systemapi/enums/permission/DataScopeEnum.java new file mode 100644 index 0000000..d3d0800 --- /dev/null +++ b/tashow-feign/tashow-system-api/src/main/java/com/tashow/cloud/systemapi/enums/permission/DataScopeEnum.java @@ -0,0 +1,40 @@ +package com.tashow.cloud.systemapi.enums.permission; + +import com.tashow.cloud.common.core.ArrayValuable; +import lombok.AllArgsConstructor; +import lombok.Getter; + +import java.util.Arrays; + +/** + * 数据范围枚举类 + * + * 用于实现数据级别的权限 + * + * @author 芋道源码 + */ +@Getter +@AllArgsConstructor +public enum DataScopeEnum implements ArrayValuable { + + ALL(1), // 全部数据权限 + + DEPT_CUSTOM(2), // 指定部门数据权限 + DEPT_ONLY(3), // 部门数据权限 + DEPT_AND_CHILD(4), // 部门及以下数据权限 + + SELF(5); // 仅本人数据权限 + + /** + * 范围 + */ + private final Integer scope; + + public static final Integer[] ARRAYS = Arrays.stream(values()).map(DataScopeEnum::getScope).toArray(Integer[]::new); + + @Override + public Integer[] array() { + return ARRAYS; + } + +} diff --git a/tashow-feign/tashow-system-api/src/main/java/com/tashow/cloud/systemapi/enums/permission/MenuTypeEnum.java b/tashow-feign/tashow-system-api/src/main/java/com/tashow/cloud/systemapi/enums/permission/MenuTypeEnum.java new file mode 100644 index 0000000..095d43e --- /dev/null +++ b/tashow-feign/tashow-system-api/src/main/java/com/tashow/cloud/systemapi/enums/permission/MenuTypeEnum.java @@ -0,0 +1,25 @@ +package com.tashow.cloud.systemapi.enums.permission; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * 菜单类型枚举类 + * + * @author 芋道源码 + */ +@Getter +@AllArgsConstructor +public enum MenuTypeEnum { + + DIR(1), // 目录 + MENU(2), // 菜单 + BUTTON(3) // 按钮 + ; + + /** + * 类型 + */ + private final Integer type; + +} diff --git a/tashow-feign/tashow-system-api/src/main/java/com/tashow/cloud/systemapi/enums/permission/RoleCodeEnum.java b/tashow-feign/tashow-system-api/src/main/java/com/tashow/cloud/systemapi/enums/permission/RoleCodeEnum.java new file mode 100644 index 0000000..154d45d --- /dev/null +++ b/tashow-feign/tashow-system-api/src/main/java/com/tashow/cloud/systemapi/enums/permission/RoleCodeEnum.java @@ -0,0 +1,32 @@ +package com.tashow.cloud.systemapi.enums.permission; + +import com.tashow.cloud.common.util.object.ObjectUtils; +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * 角色标识枚举 + */ +@Getter +@AllArgsConstructor +public enum RoleCodeEnum { + + SUPER_ADMIN("super_admin", "超级管理员"), + TENANT_ADMIN("tenant_admin", "租户管理员"), + CRM_ADMIN("crm_admin", "CRM 管理员"); // CRM 系统专用 + ; + + /** + * 角色编码 + */ + private final String code; + /** + * 名字 + */ + private final String name; + + public static boolean isSuperAdmin(String code) { + return ObjectUtils.equalsAny(code, SUPER_ADMIN.getCode()); + } + +} diff --git a/tashow-feign/tashow-system-api/src/main/java/com/tashow/cloud/systemapi/enums/permission/RoleTypeEnum.java b/tashow-feign/tashow-system-api/src/main/java/com/tashow/cloud/systemapi/enums/permission/RoleTypeEnum.java new file mode 100644 index 0000000..230ea69 --- /dev/null +++ b/tashow-feign/tashow-system-api/src/main/java/com/tashow/cloud/systemapi/enums/permission/RoleTypeEnum.java @@ -0,0 +1,21 @@ +package com.tashow.cloud.systemapi.enums.permission; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +public enum RoleTypeEnum { + + /** + * 内置角色 + */ + SYSTEM(1), + /** + * 自定义角色 + */ + CUSTOM(2); + + private final Integer type; + +} diff --git a/tashow-feign/tashow-system-api/src/main/java/com/tashow/cloud/systemapi/enums/sms/SmsReceiveStatusEnum.java b/tashow-feign/tashow-system-api/src/main/java/com/tashow/cloud/systemapi/enums/sms/SmsReceiveStatusEnum.java new file mode 100644 index 0000000..546cbf1 --- /dev/null +++ b/tashow-feign/tashow-system-api/src/main/java/com/tashow/cloud/systemapi/enums/sms/SmsReceiveStatusEnum.java @@ -0,0 +1,23 @@ +package com.tashow.cloud.systemapi.enums.sms; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * 短信的接收状态枚举 + * + * @author 芋道源码 + * @date 2021/2/1 13:39 + */ +@Getter +@AllArgsConstructor +public enum SmsReceiveStatusEnum { + + INIT(0), // 初始化 + SUCCESS(10), // 接收成功 + FAILURE(20), // 接收失败 + ; + + private final int status; + +} diff --git a/tashow-feign/tashow-system-api/src/main/java/com/tashow/cloud/systemapi/enums/sms/SmsSceneEnum.java b/tashow-feign/tashow-system-api/src/main/java/com/tashow/cloud/systemapi/enums/sms/SmsSceneEnum.java new file mode 100644 index 0000000..373e6c2 --- /dev/null +++ b/tashow-feign/tashow-system-api/src/main/java/com/tashow/cloud/systemapi/enums/sms/SmsSceneEnum.java @@ -0,0 +1,53 @@ +package com.tashow.cloud.systemapi.enums.sms; + +import cn.hutool.core.util.ArrayUtil; +import com.tashow.cloud.common.core.ArrayValuable; +import lombok.AllArgsConstructor; +import lombok.Getter; + +import java.util.Arrays; + +/** + * 用户短信验证码发送场景的枚举 + * + * @author 芋道源码 + */ +@Getter +@AllArgsConstructor +public enum SmsSceneEnum implements ArrayValuable { + + MEMBER_LOGIN(1, "user-sms-login", "会员用户 - 手机号登陆"), + MEMBER_UPDATE_MOBILE(2, "user-update-mobile", "会员用户 - 修改手机"), + MEMBER_UPDATE_PASSWORD(3, "user-update-password", "会员用户 - 修改密码"), + MEMBER_RESET_PASSWORD(4, "user-reset-password", "会员用户 - 忘记密码"), + + ADMIN_MEMBER_LOGIN(21, "admin-sms-login", "后台用户 - 手机号登录"), + ADMIN_MEMBER_REGISTER(22, "admin-sms-register", "后台用户 - 手机号注册"), + ADMIN_MEMBER_RESET_PASSWORD(23, "admin-reset-password", "后台用户 - 忘记密码"); + + public static final Integer[] ARRAYS = Arrays.stream(values()).map(SmsSceneEnum::getScene).toArray(Integer[]::new); + + /** + * 验证场景的编号 + */ + private final Integer scene; + /** + * 模版编码 + */ + private final String templateCode; + /** + * 描述 + */ + private final String description; + + @Override + public Integer[] array() { + return ARRAYS; + } + + public static SmsSceneEnum getCodeByScene(Integer scene) { + return ArrayUtil.firstMatch(sceneEnum -> sceneEnum.getScene().equals(scene), + values()); + } + +} diff --git a/tashow-feign/tashow-system-api/src/main/java/com/tashow/cloud/systemapi/enums/sms/SmsSendStatusEnum.java b/tashow-feign/tashow-system-api/src/main/java/com/tashow/cloud/systemapi/enums/sms/SmsSendStatusEnum.java new file mode 100644 index 0000000..70f1fa6 --- /dev/null +++ b/tashow-feign/tashow-system-api/src/main/java/com/tashow/cloud/systemapi/enums/sms/SmsSendStatusEnum.java @@ -0,0 +1,24 @@ +package com.tashow.cloud.systemapi.enums.sms; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * 短信的发送状态枚举 + * + * @author zzf + * @date 2021/2/1 13:39 + */ +@Getter +@AllArgsConstructor +public enum SmsSendStatusEnum { + + INIT(0), // 初始化 + SUCCESS(10), // 发送成功 + FAILURE(20), // 发送失败 + IGNORE(30), // 忽略,即不发送 + ; + + private final int status; + +} diff --git a/tashow-feign/tashow-system-api/src/main/java/com/tashow/cloud/systemapi/enums/sms/SmsTemplateTypeEnum.java b/tashow-feign/tashow-system-api/src/main/java/com/tashow/cloud/systemapi/enums/sms/SmsTemplateTypeEnum.java new file mode 100644 index 0000000..431beee --- /dev/null +++ b/tashow-feign/tashow-system-api/src/main/java/com/tashow/cloud/systemapi/enums/sms/SmsTemplateTypeEnum.java @@ -0,0 +1,25 @@ +package com.tashow.cloud.systemapi.enums.sms; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * 短信的模板类型枚举 + * + * @author 芋道源码 + */ +@Getter +@AllArgsConstructor +public enum SmsTemplateTypeEnum { + + VERIFICATION_CODE(1), // 验证码 + NOTICE(2), // 通知 + PROMOTION(3), // 营销 + ; + + /** + * 类型 + */ + private final int type; + +} diff --git a/tashow-feign/tashow-system-api/src/main/java/com/tashow/cloud/systemapi/enums/social/SocialTypeEnum.java b/tashow-feign/tashow-system-api/src/main/java/com/tashow/cloud/systemapi/enums/social/SocialTypeEnum.java new file mode 100644 index 0000000..52903a4 --- /dev/null +++ b/tashow-feign/tashow-system-api/src/main/java/com/tashow/cloud/systemapi/enums/social/SocialTypeEnum.java @@ -0,0 +1,78 @@ +package com.tashow.cloud.systemapi.enums.social; + +import cn.hutool.core.util.ArrayUtil; +import com.tashow.cloud.common.core.ArrayValuable; +import lombok.AllArgsConstructor; +import lombok.Getter; + +import java.util.Arrays; + +/** + * 社交平台的类型枚举 + * + * @author 芋道源码 + */ +@Getter +@AllArgsConstructor +public enum SocialTypeEnum implements ArrayValuable { + + /** + * Gitee + * + * @see 接入文档 + */ + GITEE(10, "GITEE"), + /** + * 钉钉 + * + * @see 接入文档 + */ + DINGTALK(20, "DINGTALK"), + + /** + * 企业微信 + * + * @see 接入文档 + */ + WECHAT_ENTERPRISE(30, "WECHAT_ENTERPRISE"), + /** + * 微信公众平台 - 移动端 H5 + * + * @see 接入文档 + */ + WECHAT_MP(31, "WECHAT_MP"), + /** + * 微信开放平台 - 网站应用 PC 端扫码授权登录 + * + * @see 接入文档 + */ + WECHAT_OPEN(32, "WECHAT_OPEN"), + /** + * 微信小程序 + * + * @see 接入文档 + */ + WECHAT_MINI_APP(34, "WECHAT_MINI_APP"), + ; + + public static final Integer[] ARRAYS = Arrays.stream(values()).map(SocialTypeEnum::getType).toArray(Integer[]::new); + + /** + * 类型 + */ + private final Integer type; + /** + * 类型的标识 + */ + private final String source; + + @Override + public Integer[] array() { + return ARRAYS; + } + + public static SocialTypeEnum valueOfType(Integer type) { + return ArrayUtil.firstMatch(o -> o.getType().equals(type), values()); + } + +} diff --git a/tashow-framework/pom.xml b/tashow-framework/pom.xml new file mode 100644 index 0000000..06d6854 --- /dev/null +++ b/tashow-framework/pom.xml @@ -0,0 +1,45 @@ + + + 4.0.0 + + tashow-platform + com.tashow.cloud + ${revision} + + tashow-framework + pom + + tashow-common + tashow-framework-web + tashow-framework-env + tashow-framework-job + tashow-framework-monitor + tashow-framework-mq + tashow-framework-protection + tashow-framework-rpc + tashow-framework-security + tashow-framework-tenant + tashow-framework-websocket + tashow-data-permission + tashow-data-mybatis + tashow-data-redis + tashow-data-excel + tashow-data-es + tashow-data-canal + + + + + 该包是技术组件,每个子包,代表一个组件。每个组件包括两部分: + 1. core 包:是该组件的核心封装 + 2. config 包:是该组件基于 Spring 的配置 + + 技术组件,也分成两类: + 1. 框架组件:和我们熟悉的 MyBatis、Redis 等等的拓展 + 2. 业务组件:和业务相关的组件的封装,例如说数据字典、操作日志等等。 + 如果是业务组件,Maven 名字会包含 biz + + + diff --git a/tashow-framework/tashow-common/pom.xml b/tashow-framework/tashow-common/pom.xml new file mode 100644 index 0000000..4e246cb --- /dev/null +++ b/tashow-framework/tashow-common/pom.xml @@ -0,0 +1,172 @@ + + + 4.0.0 + + com.tashow.cloud + tashow-framework + ${revision} + + tashow-common + jar + + ${project.artifactId} + 定义基础 pojo 类、枚举、工具类等等 + + + + org.springframework + spring-context + provided + + + + org.springframework + spring-core + provided + + + org.springframework + spring-expression + provided + + + org.springframework + spring-aop + provided + + + org.aspectj + aspectjweaver + provided + + + + + org.springframework.boot + spring-boot-configuration-processor + true + + + + + org.springframework + spring-web + provided + + + + jakarta.servlet + jakarta.servlet-api + provided + + + + + org.apache.skywalking + apm-toolkit-trace + + + + + org.projectlombok + lombok + + + + org.mapstruct + mapstruct + + + org.mapstruct + mapstruct-jdk8 + + + org.mapstruct + mapstruct-processor + + + + com.google.guava + guava + provided + + + + com.fasterxml.jackson.core + jackson-databind + provided + + + com.fasterxml.jackson.core + jackson-core + provided + + + com.fasterxml.jackson.datatype + jackson-datatype-jsr310 + provided + + + + + + org.apache.commons + commons-lang3 + + + + + + org.slf4j + slf4j-api + provided + + + + jakarta.validation + jakarta.validation-api + provided + + + + cn.hutool + hutool-all + + + + com.alibaba + transmittable-thread-local + + + + com.fhs-opensource + easy-trans-anno + + + + + org.lionsoul + ip2region + + + + + org.springframework.boot + spring-boot-starter-test + test + + + + diff --git a/tashow-framework/tashow-common/src/main/java/com/tashow/cloud/common/core/Area.java b/tashow-framework/tashow-common/src/main/java/com/tashow/cloud/common/core/Area.java new file mode 100644 index 0000000..7f4ddff --- /dev/null +++ b/tashow-framework/tashow-common/src/main/java/com/tashow/cloud/common/core/Area.java @@ -0,0 +1,61 @@ +package com.tashow.cloud.common.core; + +import com.fasterxml.jackson.annotation.JsonBackReference; +import com.fasterxml.jackson.annotation.JsonManagedReference; +import com.tashow.cloud.common.enums.AreaTypeEnum; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.ToString; + +import java.util.List; + +/** + * 区域节点,包括国家、省份、城市、地区等信息 + * + * 数据可见 resources/area.csv 文件 + * + * @author 芋道源码 + */ +@Data +@AllArgsConstructor +@NoArgsConstructor +@ToString(exclude = {"parent"}) // 参见 https://gitee.com/yudaocode/yudao-cloud-mini/pulls/2 原因 +public class Area { + + /** + * 编号 - 全球,即根目录 + */ + public static final Integer ID_GLOBAL = 0; + /** + * 编号 - 中国 + */ + public static final Integer ID_CHINA = 1; + + /** + * 编号 + */ + private Integer id; + /** + * 名字 + */ + private String name; + /** + * 类型 + * + * 枚举 {@link AreaTypeEnum} + */ + private Integer type; + + /** + * 父节点 + */ + @JsonManagedReference + private Area parent; + /** + * 子节点 + */ + @JsonBackReference + private List children; + +} diff --git a/tashow-framework/tashow-common/src/main/java/com/tashow/cloud/common/core/ArrayValuable.java b/tashow-framework/tashow-common/src/main/java/com/tashow/cloud/common/core/ArrayValuable.java new file mode 100644 index 0000000..ab6ac75 --- /dev/null +++ b/tashow-framework/tashow-common/src/main/java/com/tashow/cloud/common/core/ArrayValuable.java @@ -0,0 +1,15 @@ +package com.tashow.cloud.common.core; + +/** + * 可生成 T 数组的接口 + * + * @author HUIHUI + */ +public interface ArrayValuable { + + /** + * @return 数组 + */ + T[] array(); + +} diff --git a/tashow-framework/tashow-common/src/main/java/com/tashow/cloud/common/core/KeyValue.java b/tashow-framework/tashow-common/src/main/java/com/tashow/cloud/common/core/KeyValue.java new file mode 100644 index 0000000..b7ee90b --- /dev/null +++ b/tashow-framework/tashow-common/src/main/java/com/tashow/cloud/common/core/KeyValue.java @@ -0,0 +1,22 @@ +package com.tashow.cloud.common.core; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + +/** + * Key Value 的键值对 + * + * @author 芋道源码 + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +public class KeyValue implements Serializable { + + private K key; + private V value; + +} diff --git a/tashow-framework/tashow-common/src/main/java/com/tashow/cloud/common/enums/AreaTypeEnum.java b/tashow-framework/tashow-common/src/main/java/com/tashow/cloud/common/enums/AreaTypeEnum.java new file mode 100644 index 0000000..7a06197 --- /dev/null +++ b/tashow-framework/tashow-common/src/main/java/com/tashow/cloud/common/enums/AreaTypeEnum.java @@ -0,0 +1,39 @@ +package com.tashow.cloud.common.enums; + +import com.tashow.cloud.common.core.ArrayValuable; +import lombok.AllArgsConstructor; +import lombok.Getter; + +import java.util.Arrays; + +/** + * 区域类型枚举 + * + * @author 芋道源码 + */ +@AllArgsConstructor +@Getter +public enum AreaTypeEnum implements ArrayValuable { + + COUNTRY(1, "国家"), + PROVINCE(2, "省份"), + CITY(3, "城市"), + DISTRICT(4, "地区"), // 县、镇、区等 + ; + + public static final Integer[] ARRAYS = Arrays.stream(values()).map(AreaTypeEnum::getType).toArray(Integer[]::new); + + /** + * 类型 + */ + private final Integer type; + /** + * 名字 + */ + private final String name; + + @Override + public Integer[] array() { + return ARRAYS; + } +} diff --git a/tashow-framework/tashow-common/src/main/java/com/tashow/cloud/common/enums/CommonStatusEnum.java b/tashow-framework/tashow-common/src/main/java/com/tashow/cloud/common/enums/CommonStatusEnum.java new file mode 100644 index 0000000..1dc7c96 --- /dev/null +++ b/tashow-framework/tashow-common/src/main/java/com/tashow/cloud/common/enums/CommonStatusEnum.java @@ -0,0 +1,46 @@ +package com.tashow.cloud.common.enums; + +import cn.hutool.core.util.ObjUtil; +import com.tashow.cloud.common.core.ArrayValuable; +import lombok.AllArgsConstructor; +import lombok.Getter; + +import java.util.Arrays; + +/** + * 通用状态枚举 + * + * @author 芋道源码 + */ +@Getter +@AllArgsConstructor +public enum CommonStatusEnum implements ArrayValuable { + + ENABLE(0, "开启"), + DISABLE(1, "关闭"); + + public static final Integer[] ARRAYS = Arrays.stream(values()).map(CommonStatusEnum::getStatus).toArray(Integer[]::new); + + /** + * 状态值 + */ + private final Integer status; + /** + * 状态名 + */ + private final String name; + + @Override + public Integer[] array() { + return ARRAYS; + } + + public static boolean isEnable(Integer status) { + return ObjUtil.equal(ENABLE.status, status); + } + + public static boolean isDisable(Integer status) { + return ObjUtil.equal(DISABLE.status, status); + } + +} diff --git a/tashow-framework/tashow-common/src/main/java/com/tashow/cloud/common/enums/DateIntervalEnum.java b/tashow-framework/tashow-common/src/main/java/com/tashow/cloud/common/enums/DateIntervalEnum.java new file mode 100644 index 0000000..f0dd65e --- /dev/null +++ b/tashow-framework/tashow-common/src/main/java/com/tashow/cloud/common/enums/DateIntervalEnum.java @@ -0,0 +1,46 @@ +package com.tashow.cloud.common.enums; + +import cn.hutool.core.util.ArrayUtil; +import com.tashow.cloud.common.core.ArrayValuable; +import lombok.AllArgsConstructor; +import lombok.Getter; + +import java.util.Arrays; + +/** + * 时间间隔的枚举 + * + * @author dhb52 + */ +@Getter +@AllArgsConstructor +public enum DateIntervalEnum implements ArrayValuable { + + DAY(1, "天"), + WEEK(2, "周"), + MONTH(3, "月"), + QUARTER(4, "季度"), + YEAR(5, "年") + ; + + public static final Integer[] ARRAYS = Arrays.stream(values()).map(DateIntervalEnum::getInterval).toArray(Integer[]::new); + + /** + * 类型 + */ + private final Integer interval; + /** + * 名称 + */ + private final String name; + + @Override + public Integer[] array() { + return ARRAYS; + } + + public static DateIntervalEnum valueOf(Integer interval) { + return ArrayUtil.firstMatch(item -> item.getInterval().equals(interval), DateIntervalEnum.values()); + } + +} diff --git a/tashow-framework/tashow-common/src/main/java/com/tashow/cloud/common/enums/DocumentEnum.java b/tashow-framework/tashow-common/src/main/java/com/tashow/cloud/common/enums/DocumentEnum.java new file mode 100644 index 0000000..7146096 --- /dev/null +++ b/tashow-framework/tashow-common/src/main/java/com/tashow/cloud/common/enums/DocumentEnum.java @@ -0,0 +1,21 @@ +package com.tashow.cloud.common.enums; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * 文档地址 + * + * @author 芋道源码 + */ +@Getter +@AllArgsConstructor +public enum DocumentEnum { + + REDIS_INSTALL("https://gitee.com/zhijiantianya/ruoyi-vue-pro/issues/I4VCSJ", "Redis 安装文档"), + TENANT("https://doc.iocoder.cn", "SaaS 多租户文档"); + + private final String url; + private final String memo; + +} diff --git a/tashow-framework/tashow-common/src/main/java/com/tashow/cloud/common/enums/RpcConstants.java b/tashow-framework/tashow-common/src/main/java/com/tashow/cloud/common/enums/RpcConstants.java new file mode 100644 index 0000000..caff4da --- /dev/null +++ b/tashow-framework/tashow-common/src/main/java/com/tashow/cloud/common/enums/RpcConstants.java @@ -0,0 +1,17 @@ +package com.tashow.cloud.common.enums; + +/** + * RPC 相关的枚举 + * + * 虽然放在 yudao-spring-boot-starter-rpc 会相对合适,但是每个 API 模块需要使用到,所以暂时只好放在此处 + * + * @author 芋道源码 + */ +public class RpcConstants { + + /** + * RPC API 的前缀 + */ + public static final String RPC_API_PREFIX = "/rpc-api"; + +} diff --git a/tashow-framework/tashow-common/src/main/java/com/tashow/cloud/common/enums/TerminalEnum.java b/tashow-framework/tashow-common/src/main/java/com/tashow/cloud/common/enums/TerminalEnum.java new file mode 100644 index 0000000..a1674b7 --- /dev/null +++ b/tashow-framework/tashow-common/src/main/java/com/tashow/cloud/common/enums/TerminalEnum.java @@ -0,0 +1,40 @@ +package com.tashow.cloud.common.enums; + +import com.tashow.cloud.common.core.ArrayValuable; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +import java.util.Arrays; + +/** + * 终端的枚举 + * + * @author 芋道源码 + */ +@RequiredArgsConstructor +@Getter +public enum TerminalEnum implements ArrayValuable { + + UNKNOWN(0, "未知"), // 目的:在无法解析到 terminal 时,使用它 + WECHAT_MINI_PROGRAM(10, "微信小程序"), + WECHAT_WAP(11, "微信公众号"), + H5(20, "H5 网页"), + APP(31, "手机 App"), + ; + + public static final Integer[] ARRAYS = Arrays.stream(values()).map(TerminalEnum::getTerminal).toArray(Integer[]::new); + + /** + * 终端 + */ + private final Integer terminal; + /** + * 终端名 + */ + private final String name; + + @Override + public Integer[] array() { + return ARRAYS; + } +} diff --git a/tashow-framework/tashow-common/src/main/java/com/tashow/cloud/common/enums/UserTypeEnum.java b/tashow-framework/tashow-common/src/main/java/com/tashow/cloud/common/enums/UserTypeEnum.java new file mode 100644 index 0000000..65ffd86 --- /dev/null +++ b/tashow-framework/tashow-common/src/main/java/com/tashow/cloud/common/enums/UserTypeEnum.java @@ -0,0 +1,39 @@ +package com.tashow.cloud.common.enums; + +import cn.hutool.core.util.ArrayUtil; +import com.tashow.cloud.common.core.ArrayValuable; +import lombok.AllArgsConstructor; +import lombok.Getter; + +import java.util.Arrays; + +/** + * 全局用户类型枚举 + */ +@AllArgsConstructor +@Getter +public enum UserTypeEnum implements ArrayValuable { + + MEMBER(1, "会员"), // 面向 c 端,普通用户 + ADMIN(2, "管理员"); // 面向 b 端,管理后台 + + public static final Integer[] ARRAYS = Arrays.stream(values()).map(UserTypeEnum::getValue).toArray(Integer[]::new); + + /** + * 类型 + */ + private final Integer value; + /** + * 类型名 + */ + private final String name; + + public static UserTypeEnum valueOf(Integer value) { + return ArrayUtil.firstMatch(userType -> userType.getValue().equals(value), UserTypeEnum.values()); + } + + @Override + public Integer[] array() { + return ARRAYS; + } +} diff --git a/tashow-framework/tashow-common/src/main/java/com/tashow/cloud/common/enums/WebFilterOrderEnum.java b/tashow-framework/tashow-common/src/main/java/com/tashow/cloud/common/enums/WebFilterOrderEnum.java new file mode 100644 index 0000000..b118092 --- /dev/null +++ b/tashow-framework/tashow-common/src/main/java/com/tashow/cloud/common/enums/WebFilterOrderEnum.java @@ -0,0 +1,36 @@ +package com.tashow.cloud.common.enums; + +/** + * Web 过滤器顺序的枚举类,保证过滤器按照符合我们的预期 + * + * 考虑到每个 starter 都需要用到该工具类,所以放到 common 模块下的 enum 包下 + * + * @author 芋道源码 + */ +public interface WebFilterOrderEnum { + + int CORS_FILTER = Integer.MIN_VALUE; + + int TRACE_FILTER = CORS_FILTER + 1; + + int ENV_TAG_FILTER = TRACE_FILTER + 1; + + int REQUEST_BODY_CACHE_FILTER = Integer.MIN_VALUE + 500; + + // OrderedRequestContextFilter 默认为 -105,用于国际化上下文等等 + + int TENANT_CONTEXT_FILTER = - 104; // 需要保证在 ApiAccessLogFilter 前面 + + int API_ACCESS_LOG_FILTER = -103; // 需要保证在 RequestBodyCacheFilter 后面 + + int XSS_FILTER = -102; // 需要保证在 RequestBodyCacheFilter 后面 + + // Spring Security Filter 默认为 -100,可见 org.springframework.boot.autoconfigure.security.SecurityProperties 配置属性类 + + int TENANT_SECURITY_FILTER = -99; // 需要保证在 Spring Security 过滤器后面 + + int FLOWABLE_FILTER = -98; // 需要保证在 Spring Security 过滤后面 + + int DEMO_FILTER = Integer.MAX_VALUE; + +} diff --git a/tashow-framework/tashow-common/src/main/java/com/tashow/cloud/common/exception/ErrorCode.java b/tashow-framework/tashow-common/src/main/java/com/tashow/cloud/common/exception/ErrorCode.java new file mode 100644 index 0000000..b5ada55 --- /dev/null +++ b/tashow-framework/tashow-common/src/main/java/com/tashow/cloud/common/exception/ErrorCode.java @@ -0,0 +1,32 @@ +package com.tashow.cloud.common.exception; + +import com.tashow.cloud.common.exception.enums.GlobalErrorCodeConstants; +import com.tashow.cloud.common.exception.enums.ServiceErrorCodeRange; +import lombok.Data; + +/** + * 错误码对象 + * + * 全局错误码,占用 [0, 999], 参见 {@link GlobalErrorCodeConstants} + * 业务异常错误码,占用 [1 000 000 000, +∞),参见 {@link ServiceErrorCodeRange} + * + * TODO 错误码设计成对象的原因,为未来的 i18 国际化做准备 + */ +@Data +public class ErrorCode { + + /** + * 错误码 + */ + private final Integer code; + /** + * 错误提示 + */ + private final String msg; + + public ErrorCode(Integer code, String message) { + this.code = code; + this.msg = message; + } + +} diff --git a/tashow-framework/tashow-common/src/main/java/com/tashow/cloud/common/exception/ServerException.java b/tashow-framework/tashow-common/src/main/java/com/tashow/cloud/common/exception/ServerException.java new file mode 100644 index 0000000..3fdf147 --- /dev/null +++ b/tashow-framework/tashow-common/src/main/java/com/tashow/cloud/common/exception/ServerException.java @@ -0,0 +1,60 @@ +package com.tashow.cloud.common.exception; + +import com.tashow.cloud.common.exception.enums.GlobalErrorCodeConstants; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 服务器异常 Exception + */ +@Data +@EqualsAndHashCode(callSuper = true) +public final class ServerException extends RuntimeException { + + /** + * 全局错误码 + * + * @see GlobalErrorCodeConstants + */ + private Integer code; + /** + * 错误提示 + */ + private String message; + + /** + * 空构造方法,避免反序列化问题 + */ + public ServerException() { + } + + public ServerException(ErrorCode errorCode) { + this.code = errorCode.getCode(); + this.message = errorCode.getMsg(); + } + + public ServerException(Integer code, String message) { + this.code = code; + this.message = message; + } + + public Integer getCode() { + return code; + } + + public ServerException setCode(Integer code) { + this.code = code; + return this; + } + + @Override + public String getMessage() { + return message; + } + + public ServerException setMessage(String message) { + this.message = message; + return this; + } + +} diff --git a/tashow-framework/tashow-common/src/main/java/com/tashow/cloud/common/exception/ServiceException.java b/tashow-framework/tashow-common/src/main/java/com/tashow/cloud/common/exception/ServiceException.java new file mode 100644 index 0000000..22c983b --- /dev/null +++ b/tashow-framework/tashow-common/src/main/java/com/tashow/cloud/common/exception/ServiceException.java @@ -0,0 +1,60 @@ +package com.tashow.cloud.common.exception; + +import com.tashow.cloud.common.exception.enums.ServiceErrorCodeRange; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 业务逻辑异常 Exception + */ +@Data +@EqualsAndHashCode(callSuper = true) +public final class ServiceException extends RuntimeException { + + /** + * 业务错误码 + * + * @see ServiceErrorCodeRange + */ + private Integer code; + /** + * 错误提示 + */ + private String message; + + /** + * 空构造方法,避免反序列化问题 + */ + public ServiceException() { + } + + public ServiceException(ErrorCode errorCode) { + this.code = errorCode.getCode(); + this.message = errorCode.getMsg(); + } + + public ServiceException(Integer code, String message) { + this.code = code; + this.message = message; + } + + public Integer getCode() { + return code; + } + + public ServiceException setCode(Integer code) { + this.code = code; + return this; + } + + @Override + public String getMessage() { + return message; + } + + public ServiceException setMessage(String message) { + this.message = message; + return this; + } + +} diff --git a/tashow-framework/tashow-common/src/main/java/com/tashow/cloud/common/exception/enums/GlobalErrorCodeConstants.java b/tashow-framework/tashow-common/src/main/java/com/tashow/cloud/common/exception/enums/GlobalErrorCodeConstants.java new file mode 100644 index 0000000..037b645 --- /dev/null +++ b/tashow-framework/tashow-common/src/main/java/com/tashow/cloud/common/exception/enums/GlobalErrorCodeConstants.java @@ -0,0 +1,41 @@ +package com.tashow.cloud.common.exception.enums; + +import com.tashow.cloud.common.exception.ErrorCode; + +/** + * 全局错误码枚举 + * 0-999 系统异常编码保留 + * + * 一般情况下,使用 HTTP 响应状态码 https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Status + * 虽然说,HTTP 响应状态码作为业务使用表达能力偏弱,但是使用在系统层面还是非常不错的 + * 比较特殊的是,因为之前一直使用 0 作为成功,就不使用 200 啦。 + * + * @author 芋道源码 + */ +public interface GlobalErrorCodeConstants { + + ErrorCode SUCCESS = new ErrorCode(0, "成功"); + + // ========== 客户端错误段 ========== + + ErrorCode BAD_REQUEST = new ErrorCode(400, "请求参数不正确"); + ErrorCode UNAUTHORIZED = new ErrorCode(401, "账号未登录"); + ErrorCode FORBIDDEN = new ErrorCode(403, "没有该操作权限"); + ErrorCode NOT_FOUND = new ErrorCode(404, "请求未找到"); + ErrorCode METHOD_NOT_ALLOWED = new ErrorCode(405, "请求方法不正确"); + ErrorCode LOCKED = new ErrorCode(423, "请求失败,请稍后重试"); // 并发请求,不允许 + ErrorCode TOO_MANY_REQUESTS = new ErrorCode(429, "请求过于频繁,请稍后重试"); + + // ========== 服务端错误段 ========== + + ErrorCode INTERNAL_SERVER_ERROR = new ErrorCode(500, "系统异常"); + ErrorCode NOT_IMPLEMENTED = new ErrorCode(501, "功能未实现/未开启"); + ErrorCode ERROR_CONFIGURATION = new ErrorCode(502, "错误的配置项"); + + // ========== 自定义错误段 ========== + ErrorCode REPEATED_REQUESTS = new ErrorCode(900, "重复请求,请稍后重试"); // 重复请求 + ErrorCode DEMO_DENY = new ErrorCode(901, "演示模式,禁止写操作"); + + ErrorCode UNKNOWN = new ErrorCode(999, "未知错误"); + +} diff --git a/tashow-framework/tashow-common/src/main/java/com/tashow/cloud/common/exception/enums/ServiceErrorCodeRange.java b/tashow-framework/tashow-common/src/main/java/com/tashow/cloud/common/exception/enums/ServiceErrorCodeRange.java new file mode 100644 index 0000000..a29e3c1 --- /dev/null +++ b/tashow-framework/tashow-common/src/main/java/com/tashow/cloud/common/exception/enums/ServiceErrorCodeRange.java @@ -0,0 +1,48 @@ +package com.tashow.cloud.common.exception.enums; + +/** + * 业务异常的错误码区间,解决:解决各模块错误码定义,避免重复,在此只声明不做实际使用 + * + * 一共 10 位,分成四段 + * + * 第一段,1 位,类型 + * 1 - 业务级别异常 + * x - 预留 + * 第二段,3 位,系统类型 + * 001 - 用户系统 + * 002 - 商品系统 + * 003 - 订单系统 + * 004 - 支付系统 + * 005 - 优惠劵系统 + * ... - ... + * 第三段,3 位,模块 + * 不限制规则。 + * 一般建议,每个系统里面,可能有多个模块,可以再去做分段。以用户系统为例子: + * 001 - OAuth2 模块 + * 002 - User 模块 + * 003 - MobileCode 模块 + * 第四段,3 位,错误码 + * 不限制规则。 + * 一般建议,每个模块自增。 + * + * @author 芋道源码 + */ +public class ServiceErrorCodeRange { + + // 模块 infra 错误码区间 [1-001-000-000 ~ 1-002-000-000) + // 模块 system 错误码区间 [1-002-000-000 ~ 1-003-000-000) + // 模块 report 错误码区间 [1-003-000-000 ~ 1-004-000-000) + // 模块 member 错误码区间 [1-004-000-000 ~ 1-005-000-000) + // 模块 mp 错误码区间 [1-006-000-000 ~ 1-007-000-000) + // 模块 pay 错误码区间 [1-007-000-000 ~ 1-008-000-000) + // 模块 bpm 错误码区间 [1-009-000-000 ~ 1-010-000-000) + + // 模块 product 错误码区间 [1-008-000-000 ~ 1-009-000-000) + // 模块 trade 错误码区间 [1-011-000-000 ~ 1-012-000-000) + // 模块 promotion 错误码区间 [1-013-000-000 ~ 1-014-000-000) + + // 模块 crm 错误码区间 [1-020-000-000 ~ 1-021-000-000) + + // 模块 ai 错误码区间 [1-022-000-000 ~ 1-023-000-000) + +} diff --git a/tashow-framework/tashow-common/src/main/java/com/tashow/cloud/common/exception/util/ServiceExceptionUtil.java b/tashow-framework/tashow-common/src/main/java/com/tashow/cloud/common/exception/util/ServiceExceptionUtil.java new file mode 100644 index 0000000..19fcf96 --- /dev/null +++ b/tashow-framework/tashow-common/src/main/java/com/tashow/cloud/common/exception/util/ServiceExceptionUtil.java @@ -0,0 +1,77 @@ +package com.tashow.cloud.common.exception.util; + +import com.google.common.annotations.VisibleForTesting; +import com.tashow.cloud.common.exception.ErrorCode; +import com.tashow.cloud.common.exception.ServiceException; +import com.tashow.cloud.common.exception.enums.GlobalErrorCodeConstants; +import lombok.extern.slf4j.Slf4j; + +/** + * {@link ServiceException} 工具类 + * + * 目的在于,格式化异常信息提示。 + * 考虑到 String.format 在参数不正确时会报错,因此使用 {} 作为占位符,并使用 {@link #doFormat(int, String, Object...)} 方法来格式化 + * + */ +@Slf4j +public class ServiceExceptionUtil { + + // ========== 和 ServiceException 的集成 ========== + + public static ServiceException exception(ErrorCode errorCode) { + return exception0(errorCode.getCode(), errorCode.getMsg()); + } + + public static ServiceException exception(ErrorCode errorCode, Object... params) { + return exception0(errorCode.getCode(), errorCode.getMsg(), params); + } + + public static ServiceException exception0(Integer code, String messagePattern, Object... params) { + String message = doFormat(code, messagePattern, params); + return new ServiceException(code, message); + } + + public static ServiceException invalidParamException(String messagePattern, Object... params) { + return exception0(GlobalErrorCodeConstants.BAD_REQUEST.getCode(), messagePattern, params); + } + + // ========== 格式化方法 ========== + + /** + * 将错误编号对应的消息使用 params 进行格式化。 + * + * @param code 错误编号 + * @param messagePattern 消息模版 + * @param params 参数 + * @return 格式化后的提示 + */ + @VisibleForTesting + public static String doFormat(int code, String messagePattern, Object... params) { + StringBuilder sbuf = new StringBuilder(messagePattern.length() + 50); + int i = 0; + int j; + int l; + for (l = 0; l < params.length; l++) { + j = messagePattern.indexOf("{}", i); + if (j == -1) { + log.error("[doFormat][参数过多:错误码({})|错误内容({})|参数({})", code, messagePattern, params); + if (i == 0) { + return messagePattern; + } else { + sbuf.append(messagePattern.substring(i)); + return sbuf.toString(); + } + } else { + sbuf.append(messagePattern, i, j); + sbuf.append(params[l]); + i = j + 2; + } + } + if (messagePattern.indexOf("{}", i) != -1) { + log.error("[doFormat][参数过少:错误码({})|错误内容({})|参数({})", code, messagePattern, params); + } + sbuf.append(messagePattern.substring(i)); + return sbuf.toString(); + } + +} diff --git a/tashow-framework/tashow-common/src/main/java/com/tashow/cloud/common/pojo/CommonResult.java b/tashow-framework/tashow-common/src/main/java/com/tashow/cloud/common/pojo/CommonResult.java new file mode 100644 index 0000000..c3b12f7 --- /dev/null +++ b/tashow-framework/tashow-common/src/main/java/com/tashow/cloud/common/pojo/CommonResult.java @@ -0,0 +1,121 @@ +package com.tashow.cloud.common.pojo; + +import cn.hutool.core.lang.Assert; +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.tashow.cloud.common.exception.ErrorCode; +import com.tashow.cloud.common.exception.ServiceException; +import com.tashow.cloud.common.exception.enums.GlobalErrorCodeConstants; +import com.tashow.cloud.common.exception.util.ServiceExceptionUtil; +import lombok.Data; + +import java.io.Serializable; +import java.util.Objects; + +/** + * 通用返回 + * + * @param 数据泛型 + */ +@Data +public class CommonResult implements Serializable { + + /** + * 错误码 + * + * @see ErrorCode#getCode() + */ + private Integer code; + /** + * 返回数据 + */ + private T data; + /** + * 错误提示,用户可阅读 + * + * @see ErrorCode#getMsg() () + */ + private String msg; + + /** + * 将传入的 result 对象,转换成另外一个泛型结果的对象 + *

+ * 因为 A 方法返回的 CommonResult 对象,不满足调用其的 B 方法的返回,所以需要进行转换。 + * + * @param result 传入的 result 对象 + * @param 返回的泛型 + * @return 新的 CommonResult 对象 + */ + public static CommonResult error(CommonResult result) { + return error(result.getCode(), result.getMsg()); + } + + public static CommonResult error(Integer code, String message) { + Assert.notEquals(GlobalErrorCodeConstants.SUCCESS.getCode(), code, "code 必须是错误的!"); + CommonResult result = new CommonResult<>(); + result.code = code; + result.msg = message; + return result; + } + + public static CommonResult error(ErrorCode errorCode, Object... params) { + Assert.notEquals(GlobalErrorCodeConstants.SUCCESS.getCode(), errorCode.getCode(), "code 必须是错误的!"); + CommonResult result = new CommonResult<>(); + result.code = errorCode.getCode(); + result.msg = ServiceExceptionUtil.doFormat(errorCode.getCode(), errorCode.getMsg(), params); + return result; + } + + public static CommonResult error(ErrorCode errorCode) { + return error(errorCode.getCode(), errorCode.getMsg()); + } + + public static CommonResult success(T data) { + CommonResult result = new CommonResult<>(); + result.code = GlobalErrorCodeConstants.SUCCESS.getCode(); + result.data = data; + result.msg = ""; + return result; + } + + public static boolean isSuccess(Integer code) { + return Objects.equals(code, GlobalErrorCodeConstants.SUCCESS.getCode()); + } + + @JsonIgnore // 避免 jackson 序列化 + public boolean isSuccess() { + return isSuccess(code); + } + + @JsonIgnore // 避免 jackson 序列化 + public boolean isError() { + return !isSuccess(); + } + + // ========= 和 Exception 异常体系集成 ========= + + /** + * 判断是否有异常。如果有,则抛出 {@link ServiceException} 异常 + */ + public void checkError() throws ServiceException { + if (isSuccess()) { + return; + } + // 业务异常 + throw new ServiceException(code, msg); + } + + /** + * 判断是否有异常。如果有,则抛出 {@link ServiceException} 异常 + * 如果没有,则返回 {@link #data} 数据 + */ + @JsonIgnore // 避免 jackson 序列化 + public T getCheckedData() { + checkError(); + return data; + } + + public static CommonResult error(ServiceException serviceException) { + return error(serviceException.getCode(), serviceException.getMessage()); + } + +} diff --git a/tashow-framework/tashow-common/src/main/java/com/tashow/cloud/common/pojo/PageParam.java b/tashow-framework/tashow-common/src/main/java/com/tashow/cloud/common/pojo/PageParam.java new file mode 100644 index 0000000..99ffe39 --- /dev/null +++ b/tashow-framework/tashow-common/src/main/java/com/tashow/cloud/common/pojo/PageParam.java @@ -0,0 +1,41 @@ +package com.tashow.cloud.common.pojo; + + +import jakarta.validation.constraints.Max; +import jakarta.validation.constraints.Min; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +import java.io.Serializable; + +/** + * 分页参数 + */ +@Data +public class PageParam implements Serializable { + + + + /** + * 每页条数 - 不分页 + *

+ * 例如说,导出接口,可以设置 {@link #pageSize} 为 -1 不分页,查询所有数据。 + */ + public static final Integer PAGE_SIZE_NONE = -1; + + /** + * 页码,从 1 开始", example = "1 + */ + @NotNull(message = "页码不能为空") + @Min(value = 1, message = "页码最小值为 1") + private Integer pageNo = 1; + + /** + * 每页条数,最大值为 100" + */ + @NotNull(message = "每页条数不能为空") + @Min(value = 1, message = "每页条数最小值为 1") + @Max(value = 100, message = "每页条数最大值为 100") + private Integer pageSize = 10; + +} diff --git a/tashow-framework/tashow-common/src/main/java/com/tashow/cloud/common/pojo/PageResult.java b/tashow-framework/tashow-common/src/main/java/com/tashow/cloud/common/pojo/PageResult.java new file mode 100644 index 0000000..17c4a99 --- /dev/null +++ b/tashow-framework/tashow-common/src/main/java/com/tashow/cloud/common/pojo/PageResult.java @@ -0,0 +1,47 @@ +package com.tashow.cloud.common.pojo; + + +import lombok.Data; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; + +/** + * 分页结果 + */ +@Data +public final class PageResult implements Serializable { + + /** + * 数据 + */ + private List list; + + /** + * 总量 + */ + private Long total; + + public PageResult() { + } + + public PageResult(List list, Long total) { + this.list = list; + this.total = total; + } + + public PageResult(Long total) { + this.list = new ArrayList<>(); + this.total = total; + } + + public static PageResult empty() { + return new PageResult<>(0L); + } + + public static PageResult empty(Long total) { + return new PageResult<>(total); + } + +} diff --git a/tashow-framework/tashow-common/src/main/java/com/tashow/cloud/common/pojo/SortablePageParam.java b/tashow-framework/tashow-common/src/main/java/com/tashow/cloud/common/pojo/SortablePageParam.java new file mode 100644 index 0000000..be44608 --- /dev/null +++ b/tashow-framework/tashow-common/src/main/java/com/tashow/cloud/common/pojo/SortablePageParam.java @@ -0,0 +1,23 @@ +package com.tashow.cloud.common.pojo; + + +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; + +import java.util.List; + +/** + * 可排序的分页参数 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class SortablePageParam extends PageParam { + + /** + * 排序字段 + */ + private List sortingFields; + +} diff --git a/tashow-framework/tashow-common/src/main/java/com/tashow/cloud/common/pojo/SortingField.java b/tashow-framework/tashow-common/src/main/java/com/tashow/cloud/common/pojo/SortingField.java new file mode 100644 index 0000000..3be4082 --- /dev/null +++ b/tashow-framework/tashow-common/src/main/java/com/tashow/cloud/common/pojo/SortingField.java @@ -0,0 +1,37 @@ +package com.tashow.cloud.common.pojo; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + +/** + * 排序字段 DTO + *

+ * 类名加了 ing 的原因是,避免和 ES SortField 重名。 + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +public class SortingField implements Serializable { + + /** + * 顺序 - 升序 + */ + public static final String ORDER_ASC = "asc"; + /** + * 顺序 - 降序 + */ + public static final String ORDER_DESC = "desc"; + + /** + * 字段 + */ + private String field; + /** + * 顺序 + */ + private String order; + +} diff --git a/tashow-framework/tashow-common/src/main/java/com/tashow/cloud/common/trans/AutoTransable.java b/tashow-framework/tashow-common/src/main/java/com/tashow/cloud/common/trans/AutoTransable.java new file mode 100644 index 0000000..9020f29 --- /dev/null +++ b/tashow-framework/tashow-common/src/main/java/com/tashow/cloud/common/trans/AutoTransable.java @@ -0,0 +1,59 @@ +package com.tashow.cloud.common.trans; + +import com.fhs.core.trans.vo.VO; + +import java.util.ArrayList; +import java.util.List; + +/** + * 只有实现了这个接口的才能自动翻译 + * + * 为什么要赋值粘贴到 yudao-common 包下? + * 因为 AutoTransable 属于 easy-trans-service 下,无法方便的在 yudao-module-xxx-api 模块下使用 + * + * @author jackwang + * @since 2020-05-19 10:26:15 + */ +public interface AutoTransable { + + /** + * 根据 ids 查询数据列表 + * + * 改方法已过期啦,请使用 selectByIds + * + * @param ids 编号数组 + * @return 数据列表 + */ + @Deprecated + default List findByIds(List ids){ + return new ArrayList<>(); + } + + /** + * 根据 ids 查询 + * + * @param ids 编号数组 + * @return 数据列表 + */ + default List selectByIds(List ids){ + return this.findByIds(ids); + } + + /** + * 获取 db 中所有的数据 + * + * @return db 中所有的数据 + */ + default List select(){ + return new ArrayList<>(); + } + + /** + * 根据 id 获取 vo + * + * @param primaryValue id + * @return vo + */ + V selectById(Object primaryValue); + +} diff --git a/tashow-framework/tashow-common/src/main/java/com/tashow/cloud/common/util/cache/CacheUtils.java b/tashow-framework/tashow-common/src/main/java/com/tashow/cloud/common/util/cache/CacheUtils.java new file mode 100644 index 0000000..f039b10 --- /dev/null +++ b/tashow-framework/tashow-common/src/main/java/com/tashow/cloud/common/util/cache/CacheUtils.java @@ -0,0 +1,49 @@ +package com.tashow.cloud.common.util.cache; + +import com.google.common.cache.CacheBuilder; +import com.google.common.cache.CacheLoader; +import com.google.common.cache.LoadingCache; + +import java.time.Duration; +import java.util.concurrent.Executors; + +/** + * Cache 工具类 + * + * @author 芋道源码 + */ +public class CacheUtils { + + /** + * 构建异步刷新的 LoadingCache 对象 + * + * 注意:如果你的缓存和 ThreadLocal 有关系,要么自己处理 ThreadLocal 的传递,要么使用 {@link #buildCache(Duration, CacheLoader)} 方法 + * + * 或者简单理解: + * 1、和“人”相关的,使用 {@link #buildCache(Duration, CacheLoader)} 方法 + * 2、和“全局”、“系统”相关的,使用当前缓存方法 + * + * @param duration 过期时间 + * @param loader CacheLoader 对象 + * @return LoadingCache 对象 + */ + public static LoadingCache buildAsyncReloadingCache(Duration duration, CacheLoader loader) { + return CacheBuilder.newBuilder() + // 只阻塞当前数据加载线程,其他线程返回旧值 + .refreshAfterWrite(duration) + // 通过 asyncReloading 实现全异步加载,包括 refreshAfterWrite 被阻塞的加载线程 + .build(CacheLoader.asyncReloading(loader, Executors.newCachedThreadPool())); // TODO 芋艿:可能要思考下,未来要不要做成可配置 + } + + /** + * 构建同步刷新的 LoadingCache 对象 + * + * @param duration 过期时间 + * @param loader CacheLoader 对象 + * @return LoadingCache 对象 + */ + public static LoadingCache buildCache(Duration duration, CacheLoader loader) { + return CacheBuilder.newBuilder().refreshAfterWrite(duration).build(loader); + } + +} diff --git a/tashow-framework/tashow-common/src/main/java/com/tashow/cloud/common/util/collection/ArrayUtils.java b/tashow-framework/tashow-common/src/main/java/com/tashow/cloud/common/util/collection/ArrayUtils.java new file mode 100644 index 0000000..d7454ea --- /dev/null +++ b/tashow-framework/tashow-common/src/main/java/com/tashow/cloud/common/util/collection/ArrayUtils.java @@ -0,0 +1,59 @@ +package com.tashow.cloud.common.util.collection; + +import cn.hutool.core.collection.CollectionUtil; +import cn.hutool.core.collection.IterUtil; +import cn.hutool.core.util.ArrayUtil; + +import java.util.Collection; +import java.util.function.Consumer; +import java.util.function.Function; + +import static com.tashow.cloud.common.util.collection.CollectionUtils.convertList; + + +/** + * Array 工具类 + * + * @author 芋道源码 + */ +public class ArrayUtils { + + /** + * 将 object 和 newElements 合并成一个数组 + * + * @param object 对象 + * @param newElements 数组 + * @param 泛型 + * @return 结果数组 + */ + @SafeVarargs + public static Consumer[] append(Consumer object, Consumer... newElements) { + if (object == null) { + return newElements; + } + Consumer[] result = ArrayUtil.newArray(Consumer.class, 1 + newElements.length); + result[0] = object; + System.arraycopy(newElements, 0, result, 1, newElements.length); + return result; + } + + public static V[] toArray(Collection from, Function mapper) { + return toArray(convertList(from, mapper)); + } + + @SuppressWarnings("unchecked") + public static T[] toArray(Collection from) { + if (CollectionUtil.isEmpty(from)) { + return (T[]) (new Object[0]); + } + return ArrayUtil.toArray(from, (Class) IterUtil.getElementType(from.iterator())); + } + + public static T get(T[] array, int index) { + if (null == array || index >= array.length) { + return null; + } + return array[index]; + } + +} diff --git a/tashow-framework/tashow-common/src/main/java/com/tashow/cloud/common/util/collection/CollectionUtils.java b/tashow-framework/tashow-common/src/main/java/com/tashow/cloud/common/util/collection/CollectionUtils.java new file mode 100644 index 0000000..135a1c6 --- /dev/null +++ b/tashow-framework/tashow-common/src/main/java/com/tashow/cloud/common/util/collection/CollectionUtils.java @@ -0,0 +1,338 @@ +package com.tashow.cloud.common.util.collection; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.collection.CollectionUtil; +import cn.hutool.core.util.ArrayUtil; +import com.google.common.collect.ImmutableMap; +import com.tashow.cloud.common.pojo.PageResult; + +import java.util.*; +import java.util.function.*; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import static java.util.Arrays.asList; + +/** + * Collection 工具类 + * + * @author 芋道源码 + */ +public class CollectionUtils { + + public static boolean containsAny(Object source, Object... targets) { + return asList(targets).contains(source); + } + + public static boolean isAnyEmpty(Collection... collections) { + return Arrays.stream(collections).anyMatch(CollectionUtil::isEmpty); + } + + public static boolean anyMatch(Collection from, Predicate predicate) { + return from.stream().anyMatch(predicate); + } + + public static List filterList(Collection from, Predicate predicate) { + if (CollUtil.isEmpty(from)) { + return new ArrayList<>(); + } + return from.stream().filter(predicate).collect(Collectors.toList()); + } + + public static List distinct(Collection from, Function keyMapper) { + if (CollUtil.isEmpty(from)) { + return new ArrayList<>(); + } + return distinct(from, keyMapper, (t1, t2) -> t1); + } + + public static List distinct(Collection from, Function keyMapper, BinaryOperator cover) { + if (CollUtil.isEmpty(from)) { + return new ArrayList<>(); + } + return new ArrayList<>(convertMap(from, keyMapper, Function.identity(), cover).values()); + } + + public static List convertList(T[] from, Function func) { + if (ArrayUtil.isEmpty(from)) { + return new ArrayList<>(); + } + return convertList(Arrays.asList(from), func); + } + + public static List convertList(Collection from, Function func) { + if (CollUtil.isEmpty(from)) { + return new ArrayList<>(); + } + return from.stream().map(func).filter(Objects::nonNull).collect(Collectors.toList()); + } + + public static List convertList(Collection from, Function func, Predicate filter) { + if (CollUtil.isEmpty(from)) { + return new ArrayList<>(); + } + return from.stream().filter(filter).map(func).filter(Objects::nonNull).collect(Collectors.toList()); + } + + public static PageResult convertPage(PageResult from, Function func) { + if (ArrayUtil.isEmpty(from)) { + return new PageResult<>(from.getTotal()); + } + return new PageResult<>(convertList(from.getList(), func), from.getTotal()); + } + + public static List convertListByFlatMap(Collection from, + Function> func) { + if (CollUtil.isEmpty(from)) { + return new ArrayList<>(); + } + return from.stream().filter(Objects::nonNull).flatMap(func).filter(Objects::nonNull).collect(Collectors.toList()); + } + + public static List convertListByFlatMap(Collection from, + Function mapper, + Function> func) { + if (CollUtil.isEmpty(from)) { + return new ArrayList<>(); + } + return from.stream().map(mapper).filter(Objects::nonNull).flatMap(func).filter(Objects::nonNull).collect(Collectors.toList()); + } + + public static List mergeValuesFromMap(Map> map) { + return map.values() + .stream() + .flatMap(List::stream) + .collect(Collectors.toList()); + } + + public static Set convertSet(Collection from) { + return convertSet(from, v -> v); + } + + public static Set convertSet(Collection from, Function func) { + if (CollUtil.isEmpty(from)) { + return new HashSet<>(); + } + return from.stream().map(func).filter(Objects::nonNull).collect(Collectors.toSet()); + } + + public static Set convertSet(Collection from, Function func, Predicate filter) { + if (CollUtil.isEmpty(from)) { + return new HashSet<>(); + } + return from.stream().filter(filter).map(func).filter(Objects::nonNull).collect(Collectors.toSet()); + } + + public static Map convertMapByFilter(Collection from, Predicate filter, Function keyFunc) { + if (CollUtil.isEmpty(from)) { + return new HashMap<>(); + } + return from.stream().filter(filter).collect(Collectors.toMap(keyFunc, v -> v)); + } + + public static Set convertSetByFlatMap(Collection from, + Function> func) { + if (CollUtil.isEmpty(from)) { + return new HashSet<>(); + } + return from.stream().filter(Objects::nonNull).flatMap(func).filter(Objects::nonNull).collect(Collectors.toSet()); + } + + public static Set convertSetByFlatMap(Collection from, + Function mapper, + Function> func) { + if (CollUtil.isEmpty(from)) { + return new HashSet<>(); + } + return from.stream().map(mapper).filter(Objects::nonNull).flatMap(func).filter(Objects::nonNull).collect(Collectors.toSet()); + } + + public static Map convertMap(Collection from, Function keyFunc) { + if (CollUtil.isEmpty(from)) { + return new HashMap<>(); + } + return convertMap(from, keyFunc, Function.identity()); + } + + public static Map convertMap(Collection from, Function keyFunc, Supplier> supplier) { + if (CollUtil.isEmpty(from)) { + return supplier.get(); + } + return convertMap(from, keyFunc, Function.identity(), supplier); + } + + public static Map convertMap(Collection from, Function keyFunc, Function valueFunc) { + if (CollUtil.isEmpty(from)) { + return new HashMap<>(); + } + return convertMap(from, keyFunc, valueFunc, (v1, v2) -> v1); + } + + public static Map convertMap(Collection from, Function keyFunc, Function valueFunc, BinaryOperator mergeFunction) { + if (CollUtil.isEmpty(from)) { + return new HashMap<>(); + } + return convertMap(from, keyFunc, valueFunc, mergeFunction, HashMap::new); + } + + public static Map convertMap(Collection from, Function keyFunc, Function valueFunc, Supplier> supplier) { + if (CollUtil.isEmpty(from)) { + return supplier.get(); + } + return convertMap(from, keyFunc, valueFunc, (v1, v2) -> v1, supplier); + } + + public static Map convertMap(Collection from, Function keyFunc, Function valueFunc, BinaryOperator mergeFunction, Supplier> supplier) { + if (CollUtil.isEmpty(from)) { + return new HashMap<>(); + } + return from.stream().collect(Collectors.toMap(keyFunc, valueFunc, mergeFunction, supplier)); + } + + public static Map> convertMultiMap(Collection from, Function keyFunc) { + if (CollUtil.isEmpty(from)) { + return new HashMap<>(); + } + return from.stream().collect(Collectors.groupingBy(keyFunc, Collectors.mapping(t -> t, Collectors.toList()))); + } + + public static Map> convertMultiMap(Collection from, Function keyFunc, Function valueFunc) { + if (CollUtil.isEmpty(from)) { + return new HashMap<>(); + } + return from.stream() + .collect(Collectors.groupingBy(keyFunc, Collectors.mapping(valueFunc, Collectors.toList()))); + } + + // 暂时没想好名字,先以 2 结尾噶 + public static Map> convertMultiMap2(Collection from, Function keyFunc, Function valueFunc) { + if (CollUtil.isEmpty(from)) { + return new HashMap<>(); + } + return from.stream().collect(Collectors.groupingBy(keyFunc, Collectors.mapping(valueFunc, Collectors.toSet()))); + } + + public static Map convertImmutableMap(Collection from, Function keyFunc) { + if (CollUtil.isEmpty(from)) { + return Collections.emptyMap(); + } + ImmutableMap.Builder builder = ImmutableMap.builder(); + from.forEach(item -> builder.put(keyFunc.apply(item), item)); + return builder.build(); + } + + /** + * 对比老、新两个列表,找出新增、修改、删除的数据 + * + * @param oldList 老列表 + * @param newList 新列表 + * @param sameFunc 对比函数,返回 true 表示相同,返回 false 表示不同 + * 注意,same 是通过每个元素的“标识”,判断它们是不是同一个数据 + * @return [新增列表、修改列表、删除列表] + */ + public static List> diffList(Collection oldList, Collection newList, + BiFunction sameFunc) { + List createList = new LinkedList<>(newList); // 默认都认为是新增的,后续会进行移除 + List updateList = new ArrayList<>(); + List deleteList = new ArrayList<>(); + + // 通过以 oldList 为主遍历,找出 updateList 和 deleteList + for (T oldObj : oldList) { + // 1. 寻找是否有匹配的 + T foundObj = null; + for (Iterator iterator = createList.iterator(); iterator.hasNext(); ) { + T newObj = iterator.next(); + // 1.1 不匹配,则直接跳过 + if (!sameFunc.apply(oldObj, newObj)) { + continue; + } + // 1.2 匹配,则移除,并结束寻找 + iterator.remove(); + foundObj = newObj; + break; + } + // 2. 匹配添加到 updateList;不匹配则添加到 deleteList 中 + if (foundObj != null) { + updateList.add(foundObj); + } else { + deleteList.add(oldObj); + } + } + return asList(createList, updateList, deleteList); + } + + public static boolean containsAny(Collection source, Collection candidates) { + return org.springframework.util.CollectionUtils.containsAny(source, candidates); + } + + public static T getFirst(List from) { + return !CollectionUtil.isEmpty(from) ? from.get(0) : null; + } + + public static T findFirst(Collection from, Predicate predicate) { + return findFirst(from, predicate, Function.identity()); + } + + public static U findFirst(Collection from, Predicate predicate, Function func) { + if (CollUtil.isEmpty(from)) { + return null; + } + return from.stream().filter(predicate).findFirst().map(func).orElse(null); + } + + public static > V getMaxValue(Collection from, Function valueFunc) { + if (CollUtil.isEmpty(from)) { + return null; + } + assert !from.isEmpty(); // 断言,避免告警 + T t = from.stream().max(Comparator.comparing(valueFunc)).get(); + return valueFunc.apply(t); + } + + public static > V getMinValue(List from, Function valueFunc) { + if (CollUtil.isEmpty(from)) { + return null; + } + assert from.size() > 0; // 断言,避免告警 + T t = from.stream().min(Comparator.comparing(valueFunc)).get(); + return valueFunc.apply(t); + } + + public static > T getMinObject(List from, Function valueFunc) { + if (CollUtil.isEmpty(from)) { + return null; + } + assert from.size() > 0; // 断言,避免告警 + return from.stream().min(Comparator.comparing(valueFunc)).get(); + } + + public static > V getSumValue(Collection from, Function valueFunc, + BinaryOperator accumulator) { + return getSumValue(from, valueFunc, accumulator, null); + } + + public static > V getSumValue(Collection from, Function valueFunc, + BinaryOperator accumulator, V defaultValue) { + if (CollUtil.isEmpty(from)) { + return defaultValue; + } + assert !from.isEmpty(); // 断言,避免告警 + return from.stream().map(valueFunc).filter(Objects::nonNull).reduce(accumulator).orElse(defaultValue); + } + + public static void addIfNotNull(Collection coll, T item) { + if (item == null) { + return; + } + coll.add(item); + } + + public static Collection singleton(T obj) { + return obj == null ? Collections.emptyList() : Collections.singleton(obj); + } + + public static List newArrayList(List> list) { + return list.stream().filter(Objects::nonNull).flatMap(Collection::stream).collect(Collectors.toList()); + } + +} diff --git a/tashow-framework/tashow-common/src/main/java/com/tashow/cloud/common/util/collection/MapUtils.java b/tashow-framework/tashow-common/src/main/java/com/tashow/cloud/common/util/collection/MapUtils.java new file mode 100644 index 0000000..6d862b8 --- /dev/null +++ b/tashow-framework/tashow-common/src/main/java/com/tashow/cloud/common/util/collection/MapUtils.java @@ -0,0 +1,68 @@ +package com.tashow.cloud.common.util.collection; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.collection.CollectionUtil; +import cn.hutool.core.util.ObjUtil; +import com.google.common.collect.Maps; +import com.google.common.collect.Multimap; +import com.tashow.cloud.common.core.KeyValue; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.function.Consumer; + +/** + * Map 工具类 + * + * @author 芋道源码 + */ +public class MapUtils { + + /** + * 从哈希表表中,获得 keys 对应的所有 value 数组 + * + * @param multimap 哈希表 + * @param keys keys + * @return value 数组 + */ + public static List getList(Multimap multimap, Collection keys) { + List result = new ArrayList<>(); + keys.forEach(k -> { + Collection values = multimap.get(k); + if (CollectionUtil.isEmpty(values)) { + return; + } + result.addAll(values); + }); + return result; + } + + /** + * 从哈希表查找到 key 对应的 value,然后进一步处理 + * key 为 null 时, 不处理 + * 注意,如果查找到的 value 为 null 时,不进行处理 + * + * @param map 哈希表 + * @param key key + * @param consumer 进一步处理的逻辑 + */ + public static void findAndThen(Map map, K key, Consumer consumer) { + if (ObjUtil.isNull(key) || CollUtil.isEmpty(map)) { + return; + } + V value = map.get(key); + if (value == null) { + return; + } + consumer.accept(value); + } + + public static Map convertMap(List> keyValues) { + Map map = Maps.newLinkedHashMapWithExpectedSize(keyValues.size()); + keyValues.forEach(keyValue -> map.put(keyValue.getKey(), keyValue.getValue())); + return map; + } + +} diff --git a/tashow-framework/tashow-common/src/main/java/com/tashow/cloud/common/util/collection/SetUtils.java b/tashow-framework/tashow-common/src/main/java/com/tashow/cloud/common/util/collection/SetUtils.java new file mode 100644 index 0000000..fd5c7fd --- /dev/null +++ b/tashow-framework/tashow-common/src/main/java/com/tashow/cloud/common/util/collection/SetUtils.java @@ -0,0 +1,19 @@ +package com.tashow.cloud.common.util.collection; + +import cn.hutool.core.collection.CollUtil; + +import java.util.Set; + +/** + * Set 工具类 + * + * @author 芋道源码 + */ +public class SetUtils { + + @SafeVarargs + public static Set asSet(T... objs) { + return CollUtil.newHashSet(objs); + } + +} diff --git a/tashow-framework/tashow-common/src/main/java/com/tashow/cloud/common/util/date/DateUtils.java b/tashow-framework/tashow-common/src/main/java/com/tashow/cloud/common/util/date/DateUtils.java new file mode 100644 index 0000000..154dad4 --- /dev/null +++ b/tashow-framework/tashow-common/src/main/java/com/tashow/cloud/common/util/date/DateUtils.java @@ -0,0 +1,185 @@ +package com.tashow.cloud.common.util.date; + +import cn.hutool.core.date.LocalDateTimeUtil; + +import java.time.*; +import java.time.temporal.ChronoUnit; +import java.util.Calendar; +import java.util.Date; + +/** + * 时间工具类 + * + * @author 芋道源码 + */ +public class DateUtils { + + /** + * 时区 - 默认 + */ + public static final String TIME_ZONE_DEFAULT = "GMT+8"; + + /** + * 秒转换成毫秒 + */ + public static final long SECOND_MILLIS = 1000; + + public static final String FORMAT_YEAR_MONTH_DAY = "yyyy-MM-dd"; + + public static final String FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND = "yyyy-MM-dd HH:mm:ss"; + + + // 默认数据保留天数 + private static final long RETENTION_DAYS = 90; + /** + * 将 LocalDateTime 转换成 Date + * + * @param date LocalDateTime + * @return LocalDateTime + */ + public static Date of(LocalDateTime date) { + if (date == null) { + return null; + } + // 将此日期时间与时区相结合以创建 ZonedDateTime + ZonedDateTime zonedDateTime = date.atZone(ZoneId.systemDefault()); + // 本地时间线 LocalDateTime 到即时时间线 Instant 时间戳 + Instant instant = zonedDateTime.toInstant(); + // UTC时间(世界协调时间,UTC + 00:00)转北京(北京,UTC + 8:00)时间 + return Date.from(instant); + } + + /** + * 将 Date 转换成 LocalDateTime + * + * @param date Date + * @return LocalDateTime + */ + public static LocalDateTime of(Date date) { + if (date == null) { + return null; + } + // 转为时间戳 + Instant instant = date.toInstant(); + // UTC时间(世界协调时间,UTC + 00:00)转北京(北京,UTC + 8:00)时间 + return LocalDateTime.ofInstant(instant, ZoneId.systemDefault()); + } + + public static Date addTime(Duration duration) { + return new Date(System.currentTimeMillis() + duration.toMillis()); + } + + public static boolean isExpired(LocalDateTime time) { + LocalDateTime now = LocalDateTime.now(); + return now.isAfter(time); + } + + /** + * 创建指定时间 + * + * @param year 年 + * @param mouth 月 + * @param day 日 + * @return 指定时间 + */ + public static Date buildTime(int year, int mouth, int day) { + return buildTime(year, mouth, day, 0, 0, 0); + } + + /** + * 创建指定时间 + * + * @param year 年 + * @param mouth 月 + * @param day 日 + * @param hour 小时 + * @param minute 分钟 + * @param second 秒 + * @return 指定时间 + */ + public static Date buildTime(int year, int mouth, int day, + int hour, int minute, int second) { + Calendar calendar = Calendar.getInstance(); + calendar.set(Calendar.YEAR, year); + calendar.set(Calendar.MONTH, mouth - 1); + calendar.set(Calendar.DAY_OF_MONTH, day); + calendar.set(Calendar.HOUR_OF_DAY, hour); + calendar.set(Calendar.MINUTE, minute); + calendar.set(Calendar.SECOND, second); + calendar.set(Calendar.MILLISECOND, 0); // 一般情况下,都是 0 毫秒 + return calendar.getTime(); + } + + public static Date max(Date a, Date b) { + if (a == null) { + return b; + } + if (b == null) { + return a; + } + return a.compareTo(b) > 0 ? a : b; + } + + public static LocalDateTime max(LocalDateTime a, LocalDateTime b) { + if (a == null) { + return b; + } + if (b == null) { + return a; + } + return a.isAfter(b) ? a : b; + } + + /** + * 是否今天 + * + * @param date 日期 + * @return 是否 + */ + public static boolean isToday(LocalDateTime date) { + return LocalDateTimeUtil.isSameDay(date, LocalDateTime.now()); + } + + /** + * 是否昨天 + * + * @param date 日期 + * @return 是否 + */ + public static boolean isYesterday(LocalDateTime date) { + return LocalDateTimeUtil.isSameDay(date, LocalDateTime.now().minusDays(1)); + } + + + /** + * 根据删除时间,计算还剩多少天被彻底删除(默认保留 90 天) + * + * @param deleteTime 删除时间 + * @return 剩余天数(>=0),0 表示已过期 + */ + public static long getRemainingDays(Date deleteTime) { + if (deleteTime == null) { + throw new IllegalArgumentException("删除时间不能为 null"); + } + + // 将 Date 转换为 LocalDateTime + LocalDateTime deleteDateTime = deleteTime.toInstant() + .atZone(ZoneId.systemDefault()) + .toLocalDateTime(); + + // 当前时间 + LocalDateTime now = LocalDateTime.now(); + + // 到期时间 = 删除时间 + 保留天数 + LocalDateTime expireTime = deleteDateTime.plusDays(RETENTION_DAYS); + + // 如果当前时间已经超过到期时间,剩余天数为 0 + if (now.isAfter(expireTime)) { + return 0; + } + + // 计算剩余天数(向下取整,不进位) + return ChronoUnit.DAYS.between(now, expireTime); + } + +} diff --git a/tashow-framework/tashow-common/src/main/java/com/tashow/cloud/common/util/date/LocalDateTimeUtils.java b/tashow-framework/tashow-common/src/main/java/com/tashow/cloud/common/util/date/LocalDateTimeUtils.java new file mode 100644 index 0000000..73b431b --- /dev/null +++ b/tashow-framework/tashow-common/src/main/java/com/tashow/cloud/common/util/date/LocalDateTimeUtils.java @@ -0,0 +1,309 @@ +package com.tashow.cloud.common.util.date; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.date.DatePattern; +import cn.hutool.core.date.LocalDateTimeUtil; +import cn.hutool.core.lang.Assert; +import cn.hutool.core.util.StrUtil; +import com.tashow.cloud.common.enums.DateIntervalEnum; + +import java.time.*; +import java.time.format.DateTimeParseException; +import java.time.temporal.ChronoUnit; +import java.time.temporal.TemporalAdjusters; +import java.util.ArrayList; +import java.util.List; + +/** + * 时间工具类,用于 {@link LocalDateTime} + * + * @author 芋道源码 + */ +public class LocalDateTimeUtils { + + /** + * 空的 LocalDateTime 对象,主要用于 DB 唯一索引的默认值 + */ + public static LocalDateTime EMPTY = buildTime(1970, 1, 1); + + /** + * 解析时间 + * + * 相比 {@link LocalDateTimeUtil#parse(CharSequence)} 方法来说,会尽量去解析,直到成功 + * + * @param time 时间 + * @return 时间字符串 + */ + public static LocalDateTime parse(String time) { + try { + return LocalDateTimeUtil.parse(time, DatePattern.NORM_DATE_PATTERN); + } catch (DateTimeParseException e) { + return LocalDateTimeUtil.parse(time); + } + } + + public static LocalDateTime addTime(Duration duration) { + return LocalDateTime.now().plus(duration); + } + + public static LocalDateTime minusTime(Duration duration) { + return LocalDateTime.now().minus(duration); + } + + public static boolean beforeNow(LocalDateTime date) { + return date.isBefore(LocalDateTime.now()); + } + + public static boolean afterNow(LocalDateTime date) { + return date.isAfter(LocalDateTime.now()); + } + + /** + * 创建指定时间 + * + * @param year 年 + * @param mouth 月 + * @param day 日 + * @return 指定时间 + */ + public static LocalDateTime buildTime(int year, int mouth, int day) { + return LocalDateTime.of(year, mouth, day, 0, 0, 0); + } + + public static LocalDateTime[] buildBetweenTime(int year1, int mouth1, int day1, + int year2, int mouth2, int day2) { + return new LocalDateTime[]{buildTime(year1, mouth1, day1), buildTime(year2, mouth2, day2)}; + } + + /** + * 判指定断时间,是否在该时间范围内 + * + * @param startTime 开始时间 + * @param endTime 结束时间 + * @param time 指定时间 + * @return 是否 + */ + public static boolean isBetween(LocalDateTime startTime, LocalDateTime endTime, String time) { + if (startTime == null || endTime == null || time == null) { + return false; + } + return LocalDateTimeUtil.isIn(parse(time), startTime, endTime); + } + + /** + * 判断当前时间是否在该时间范围内 + * + * @param startTime 开始时间 + * @param endTime 结束时间 + * @return 是否 + */ + public static boolean isBetween(LocalDateTime startTime, LocalDateTime endTime) { + if (startTime == null || endTime == null) { + return false; + } + return LocalDateTimeUtil.isIn(LocalDateTime.now(), startTime, endTime); + } + + /** + * 判断当前时间是否在该时间范围内 + * + * @param startTime 开始时间 + * @param endTime 结束时间 + * @return 是否 + */ + public static boolean isBetween(String startTime, String endTime) { + if (startTime == null || endTime == null) { + return false; + } + LocalDate nowDate = LocalDate.now(); + return LocalDateTimeUtil.isIn(LocalDateTime.now(), + LocalDateTime.of(nowDate, LocalTime.parse(startTime)), + LocalDateTime.of(nowDate, LocalTime.parse(endTime))); + } + + /** + * 判断时间段是否重叠 + * + * @param startTime1 开始 time1 + * @param endTime1 结束 time1 + * @param startTime2 开始 time2 + * @param endTime2 结束 time2 + * @return 重叠:true 不重叠:false + */ + public static boolean isOverlap(LocalTime startTime1, LocalTime endTime1, LocalTime startTime2, LocalTime endTime2) { + LocalDate nowDate = LocalDate.now(); + return LocalDateTimeUtil.isOverlap(LocalDateTime.of(nowDate, startTime1), LocalDateTime.of(nowDate, endTime1), + LocalDateTime.of(nowDate, startTime2), LocalDateTime.of(nowDate, endTime2)); + } + + /** + * 获取指定日期所在的月份的开始时间 + * 例如:2023-09-30 00:00:00,000 + * + * @param date 日期 + * @return 月份的开始时间 + */ + public static LocalDateTime beginOfMonth(LocalDateTime date) { + return date.with(TemporalAdjusters.firstDayOfMonth()).with(LocalTime.MIN); + } + + /** + * 获取指定日期所在的月份的最后时间 + * 例如:2023-09-30 23:59:59,999 + * + * @param date 日期 + * @return 月份的结束时间 + */ + public static LocalDateTime endOfMonth(LocalDateTime date) { + return date.with(TemporalAdjusters.lastDayOfMonth()).with(LocalTime.MAX); + } + + /** + * 获得指定日期所在季度 + * + * @param date 日期 + * @return 所在季度 + */ + public static int getQuarterOfYear(LocalDateTime date) { + return (date.getMonthValue() - 1) / 3 + 1; + } + + /** + * 获取指定日期到现在过了几天,如果指定日期在当前日期之后,获取结果为负 + * + * @param dateTime 日期 + * @return 相差天数 + */ + public static Long between(LocalDateTime dateTime) { + return LocalDateTimeUtil.between(dateTime, LocalDateTime.now(), ChronoUnit.DAYS); + } + + /** + * 获取今天的开始时间 + * + * @return 今天 + */ + public static LocalDateTime getToday() { + return LocalDateTimeUtil.beginOfDay(LocalDateTime.now()); + } + + /** + * 获取昨天的开始时间 + * + * @return 昨天 + */ + public static LocalDateTime getYesterday() { + return LocalDateTimeUtil.beginOfDay(LocalDateTime.now().minusDays(1)); + } + + /** + * 获取本月的开始时间 + * + * @return 本月 + */ + public static LocalDateTime getMonth() { + return beginOfMonth(LocalDateTime.now()); + } + + /** + * 获取本年的开始时间 + * + * @return 本年 + */ + public static LocalDateTime getYear() { + return LocalDateTime.now().with(TemporalAdjusters.firstDayOfYear()).with(LocalTime.MIN); + } + + public static List getDateRangeList(LocalDateTime startTime, + LocalDateTime endTime, + Integer interval) { + // 1.1 找到枚举 + DateIntervalEnum intervalEnum = DateIntervalEnum.valueOf(interval); + Assert.notNull(intervalEnum, "interval({}} 找不到对应的枚举", interval); + // 1.2 将时间对齐 + startTime = LocalDateTimeUtil.beginOfDay(startTime); + endTime = LocalDateTimeUtil.endOfDay(endTime); + + // 2. 循环,生成时间范围 + List timeRanges = new ArrayList<>(); + switch (intervalEnum) { + case DAY: + while (startTime.isBefore(endTime)) { + timeRanges.add(new LocalDateTime[]{startTime, startTime.plusDays(1).minusNanos(1)}); + startTime = startTime.plusDays(1); + } + break; + case WEEK: + while (startTime.isBefore(endTime)) { + LocalDateTime endOfWeek = startTime.with(DayOfWeek.SUNDAY).plusDays(1).minusNanos(1); + timeRanges.add(new LocalDateTime[]{startTime, endOfWeek}); + startTime = endOfWeek.plusNanos(1); + } + break; + case MONTH: + while (startTime.isBefore(endTime)) { + LocalDateTime endOfMonth = startTime.with(TemporalAdjusters.lastDayOfMonth()).plusDays(1).minusNanos(1); + timeRanges.add(new LocalDateTime[]{startTime, endOfMonth}); + startTime = endOfMonth.plusNanos(1); + } + break; + case QUARTER: + while (startTime.isBefore(endTime)) { + int quarterOfYear = getQuarterOfYear(startTime); + LocalDateTime quarterEnd = quarterOfYear == 4 + ? startTime.with(TemporalAdjusters.lastDayOfYear()).plusDays(1).minusNanos(1) + : startTime.withMonth(quarterOfYear * 3 + 1).withDayOfMonth(1).minusNanos(1); + timeRanges.add(new LocalDateTime[]{startTime, quarterEnd}); + startTime = quarterEnd.plusNanos(1); + } + break; + case YEAR: + while (startTime.isBefore(endTime)) { + LocalDateTime endOfYear = startTime.with(TemporalAdjusters.lastDayOfYear()).plusDays(1).minusNanos(1); + timeRanges.add(new LocalDateTime[]{startTime, endOfYear}); + startTime = endOfYear.plusNanos(1); + } + break; + default: + throw new IllegalArgumentException("Invalid interval: " + interval); + } + // 3. 兜底,最后一个时间,需要保持在 endTime 之前 + LocalDateTime[] lastTimeRange = CollUtil.getLast(timeRanges); + if (lastTimeRange != null) { + lastTimeRange[1] = endTime; + } + return timeRanges; + } + + /** + * 格式化时间范围 + * + * @param startTime 开始时间 + * @param endTime 结束时间 + * @param interval 时间间隔 + * @return 时间范围 + */ + public static String formatDateRange(LocalDateTime startTime, LocalDateTime endTime, Integer interval) { + // 1. 找到枚举 + DateIntervalEnum intervalEnum = DateIntervalEnum.valueOf(interval); + Assert.notNull(intervalEnum, "interval({}} 找不到对应的枚举", interval); + + // 2. 循环,生成时间范围 + switch (intervalEnum) { + case DAY: + return LocalDateTimeUtil.format(startTime, DatePattern.NORM_DATE_PATTERN); + case WEEK: + return LocalDateTimeUtil.format(startTime, DatePattern.NORM_DATE_PATTERN) + + StrUtil.format("(第 {} 周)", LocalDateTimeUtil.weekOfYear(startTime)); + case MONTH: + return LocalDateTimeUtil.format(startTime, DatePattern.NORM_MONTH_PATTERN); + case QUARTER: + return StrUtil.format("{}-Q{}", startTime.getYear(), getQuarterOfYear(startTime)); + case YEAR: + return LocalDateTimeUtil.format(startTime, DatePattern.NORM_YEAR_PATTERN); + default: + throw new IllegalArgumentException("Invalid interval: " + interval); + } + } + +} diff --git a/tashow-framework/tashow-common/src/main/java/com/tashow/cloud/common/util/http/HttpUtils.java b/tashow-framework/tashow-common/src/main/java/com/tashow/cloud/common/util/http/HttpUtils.java new file mode 100644 index 0000000..ec90edd --- /dev/null +++ b/tashow-framework/tashow-common/src/main/java/com/tashow/cloud/common/util/http/HttpUtils.java @@ -0,0 +1,163 @@ +package com.tashow.cloud.common.util.http; + +import cn.hutool.core.codec.Base64; +import cn.hutool.core.map.TableMap; +import cn.hutool.core.net.url.UrlBuilder; +import cn.hutool.core.util.ReflectUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.http.HttpRequest; +import cn.hutool.http.HttpResponse; +import org.springframework.util.StringUtils; +import org.springframework.web.util.UriComponents; +import org.springframework.web.util.UriComponentsBuilder; + +import jakarta.servlet.http.HttpServletRequest; +import java.net.URI; +import java.nio.charset.Charset; +import java.util.Map; + +/** + * HTTP 工具类 + * + * @author 芋道源码 + */ +public class HttpUtils { + + @SuppressWarnings("unchecked") + public static String replaceUrlQuery(String url, String key, String value) { + UrlBuilder builder = UrlBuilder.of(url, Charset.defaultCharset()); + // 先移除 + TableMap query = (TableMap) + ReflectUtil.getFieldValue(builder.getQuery(), "query"); + query.remove(key); + // 后添加 + builder.addQuery(key, value); + return builder.build(); + } + + private String append(String base, Map query, boolean fragment) { + return append(base, query, null, fragment); + } + + /** + * 拼接 URL + * + * copy from Spring Security OAuth2 的 AuthorizationEndpoint 类的 append 方法 + * + * @param base 基础 URL + * @param query 查询参数 + * @param keys query 的 key,对应的原本的 key 的映射。例如说 query 里有个 key 是 xx,实际它的 key 是 extra_xx,则通过 keys 里添加这个映射 + * @param fragment URL 的 fragment,即拼接到 # 中 + * @return 拼接后的 URL + */ + public static String append(String base, Map query, Map keys, boolean fragment) { + UriComponentsBuilder template = UriComponentsBuilder.newInstance(); + UriComponentsBuilder builder = UriComponentsBuilder.fromUriString(base); + URI redirectUri; + try { + // assume it's encoded to start with (if it came in over the wire) + redirectUri = builder.build(true).toUri(); + } catch (Exception e) { + // ... but allow client registrations to contain hard-coded non-encoded values + redirectUri = builder.build().toUri(); + builder = UriComponentsBuilder.fromUri(redirectUri); + } + template.scheme(redirectUri.getScheme()).port(redirectUri.getPort()).host(redirectUri.getHost()) + .userInfo(redirectUri.getUserInfo()).path(redirectUri.getPath()); + + if (fragment) { + StringBuilder values = new StringBuilder(); + if (redirectUri.getFragment() != null) { + String append = redirectUri.getFragment(); + values.append(append); + } + for (String key : query.keySet()) { + if (values.length() > 0) { + values.append("&"); + } + String name = key; + if (keys != null && keys.containsKey(key)) { + name = keys.get(key); + } + values.append(name).append("={").append(key).append("}"); + } + if (values.length() > 0) { + template.fragment(values.toString()); + } + UriComponents encoded = template.build().expand(query).encode(); + builder.fragment(encoded.getFragment()); + } else { + for (String key : query.keySet()) { + String name = key; + if (keys != null && keys.containsKey(key)) { + name = keys.get(key); + } + template.queryParam(name, "{" + key + "}"); + } + template.fragment(redirectUri.getFragment()); + UriComponents encoded = template.build().expand(query).encode(); + builder.query(encoded.getQuery()); + } + return builder.build().toUriString(); + } + + public static String[] obtainBasicAuthorization(HttpServletRequest request) { + String clientId; + String clientSecret; + // 先从 Header 中获取 + String authorization = request.getHeader("Authorization"); + authorization = StrUtil.subAfter(authorization, "Basic ", true); + if (StringUtils.hasText(authorization)) { + authorization = Base64.decodeStr(authorization); + clientId = StrUtil.subBefore(authorization, ":", false); + clientSecret = StrUtil.subAfter(authorization, ":", false); + // 再从 Param 中获取 + } else { + clientId = request.getParameter("client_id"); + clientSecret = request.getParameter("client_secret"); + } + + // 如果两者非空,则返回 + if (StrUtil.isNotEmpty(clientId) && StrUtil.isNotEmpty(clientSecret)) { + return new String[]{clientId, clientSecret}; + } + return null; + } + + /** + * HTTP post 请求,基于 {@link cn.hutool.http.HttpUtil} 实现 + * + * 为什么要封装该方法,因为 HttpUtil 默认封装的方法,没有允许传递 headers 参数 + * + * @param url URL + * @param headers 请求头 + * @param requestBody 请求体 + * @return 请求结果 + */ + public static String post(String url, Map headers, String requestBody) { + try (HttpResponse response = HttpRequest.post(url) + .addHeaders(headers) + .body(requestBody) + .execute()) { + return response.body(); + } + } + + /** + * HTTP get 请求,基于 {@link cn.hutool.http.HttpUtil} 实现 + * + * 为什么要封装该方法,因为 HttpUtil 默认封装的方法,没有允许传递 headers 参数 + * + * @param url URL + * @param headers 请求头 + * @return 请求结果 + */ + public static String get(String url, Map headers) { + try (HttpResponse response = HttpRequest.get(url) + .addHeaders(headers) + .execute()) { + return response.body(); + } + } + +} diff --git a/tashow-framework/tashow-common/src/main/java/com/tashow/cloud/common/util/io/FileUtils.java b/tashow-framework/tashow-common/src/main/java/com/tashow/cloud/common/util/io/FileUtils.java new file mode 100644 index 0000000..7ef8c27 --- /dev/null +++ b/tashow-framework/tashow-common/src/main/java/com/tashow/cloud/common/util/io/FileUtils.java @@ -0,0 +1,84 @@ +package com.tashow.cloud.common.util.io; + +import cn.hutool.core.io.FileTypeUtil; +import cn.hutool.core.io.FileUtil; +import cn.hutool.core.io.file.FileNameUtil; +import cn.hutool.core.util.IdUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.crypto.digest.DigestUtil; +import lombok.SneakyThrows; + +import java.io.ByteArrayInputStream; +import java.io.File; + +/** + * 文件工具类 + * + * @author 芋道源码 + */ +public class FileUtils { + + /** + * 创建临时文件 + * 该文件会在 JVM 退出时,进行删除 + * + * @param data 文件内容 + * @return 文件 + */ + @SneakyThrows + public static File createTempFile(String data) { + File file = createTempFile(); + // 写入内容 + FileUtil.writeUtf8String(data, file); + return file; + } + + /** + * 创建临时文件 + * 该文件会在 JVM 退出时,进行删除 + * + * @param data 文件内容 + * @return 文件 + */ + @SneakyThrows + public static File createTempFile(byte[] data) { + File file = createTempFile(); + // 写入内容 + FileUtil.writeBytes(data, file); + return file; + } + + /** + * 创建临时文件,无内容 + * 该文件会在 JVM 退出时,进行删除 + * + * @return 文件 + */ + @SneakyThrows + public static File createTempFile() { + // 创建文件,通过 UUID 保证唯一 + File file = File.createTempFile(IdUtil.simpleUUID(), null); + // 标记 JVM 退出时,自动删除 + file.deleteOnExit(); + return file; + } + + /** + * 生成文件路径 + * + * @param content 文件内容 + * @param originalName 原始文件名 + * @return path,唯一不可重复 + */ + public static String generatePath(byte[] content, String originalName) { + String sha256Hex = DigestUtil.sha256Hex(content); + // 情况一:如果存在 name,则优先使用 name 的后缀 + if (StrUtil.isNotBlank(originalName)) { + String extName = FileNameUtil.extName(originalName); + return StrUtil.isBlank(extName) ? sha256Hex : sha256Hex + "." + extName; + } + // 情况二:基于 content 计算 + return sha256Hex + '.' + FileTypeUtil.getType(new ByteArrayInputStream(content)); + } + +} diff --git a/tashow-framework/tashow-common/src/main/java/com/tashow/cloud/common/util/io/IoUtils.java b/tashow-framework/tashow-common/src/main/java/com/tashow/cloud/common/util/io/IoUtils.java new file mode 100644 index 0000000..d8f8bd0 --- /dev/null +++ b/tashow-framework/tashow-common/src/main/java/com/tashow/cloud/common/util/io/IoUtils.java @@ -0,0 +1,28 @@ +package com.tashow.cloud.common.util.io; + +import cn.hutool.core.io.IORuntimeException; +import cn.hutool.core.io.IoUtil; +import cn.hutool.core.util.StrUtil; + +import java.io.InputStream; + +/** + * IO 工具类,用于 {@link IoUtil} 缺失的方法 + * + * @author 芋道源码 + */ +public class IoUtils { + + /** + * 从流中读取 UTF8 编码的内容 + * + * @param in 输入流 + * @param isClose 是否关闭 + * @return 内容 + * @throws IORuntimeException IO 异常 + */ + public static String readUtf8(InputStream in, boolean isClose) throws IORuntimeException { + return StrUtil.utf8Str(IoUtil.read(in, isClose)); + } + +} diff --git a/tashow-framework/tashow-common/src/main/java/com/tashow/cloud/common/util/ip/AreaUtils.java b/tashow-framework/tashow-common/src/main/java/com/tashow/cloud/common/util/ip/AreaUtils.java new file mode 100644 index 0000000..0e89ea9 --- /dev/null +++ b/tashow-framework/tashow-common/src/main/java/com/tashow/cloud/common/util/ip/AreaUtils.java @@ -0,0 +1,215 @@ +package com.tashow.cloud.common.util.ip; + +import cn.hutool.core.io.resource.ResourceUtil; +import cn.hutool.core.lang.Assert; +import cn.hutool.core.text.csv.CsvRow; +import cn.hutool.core.text.csv.CsvUtil; +import com.tashow.cloud.common.core.Area; +import com.tashow.cloud.common.enums.AreaTypeEnum; +import com.tashow.cloud.common.util.object.ObjectUtils; +import lombok.NonNull; +import lombok.extern.slf4j.Slf4j; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.function.Function; + +import static com.tashow.cloud.common.util.collection.CollectionUtils.convertList; +import static com.tashow.cloud.common.util.collection.CollectionUtils.findFirst; + + +/** + * 区域工具类 + * + * @author 芋道源码 + */ +@Slf4j +public class AreaUtils { + + /** + * 初始化 SEARCHER + */ + @SuppressWarnings("InstantiationOfUtilityClass") + private final static AreaUtils INSTANCE = new AreaUtils(); + + /** + * Area 内存缓存,提升访问速度 + */ + private static Map areas; + + private AreaUtils() { + long now = System.currentTimeMillis(); + areas = new HashMap<>(); + areas.put(Area.ID_GLOBAL, new Area(Area.ID_GLOBAL, "全球", 0, + null, new ArrayList<>())); + // 从 csv 中加载数据 + List rows = CsvUtil.getReader().read(ResourceUtil.getUtf8Reader("area.csv")).getRows(); + rows.remove(0); // 删除 header + for (CsvRow row : rows) { + // 创建 Area 对象 + Area area = new Area(Integer.valueOf(row.get(0)), row.get(1), Integer.valueOf(row.get(2)), + null, new ArrayList<>()); + // 添加到 areas 中 + areas.put(area.getId(), area); + } + + // 构建父子关系:因为 Area 中没有 parentId 字段,所以需要重复读取 + for (CsvRow row : rows) { + Area area = areas.get(Integer.valueOf(row.get(0))); // 自己 + Area parent = areas.get(Integer.valueOf(row.get(3))); // 父 + Assert.isTrue(area != parent, "{}:父子节点相同", area.getName()); + area.setParent(parent); + parent.getChildren().add(area); + } + log.info("启动加载 AreaUtils 成功,耗时 ({}) 毫秒", System.currentTimeMillis() - now); + } + + /** + * 获得指定编号对应的区域 + * + * @param id 区域编号 + * @return 区域 + */ + public static Area getArea(Integer id) { + return areas.get(id); + } + + /** + * 获得指定区域对应的编号 + * + * @param pathStr 区域路径,例如说:河南省/石家庄市/新华区 + * @return 区域 + */ + public static Area parseArea(String pathStr) { + String[] paths = pathStr.split("/"); + Area area = null; + for (String path : paths) { + if (area == null) { + area = findFirst(areas.values(), item -> item.getName().equals(path)); + } else { + area = findFirst(area.getChildren(), item -> item.getName().equals(path)); + } + } + return area; + } + + /** + * 获取所有节点的全路径名称如:河南省/石家庄市/新华区 + * + * @param areas 地区树 + * @return 所有节点的全路径名称 + */ + public static List getAreaNodePathList(List areas) { + List paths = new ArrayList<>(); + areas.forEach(area -> getAreaNodePathList(area, "", paths)); + return paths; + } + + /** + * 构建一棵树的所有节点的全路径名称,并将其存储为 "祖先/父级/子级" 的形式 + * + * @param node 父节点 + * @param path 全路径名称 + * @param paths 全路径名称列表,省份/城市/地区 + */ + private static void getAreaNodePathList(Area node, String path, List paths) { + if (node == null) { + return; + } + // 构建当前节点的路径 + String currentPath = path.isEmpty() ? node.getName() : path + "/" + node.getName(); + paths.add(currentPath); + // 递归遍历子节点 + for (Area child : node.getChildren()) { + getAreaNodePathList(child, currentPath, paths); + } + } + + /** + * 格式化区域 + * + * @param id 区域编号 + * @return 格式化后的区域 + */ + public static String format(Integer id) { + return format(id, " "); + } + + /** + * 格式化区域 + * + * 例如说: + * 1. id = “静安区”时:上海 上海市 静安区 + * 2. id = “上海市”时:上海 上海市 + * 3. id = “上海”时:上海 + * 4. id = “美国”时:美国 + * 当区域在中国时,默认不显示中国 + * + * @param id 区域编号 + * @param separator 分隔符 + * @return 格式化后的区域 + */ + public static String format(Integer id, String separator) { + // 获得区域 + Area area = areas.get(id); + if (area == null) { + return null; + } + + // 格式化 + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < AreaTypeEnum.values().length; i++) { // 避免死循环 + sb.insert(0, area.getName()); + // “递归”父节点 + area = area.getParent(); + if (area == null + || ObjectUtils.equalsAny(area.getId(), Area.ID_GLOBAL, Area.ID_CHINA)) { // 跳过父节点为中国的情况 + break; + } + sb.insert(0, separator); + } + return sb.toString(); + } + + /** + * 获取指定类型的区域列表 + * + * @param type 区域类型 + * @param func 转换函数 + * @param 结果类型 + * @return 区域列表 + */ + public static List getByType(AreaTypeEnum type, Function func) { + return convertList(areas.values(), func, area -> type.getType().equals(area.getType())); + } + + /** + * 根据区域编号、上级区域类型,获取上级区域编号 + * + * @param id 区域编号 + * @param type 区域类型 + * @return 上级区域编号 + */ + public static Integer getParentIdByType(Integer id, @NonNull AreaTypeEnum type) { + for (int i = 0; i < Byte.MAX_VALUE; i++) { + Area area = AreaUtils.getArea(id); + if (area == null) { + return null; + } + // 情况一:匹配到,返回它 + if (type.getType().equals(area.getType())) { + return area.getId(); + } + // 情况二:找到根节点,返回空 + if (area.getParent() == null || area.getParent().getId() == null) { + return null; + } + // 其它:继续向上查找 + id = area.getParent().getId(); + } + return null; + } + +} diff --git a/tashow-framework/tashow-common/src/main/java/com/tashow/cloud/common/util/ip/IPUtils.java b/tashow-framework/tashow-common/src/main/java/com/tashow/cloud/common/util/ip/IPUtils.java new file mode 100644 index 0000000..984b71d --- /dev/null +++ b/tashow-framework/tashow-common/src/main/java/com/tashow/cloud/common/util/ip/IPUtils.java @@ -0,0 +1,87 @@ +package com.tashow.cloud.common.util.ip; + +import cn.hutool.core.io.resource.ResourceUtil; +import com.tashow.cloud.common.core.Area; +import lombok.SneakyThrows; +import lombok.extern.slf4j.Slf4j; +import org.lionsoul.ip2region.xdb.Searcher; + +import java.io.IOException; + +/** + * IP 工具类 + * + * IP 数据源来自 ip2region.xdb 精简版,基于 项目 + * + * @author wanglhup + */ +@Slf4j +public class IPUtils { + + /** + * 初始化 SEARCHER + */ + @SuppressWarnings("InstantiationOfUtilityClass") + private final static IPUtils INSTANCE = new IPUtils(); + + /** + * IP 查询器,启动加载到内存中 + */ + private static Searcher SEARCHER; + + /** + * 私有化构造 + */ + private IPUtils() { + try { + long now = System.currentTimeMillis(); + byte[] bytes = ResourceUtil.readBytes("ip2region.xdb"); + SEARCHER = Searcher.newWithBuffer(bytes); + log.info("启动加载 IPUtils 成功,耗时 ({}) 毫秒", System.currentTimeMillis() - now); + } catch (IOException e) { + log.error("启动加载 IPUtils 失败", e); + } + } + + /** + * 查询 IP 对应的地区编号 + * + * @param ip IP 地址,格式为 127.0.0.1 + * @return 地区id + */ + @SneakyThrows + public static Integer getAreaId(String ip) { + return Integer.parseInt(SEARCHER.search(ip.trim())); + } + + /** + * 查询 IP 对应的地区编号 + * + * @param ip IP 地址的时间戳,格式参考{@link Searcher#checkIP(String)} 的返回 + * @return 地区编号 + */ + @SneakyThrows + public static Integer getAreaId(long ip) { + return Integer.parseInt(SEARCHER.search(ip)); + } + + /** + * 查询 IP 对应的地区 + * + * @param ip IP 地址,格式为 127.0.0.1 + * @return 地区 + */ + public static Area getArea(String ip) { + return AreaUtils.getArea(getAreaId(ip)); + } + + /** + * 查询 IP 对应的地区 + * + * @param ip IP 地址的时间戳,格式参考{@link Searcher#checkIP(String)} 的返回 + * @return 地区 + */ + public static Area getArea(long ip) { + return AreaUtils.getArea(getAreaId(ip)); + } +} diff --git a/tashow-framework/tashow-common/src/main/java/com/tashow/cloud/common/util/json/JsonUtils.java b/tashow-framework/tashow-common/src/main/java/com/tashow/cloud/common/util/json/JsonUtils.java new file mode 100644 index 0000000..6c67a4b --- /dev/null +++ b/tashow-framework/tashow-common/src/main/java/com/tashow/cloud/common/util/json/JsonUtils.java @@ -0,0 +1,210 @@ +package com.tashow.cloud.common.util.json; + +import cn.hutool.core.util.ArrayUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.json.JSONUtil; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; +import lombok.SneakyThrows; +import lombok.extern.slf4j.Slf4j; + +import java.io.IOException; +import java.lang.reflect.Type; +import java.util.ArrayList; +import java.util.List; + +/** + * JSON 工具类 + * + * @author 芋道源码 + */ +@Slf4j +public class JsonUtils { + + private static ObjectMapper objectMapper = new ObjectMapper(); + + static { + objectMapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false); + objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); + objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL); // 忽略 null 值 + objectMapper.registerModules(new JavaTimeModule()); // 解决 LocalDateTime 的序列化 + } + + /** + * 初始化 objectMapper 属性 + *

+ * 通过这样的方式,使用 Spring 创建的 ObjectMapper Bean + * + * @param objectMapper ObjectMapper 对象 + */ + public static void init(ObjectMapper objectMapper) { + JsonUtils.objectMapper = objectMapper; + } + + @SneakyThrows + public static String toJsonString(Object object) { + return objectMapper.writeValueAsString(object); + } + + @SneakyThrows + public static byte[] toJsonByte(Object object) { + return objectMapper.writeValueAsBytes(object); + } + + @SneakyThrows + public static String toJsonPrettyString(Object object) { + return objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(object); + } + + public static T parseObject(String text, Class clazz) { + if (StrUtil.isEmpty(text)) { + return null; + } + try { + return objectMapper.readValue(text, clazz); + } catch (IOException e) { + log.error("json parse err,json:{}", text, e); + throw new RuntimeException(e); + } + } + + public static T parseObject(String text, String path, Class clazz) { + if (StrUtil.isEmpty(text)) { + return null; + } + try { + JsonNode treeNode = objectMapper.readTree(text); + JsonNode pathNode = treeNode.path(path); + return objectMapper.readValue(pathNode.toString(), clazz); + } catch (IOException e) { + log.error("json parse err,json:{}", text, e); + throw new RuntimeException(e); + } + } + + public static T parseObject(String text, Type type) { + if (StrUtil.isEmpty(text)) { + return null; + } + try { + return objectMapper.readValue(text, objectMapper.getTypeFactory().constructType(type)); + } catch (IOException e) { + log.error("json parse err,json:{}", text, e); + throw new RuntimeException(e); + } + } + + /** + * 将字符串解析成指定类型的对象 + * 使用 {@link #parseObject(String, Class)} 时,在@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS) 的场景下, + * 如果 text 没有 class 属性,则会报错。此时,使用这个方法,可以解决。 + * + * @param text 字符串 + * @param clazz 类型 + * @return 对象 + */ + public static T parseObject2(String text, Class clazz) { + if (StrUtil.isEmpty(text)) { + return null; + } + return JSONUtil.toBean(text, clazz); + } + + public static T parseObject(byte[] bytes, Class clazz) { + if (ArrayUtil.isEmpty(bytes)) { + return null; + } + try { + return objectMapper.readValue(bytes, clazz); + } catch (IOException e) { + log.error("json parse err,json:{}", bytes, e); + throw new RuntimeException(e); + } + } + + public static T parseObject(String text, TypeReference typeReference) { + try { + return objectMapper.readValue(text, typeReference); + } catch (IOException e) { + log.error("json parse err,json:{}", text, e); + throw new RuntimeException(e); + } + } + + /** + * 解析 JSON 字符串成指定类型的对象,如果解析失败,则返回 null + * + * @param text 字符串 + * @param typeReference 类型引用 + * @return 指定类型的对象 + */ + public static T parseObjectQuietly(String text, TypeReference typeReference) { + try { + return objectMapper.readValue(text, typeReference); + } catch (IOException e) { + return null; + } + } + + public static List parseArray(String text, Class clazz) { + if (StrUtil.isEmpty(text)) { + return new ArrayList<>(); + } + try { + return objectMapper.readValue(text, objectMapper.getTypeFactory().constructCollectionType(List.class, clazz)); + } catch (IOException e) { + log.error("json parse err,json:{}", text, e); + throw new RuntimeException(e); + } + } + + public static List parseArray(String text, String path, Class clazz) { + if (StrUtil.isEmpty(text)) { + return null; + } + try { + JsonNode treeNode = objectMapper.readTree(text); + JsonNode pathNode = treeNode.path(path); + return objectMapper.readValue(pathNode.toString(), objectMapper.getTypeFactory().constructCollectionType(List.class, clazz)); + } catch (IOException e) { + log.error("json parse err,json:{}", text, e); + throw new RuntimeException(e); + } + } + + public static JsonNode parseTree(String text) { + try { + return objectMapper.readTree(text); + } catch (IOException e) { + log.error("json parse err,json:{}", text, e); + throw new RuntimeException(e); + } + } + + public static JsonNode parseTree(byte[] text) { + try { + return objectMapper.readTree(text); + } catch (IOException e) { + log.error("json parse err,json:{}", text, e); + throw new RuntimeException(e); + } + } + + public static boolean isJson(String text) { + return JSONUtil.isTypeJSON(text); + } + + /** + * 判断字符串是否为 JSON 类型的字符串 + * @param str 字符串 + */ + public static boolean isJsonObject(String str) { + return JSONUtil.isTypeJSONObject(str); + } + +} diff --git a/tashow-framework/tashow-common/src/main/java/com/tashow/cloud/common/util/json/databind/NumberSerializer.java b/tashow-framework/tashow-common/src/main/java/com/tashow/cloud/common/util/json/databind/NumberSerializer.java new file mode 100644 index 0000000..f227979 --- /dev/null +++ b/tashow-framework/tashow-common/src/main/java/com/tashow/cloud/common/util/json/databind/NumberSerializer.java @@ -0,0 +1,37 @@ +package com.tashow.cloud.common.util.json.databind; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.SerializerProvider; +import com.fasterxml.jackson.databind.annotation.JacksonStdImpl; + +import java.io.IOException; + +/** + * Long 序列化规则 + * + * 会将超长 long 值转换为 string,解决前端 JavaScript 最大安全整数是 2^53-1 的问题 + * + * @author 星语 + */ +@JacksonStdImpl +public class NumberSerializer extends com.fasterxml.jackson.databind.ser.std.NumberSerializer { + + private static final long MAX_SAFE_INTEGER = 9007199254740991L; + private static final long MIN_SAFE_INTEGER = -9007199254740991L; + + public static final NumberSerializer INSTANCE = new NumberSerializer(Number.class); + + public NumberSerializer(Class rawType) { + super(rawType); + } + + @Override + public void serialize(Number value, JsonGenerator gen, SerializerProvider serializers) throws IOException { + // 超出范围 序列化位字符串 + if (value.longValue() > MIN_SAFE_INTEGER && value.longValue() < MAX_SAFE_INTEGER) { + super.serialize(value, gen, serializers); + } else { + gen.writeString(value.toString()); + } + } +} diff --git a/tashow-framework/tashow-common/src/main/java/com/tashow/cloud/common/util/json/databind/TimestampLocalDateTimeDeserializer.java b/tashow-framework/tashow-common/src/main/java/com/tashow/cloud/common/util/json/databind/TimestampLocalDateTimeDeserializer.java new file mode 100644 index 0000000..e70d110 --- /dev/null +++ b/tashow-framework/tashow-common/src/main/java/com/tashow/cloud/common/util/json/databind/TimestampLocalDateTimeDeserializer.java @@ -0,0 +1,27 @@ +package com.tashow.cloud.common.util.json.databind; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; + +import java.io.IOException; +import java.time.Instant; +import java.time.LocalDateTime; +import java.time.ZoneId; + +/** + * 基于时间戳的 LocalDateTime 反序列化器 + * + * @author 老五 + */ +public class TimestampLocalDateTimeDeserializer extends JsonDeserializer { + + public static final TimestampLocalDateTimeDeserializer INSTANCE = new TimestampLocalDateTimeDeserializer(); + + @Override + public LocalDateTime deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { + // 将 Long 时间戳,转换为 LocalDateTime 对象 + return LocalDateTime.ofInstant(Instant.ofEpochMilli(p.getValueAsLong()), ZoneId.systemDefault()); + } + +} diff --git a/tashow-framework/tashow-common/src/main/java/com/tashow/cloud/common/util/json/databind/TimestampLocalDateTimeSerializer.java b/tashow-framework/tashow-common/src/main/java/com/tashow/cloud/common/util/json/databind/TimestampLocalDateTimeSerializer.java new file mode 100644 index 0000000..6c25bdf --- /dev/null +++ b/tashow-framework/tashow-common/src/main/java/com/tashow/cloud/common/util/json/databind/TimestampLocalDateTimeSerializer.java @@ -0,0 +1,26 @@ +package com.tashow.cloud.common.util.json.databind; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.JsonSerializer; +import com.fasterxml.jackson.databind.SerializerProvider; + +import java.io.IOException; +import java.time.LocalDateTime; +import java.time.ZoneId; + +/** + * 基于时间戳的 LocalDateTime 序列化器 + * + * @author 老五 + */ +public class TimestampLocalDateTimeSerializer extends JsonSerializer { + + public static final TimestampLocalDateTimeSerializer INSTANCE = new TimestampLocalDateTimeSerializer(); + + @Override + public void serialize(LocalDateTime value, JsonGenerator gen, SerializerProvider serializers) throws IOException { + // 将 LocalDateTime 对象,转换为 Long 时间戳 + gen.writeNumber(value.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli()); + } + +} diff --git a/tashow-framework/tashow-common/src/main/java/com/tashow/cloud/common/util/monitor/TracerUtils.java b/tashow-framework/tashow-common/src/main/java/com/tashow/cloud/common/util/monitor/TracerUtils.java new file mode 100644 index 0000000..e6c0372 --- /dev/null +++ b/tashow-framework/tashow-common/src/main/java/com/tashow/cloud/common/util/monitor/TracerUtils.java @@ -0,0 +1,30 @@ +package com.tashow.cloud.common.util.monitor; + +import org.apache.skywalking.apm.toolkit.trace.TraceContext; + +/** + * 链路追踪工具类 + * + * 考虑到每个 starter 都需要用到该工具类,所以放到 common 模块下的 util 包下 + * + * @author 芋道源码 + */ +public class TracerUtils { + + /** + * 私有化构造方法 + */ + private TracerUtils() { + } + + /** + * 获得链路追踪编号,直接返回 SkyWalking 的 TraceId。 + * 如果不存在的话为空字符串!!! + * + * @return 链路追踪编号 + */ + public static String getTraceId() { + return TraceContext.traceId(); + } + +} diff --git a/tashow-framework/tashow-common/src/main/java/com/tashow/cloud/common/util/number/MoneyUtils.java b/tashow-framework/tashow-common/src/main/java/com/tashow/cloud/common/util/number/MoneyUtils.java new file mode 100644 index 0000000..23ccbd2 --- /dev/null +++ b/tashow-framework/tashow-common/src/main/java/com/tashow/cloud/common/util/number/MoneyUtils.java @@ -0,0 +1,131 @@ +package com.tashow.cloud.common.util.number; + +import cn.hutool.core.math.Money; +import cn.hutool.core.util.NumberUtil; + +import java.math.BigDecimal; +import java.math.RoundingMode; + +/** + * 金额工具类 + * + * @author 芋道源码 + */ +public class MoneyUtils { + + /** + * 金额的小数位数 + */ + private static final int PRICE_SCALE = 2; + + /** + * 百分比对应的 BigDecimal 对象 + */ + public static final BigDecimal PERCENT_100 = BigDecimal.valueOf(100); + + /** + * 计算百分比金额,四舍五入 + * + * @param price 金额 + * @param rate 百分比,例如说 56.77% 则传入 56.77 + * @return 百分比金额 + */ + public static Integer calculateRatePrice(Integer price, Double rate) { + return calculateRatePrice(price, rate, 0, RoundingMode.HALF_UP).intValue(); + } + + /** + * 计算百分比金额,向下传入 + * + * @param price 金额 + * @param rate 百分比,例如说 56.77% 则传入 56.77 + * @return 百分比金额 + */ + public static Integer calculateRatePriceFloor(Integer price, Double rate) { + return calculateRatePrice(price, rate, 0, RoundingMode.FLOOR).intValue(); + } + + /** + * 计算百分比金额 + * + * @param price 金额(单位分) + * @param count 数量 + * @param percent 折扣(单位分),列如 60.2%,则传入 6020 + * @return 商品总价 + */ + public static Integer calculator(Integer price, Integer count, Integer percent) { + price = price * count; + if (percent == null) { + return price; + } + return MoneyUtils.calculateRatePriceFloor(price, (double) (percent / 100)); + } + + /** + * 计算百分比金额 + * + * @param price 金额 + * @param rate 百分比,例如说 56.77% 则传入 56.77 + * @param scale 保留小数位数 + * @param roundingMode 舍入模式 + */ + public static BigDecimal calculateRatePrice(Number price, Number rate, int scale, RoundingMode roundingMode) { + return NumberUtil.toBigDecimal(price).multiply(NumberUtil.toBigDecimal(rate)) // 乘以 + .divide(BigDecimal.valueOf(100), scale, roundingMode); // 除以 100 + } + + /** + * 分转元 + * + * @param fen 分 + * @return 元 + */ + public static BigDecimal fenToYuan(int fen) { + return new Money(0, fen).getAmount(); + } + + /** + * 分转元(字符串) + * + * 例如说 fen 为 1 时,则结果为 0.01 + * + * @param fen 分 + * @return 元 + */ + public static String fenToYuanStr(int fen) { + return new Money(0, fen).toString(); + } + + /** + * 金额相乘,默认进行四舍五入 + * + * 位数:{@link #PRICE_SCALE} + * + * @param price 金额 + * @param count 数量 + * @return 金额相乘结果 + */ + public static BigDecimal priceMultiply(BigDecimal price, BigDecimal count) { + if (price == null || count == null) { + return null; + } + return price.multiply(count).setScale(PRICE_SCALE, RoundingMode.HALF_UP); + } + + /** + * 金额相乘(百分比),默认进行四舍五入 + * + * 位数:{@link #PRICE_SCALE} + * + * @param price 金额 + * @param percent 百分比 + * @return 金额相乘结果 + */ + public static BigDecimal priceMultiplyPercent(BigDecimal price, BigDecimal percent) { + if (price == null || percent == null) { + return null; + } + return price.multiply(percent).divide(PERCENT_100, PRICE_SCALE, RoundingMode.HALF_UP); + } + +} diff --git a/tashow-framework/tashow-common/src/main/java/com/tashow/cloud/common/util/number/NumberUtils.java b/tashow-framework/tashow-common/src/main/java/com/tashow/cloud/common/util/number/NumberUtils.java new file mode 100644 index 0000000..8566be4 --- /dev/null +++ b/tashow-framework/tashow-common/src/main/java/com/tashow/cloud/common/util/number/NumberUtils.java @@ -0,0 +1,64 @@ +package com.tashow.cloud.common.util.number; + +import cn.hutool.core.util.NumberUtil; +import cn.hutool.core.util.StrUtil; + +import java.math.BigDecimal; + +/** + * 数字的工具类,补全 {@link NumberUtil} 的功能 + * + * @author 芋道源码 + */ +public class NumberUtils { + + public static Long parseLong(String str) { + return StrUtil.isNotEmpty(str) ? Long.valueOf(str) : null; + } + + public static Integer parseInt(String str) { + return StrUtil.isNotEmpty(str) ? Integer.valueOf(str) : null; + } + + /** + * 通过经纬度获取地球上两点之间的距离 + * + * 参考 <DistanceUtil> 实现,目前它已经被 hutool 删除 + * + * @param lat1 经度1 + * @param lng1 纬度1 + * @param lat2 经度2 + * @param lng2 纬度2 + * @return 距离,单位:千米 + */ + public static double getDistance(double lat1, double lng1, double lat2, double lng2) { + double radLat1 = lat1 * Math.PI / 180.0; + double radLat2 = lat2 * Math.PI / 180.0; + double a = radLat1 - radLat2; + double b = lng1 * Math.PI / 180.0 - lng2 * Math.PI / 180.0; + double distance = 2 * Math.asin(Math.sqrt(Math.pow(Math.sin(a / 2), 2) + + Math.cos(radLat1) * Math.cos(radLat2) + * Math.pow(Math.sin(b / 2), 2))); + distance = distance * 6378.137; + distance = Math.round(distance * 10000d) / 10000d; + return distance; + } + + /** + * 提供精确的乘法运算 + * + * 和 hutool {@link NumberUtil#mul(BigDecimal...)} 的差别是,如果存在 null,则返回 null + * + * @param values 多个被乘值 + * @return 积 + */ + public static BigDecimal mul(BigDecimal... values) { + for (BigDecimal value : values) { + if (value == null) { + return null; + } + } + return NumberUtil.mul(values); + } + +} diff --git a/tashow-framework/tashow-common/src/main/java/com/tashow/cloud/common/util/object/BeanUtils.java b/tashow-framework/tashow-common/src/main/java/com/tashow/cloud/common/util/object/BeanUtils.java new file mode 100644 index 0000000..2617e00 --- /dev/null +++ b/tashow-framework/tashow-common/src/main/java/com/tashow/cloud/common/util/object/BeanUtils.java @@ -0,0 +1,69 @@ +package com.tashow.cloud.common.util.object; + +import cn.hutool.core.bean.BeanUtil; +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.common.util.collection.CollectionUtils; + +import java.util.List; +import java.util.function.Consumer; + +/** + * Bean 工具类 + * + * 1. 默认使用 {@link BeanUtil} 作为实现类,虽然不同 bean 工具的性能有差别,但是对绝大多数同学的项目,不用在意这点性能 + * 2. 针对复杂的对象转换,可以搜参考 AuthConvert 实现,通过 mapstruct + default 配合实现 + * + * @author 芋道源码 + */ +public class BeanUtils { + + public static T toBean(Object source, Class targetClass) { + return BeanUtil.toBean(source, targetClass); + } + + public static T toBean(Object source, Class targetClass, Consumer peek) { + T target = toBean(source, targetClass); + if (target != null) { + peek.accept(target); + } + return target; + } + + public static List toBean(List source, Class targetType) { + if (source == null) { + return null; + } + return CollectionUtils.convertList(source, s -> toBean(s, targetType)); + } + + public static List toBean(List source, Class targetType, Consumer peek) { + List list = toBean(source, targetType); + if (list != null) { + list.forEach(peek); + } + return list; + } + + public static PageResult toBean(PageResult source, Class targetType) { + return toBean(source, targetType, null); + } + + public static PageResult toBean(PageResult source, Class targetType, Consumer peek) { + if (source == null) { + return null; + } + List list = toBean(source.getList(), targetType); + if (peek != null) { + list.forEach(peek); + } + return new PageResult<>(list, source.getTotal()); + } + + public static void copyProperties(Object source, Object target) { + if (source == null || target == null) { + return; + } + BeanUtil.copyProperties(source, target, false); + } + +} diff --git a/tashow-framework/tashow-common/src/main/java/com/tashow/cloud/common/util/object/ObjectUtils.java b/tashow-framework/tashow-common/src/main/java/com/tashow/cloud/common/util/object/ObjectUtils.java new file mode 100644 index 0000000..1abef2d --- /dev/null +++ b/tashow-framework/tashow-common/src/main/java/com/tashow/cloud/common/util/object/ObjectUtils.java @@ -0,0 +1,63 @@ +package com.tashow.cloud.common.util.object; + +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.ReflectUtil; + +import java.lang.reflect.Field; +import java.util.Arrays; +import java.util.function.Consumer; + +/** + * Object 工具类 + * + * @author 芋道源码 + */ +public class ObjectUtils { + + /** + * 复制对象,并忽略 Id 编号 + * + * @param object 被复制对象 + * @param consumer 消费者,可以二次编辑被复制对象 + * @return 复制后的对象 + */ + public static T cloneIgnoreId(T object, Consumer consumer) { + T result = ObjectUtil.clone(object); + // 忽略 id 编号 + Field field = ReflectUtil.getField(object.getClass(), "id"); + if (field != null) { + ReflectUtil.setFieldValue(result, field, null); + } + // 二次编辑 + if (result != null) { + consumer.accept(result); + } + return result; + } + + public static > T max(T obj1, T obj2) { + if (obj1 == null) { + return obj2; + } + if (obj2 == null) { + return obj1; + } + return obj1.compareTo(obj2) > 0 ? obj1 : obj2; + } + + @SafeVarargs + public static T defaultIfNull(T... array) { + for (T item : array) { + if (item != null) { + return item; + } + } + return null; + } + + @SafeVarargs + public static boolean equalsAny(T obj, T... array) { + return Arrays.asList(array).contains(obj); + } + +} diff --git a/tashow-framework/tashow-common/src/main/java/com/tashow/cloud/common/util/object/PageUtils.java b/tashow-framework/tashow-common/src/main/java/com/tashow/cloud/common/util/object/PageUtils.java new file mode 100644 index 0000000..ca39c50 --- /dev/null +++ b/tashow-framework/tashow-common/src/main/java/com/tashow/cloud/common/util/object/PageUtils.java @@ -0,0 +1,67 @@ +package com.tashow.cloud.common.util.object; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.lang.func.Func1; +import cn.hutool.core.lang.func.LambdaUtil; +import cn.hutool.core.util.ArrayUtil; +import com.tashow.cloud.common.pojo.PageParam; +import com.tashow.cloud.common.pojo.SortablePageParam; +import com.tashow.cloud.common.pojo.SortingField; +import org.springframework.util.Assert; + +import static java.util.Collections.singletonList; + +/** + * {@link PageParam} 工具类 + * + * @author 芋道源码 + */ +public class PageUtils { + + private static final Object[] ORDER_TYPES = new String[]{SortingField.ORDER_ASC, SortingField.ORDER_DESC}; + + public static int getStart(PageParam pageParam) { + return (pageParam.getPageNo() - 1) * pageParam.getPageSize(); + } + + /** + * 构建排序字段(默认倒序) + * + * @param func 排序字段的 Lambda 表达式 + * @param 排序字段所属的类型 + * @return 排序字段 + */ + public static SortingField buildSortingField(Func1 func) { + return buildSortingField(func, SortingField.ORDER_DESC); + } + + /** + * 构建排序字段 + * + * @param func 排序字段的 Lambda 表达式 + * @param order 排序类型 {@link SortingField#ORDER_ASC} {@link SortingField#ORDER_DESC} + * @param 排序字段所属的类型 + * @return 排序字段 + */ + public static SortingField buildSortingField(Func1 func, String order) { + Assert.isTrue(ArrayUtil.contains(ORDER_TYPES, order), String.format("字段的排序类型只能是 %s/%s", ORDER_TYPES)); + + String fieldName = LambdaUtil.getFieldName(func); + return new SortingField(fieldName, order); + } + + /** + * 构建默认的排序字段 + * 如果排序字段为空,则设置排序字段;否则忽略 + * + * @param sortablePageParam 排序分页查询参数 + * @param func 排序字段的 Lambda 表达式 + * @param 排序字段所属的类型 + */ + public static void buildDefaultSortingField(SortablePageParam sortablePageParam, Func1 func) { + if (sortablePageParam != null && CollUtil.isEmpty(sortablePageParam.getSortingFields())) { + sortablePageParam.setSortingFields(singletonList(buildSortingField(func))); + } + } + +} diff --git a/tashow-framework/tashow-common/src/main/java/com/tashow/cloud/common/util/package-info.java b/tashow-framework/tashow-common/src/main/java/com/tashow/cloud/common/util/package-info.java new file mode 100644 index 0000000..f7bb781 --- /dev/null +++ b/tashow-framework/tashow-common/src/main/java/com/tashow/cloud/common/util/package-info.java @@ -0,0 +1,7 @@ +/** + * 对于工具类的选择,优先查找 Hutool 中有没对应的方法 + * 如果没有,则自己封装对应的工具类,以 Utils 结尾,用于区分 + * + * ps:如果担心 Hutool 存在坑的问题,可以阅读 Hutool 的实现源码,以确保可靠性。并且,可以补充相关的单元测试。 + */ +package com.tashow.cloud.common.util; diff --git a/tashow-framework/tashow-common/src/main/java/com/tashow/cloud/common/util/serializer/ImgJsonSerializer.java b/tashow-framework/tashow-common/src/main/java/com/tashow/cloud/common/util/serializer/ImgJsonSerializer.java new file mode 100644 index 0000000..9af3bca --- /dev/null +++ b/tashow-framework/tashow-common/src/main/java/com/tashow/cloud/common/util/serializer/ImgJsonSerializer.java @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved. + * + * https://www.mall4j.com/ + * + * 未经允许,不可做商业用途! + * + * 版权所有,侵权必究! + */ + +package com.tashow.cloud.common.util.serializer; + +import cn.hutool.core.util.StrUtil; +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.JsonSerializer; +import com.fasterxml.jackson.databind.SerializerProvider; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import java.io.IOException; +import java.util.Objects; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * @author lanhai + */ +@Component +public class ImgJsonSerializer extends JsonSerializer { + + /* @Autowired + private Qiniu qiniu; + @Autowired + private ImgUploadUtil imgUploadUtil;*/ + + @Override + public void serialize(String value, JsonGenerator gen, SerializerProvider serializers) throws IOException { + /*if (StrUtil.isBlank(value)) { + gen.writeString(StrUtil.EMPTY); + return; + } + String[] imgs = value.split(StrUtil.COMMA); + StringBuilder sb = new StringBuilder(); + String resourceUrl = ""; + String rule="^((http[s]{0,1})://)"; + Pattern pattern= Pattern.compile(rule); + if (Objects.equals(imgUploadUtil.getUploadType(), 2)) { + resourceUrl = qiniu.getResourcesUrl(); + } else if (Objects.equals(imgUploadUtil.getUploadType(), 1)) { + resourceUrl = imgUploadUtil.getResourceUrl(); + } + for (String img : imgs) { + Matcher matcher = pattern.matcher(img); + //若图片以http或https开头,直接返回 + if (matcher.find()){ + sb.append(img).append(StrUtil.COMMA); + }else { + sb.append(resourceUrl).append(img).append(StrUtil.COMMA); + } + } + sb.deleteCharAt(sb.length()-1); + gen.writeString(sb.toString());*/ + } +} diff --git a/tashow-framework/tashow-common/src/main/java/com/tashow/cloud/common/util/servlet/ServletUtils.java b/tashow-framework/tashow-common/src/main/java/com/tashow/cloud/common/util/servlet/ServletUtils.java new file mode 100644 index 0000000..ff848c2 --- /dev/null +++ b/tashow-framework/tashow-common/src/main/java/com/tashow/cloud/common/util/servlet/ServletUtils.java @@ -0,0 +1,101 @@ +package com.tashow.cloud.common.util.servlet; + +import cn.hutool.core.util.StrUtil; +import cn.hutool.extra.servlet.JakartaServletUtil; +import com.tashow.cloud.common.util.json.JsonUtils; +import jakarta.servlet.ServletRequest; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.springframework.http.MediaType; +import org.springframework.web.context.request.RequestAttributes; +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.context.request.ServletRequestAttributes; + +import java.util.Map; + +/** + * 客户端工具类 + * + * @author 芋道源码 + */ +public class ServletUtils { + + /** + * 返回 JSON 字符串 + * + * @param response 响应 + * @param object 对象,会序列化成 JSON 字符串 + */ + @SuppressWarnings("deprecation") // 必须使用 APPLICATION_JSON_UTF8_VALUE,否则会乱码 + public static void writeJSON(HttpServletResponse response, Object object) { + String content = JsonUtils.toJsonString(object); + JakartaServletUtil.write(response, content, MediaType.APPLICATION_JSON_UTF8_VALUE); + } + + /** + * @param request 请求 + * @return ua + */ + public static String getUserAgent(HttpServletRequest request) { + String ua = request.getHeader("User-Agent"); + return ua != null ? ua : ""; + } + + /** + * 获得请求 + * + * @return HttpServletRequest + */ + public static HttpServletRequest getRequest() { + RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes(); + if (!(requestAttributes instanceof ServletRequestAttributes)) { + return null; + } + return ((ServletRequestAttributes) requestAttributes).getRequest(); + } + + public static String getUserAgent() { + HttpServletRequest request = getRequest(); + if (request == null) { + return null; + } + return getUserAgent(request); + } + + public static String getClientIP() { + HttpServletRequest request = getRequest(); + if (request == null) { + return null; + } + return JakartaServletUtil.getClientIP(request); + } + + public static boolean isJsonRequest(ServletRequest request) { + return StrUtil.startWithIgnoreCase(request.getContentType(), MediaType.APPLICATION_JSON_VALUE); + } + + public static String getBody(HttpServletRequest request) { + // 只有在 json 请求在读取,因为只有 CacheRequestBodyFilter 才会进行缓存,支持重复读取 + if (isJsonRequest(request)) { + return JakartaServletUtil.getBody(request); + } + return null; + } + + public static byte[] getBodyBytes(HttpServletRequest request) { + // 只有在 json 请求在读取,因为只有 CacheRequestBodyFilter 才会进行缓存,支持重复读取 + if (isJsonRequest(request)) { + return JakartaServletUtil.getBodyBytes(request); + } + return null; + } + + public static String getClientIP(HttpServletRequest request) { + return JakartaServletUtil.getClientIP(request); + } + + public static Map getParamMap(HttpServletRequest request) { + return JakartaServletUtil.getParamMap(request); + } + +} diff --git a/tashow-framework/tashow-common/src/main/java/com/tashow/cloud/common/util/spring/SpringExpressionUtils.java b/tashow-framework/tashow-common/src/main/java/com/tashow/cloud/common/util/spring/SpringExpressionUtils.java new file mode 100644 index 0000000..ac23474 --- /dev/null +++ b/tashow-framework/tashow-common/src/main/java/com/tashow/cloud/common/util/spring/SpringExpressionUtils.java @@ -0,0 +1,109 @@ +package com.tashow.cloud.common.util.spring; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.map.MapUtil; +import cn.hutool.core.util.ArrayUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.extra.spring.SpringUtil; +import org.aspectj.lang.JoinPoint; +import org.aspectj.lang.reflect.MethodSignature; +import org.springframework.context.expression.BeanFactoryResolver; +import org.springframework.core.DefaultParameterNameDiscoverer; +import org.springframework.core.ParameterNameDiscoverer; +import org.springframework.expression.EvaluationContext; +import org.springframework.expression.Expression; +import org.springframework.expression.ExpressionParser; +import org.springframework.expression.spel.standard.SpelExpressionParser; +import org.springframework.expression.spel.support.StandardEvaluationContext; + +import java.lang.reflect.Method; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +/** + * Spring EL 表达式的工具类 + * + * @author mashu + */ +public class SpringExpressionUtils { + + /** + * Spring EL 表达式解析器 + */ + private static final ExpressionParser EXPRESSION_PARSER = new SpelExpressionParser(); + /** + * 参数名发现器 + */ + private static final ParameterNameDiscoverer PARAMETER_NAME_DISCOVERER = new DefaultParameterNameDiscoverer(); + + private SpringExpressionUtils() { + } + + /** + * 从切面中,单个解析 EL 表达式的结果 + * + * @param joinPoint 切面点 + * @param expressionString EL 表达式数组 + * @return 执行界面 + */ + public static Object parseExpression(JoinPoint joinPoint, String expressionString) { + Map result = parseExpressions(joinPoint, Collections.singletonList(expressionString)); + return result.get(expressionString); + } + + /** + * 从切面中,批量解析 EL 表达式的结果 + * + * @param joinPoint 切面点 + * @param expressionStrings EL 表达式数组 + * @return 结果,key 为表达式,value 为对应值 + */ + public static Map parseExpressions(JoinPoint joinPoint, List expressionStrings) { + // 如果为空,则不进行解析 + if (CollUtil.isEmpty(expressionStrings)) { + return MapUtil.newHashMap(); + } + + // 第一步,构建解析的上下文 EvaluationContext + // 通过 joinPoint 获取被注解方法 + MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature(); + Method method = methodSignature.getMethod(); + // 使用 spring 的 ParameterNameDiscoverer 获取方法形参名数组 + String[] paramNames = PARAMETER_NAME_DISCOVERER.getParameterNames(method); + // Spring 的表达式上下文对象 + EvaluationContext context = new StandardEvaluationContext(); + // 给上下文赋值 + if (ArrayUtil.isNotEmpty(paramNames)) { + Object[] args = joinPoint.getArgs(); + for (int i = 0; i < paramNames.length; i++) { + context.setVariable(paramNames[i], args[i]); + } + } + + // 第二步,逐个参数解析 + Map result = MapUtil.newHashMap(expressionStrings.size(), true); + expressionStrings.forEach(key -> { + Object value = EXPRESSION_PARSER.parseExpression(key).getValue(context); + result.put(key, value); + }); + return result; + } + + /** + * 从 Bean 工厂,解析 EL 表达式的结果 + * + * @param expressionString EL 表达式 + * @return 执行界面 + */ + public static Object parseExpression(String expressionString) { + if (StrUtil.isBlank(expressionString)) { + return null; + } + Expression expression = EXPRESSION_PARSER.parseExpression(expressionString); + StandardEvaluationContext context = new StandardEvaluationContext(); + context.setBeanResolver(new BeanFactoryResolver(SpringUtil.getApplicationContext())); + return expression.getValue(context); + } + +} diff --git a/tashow-framework/tashow-common/src/main/java/com/tashow/cloud/common/util/spring/SpringUtils.java b/tashow-framework/tashow-common/src/main/java/com/tashow/cloud/common/util/spring/SpringUtils.java new file mode 100644 index 0000000..e742dee --- /dev/null +++ b/tashow-framework/tashow-common/src/main/java/com/tashow/cloud/common/util/spring/SpringUtils.java @@ -0,0 +1,24 @@ +package com.tashow.cloud.common.util.spring; + +import cn.hutool.extra.spring.SpringUtil; + +import java.util.Objects; + +/** + * Spring 工具类 + * + * @author 芋道源码 + */ +public class SpringUtils extends SpringUtil { + + /** + * 是否为生产环境 + * + * @return 是否生产环境 + */ + public static boolean isProd() { + String activeProfile = getActiveProfile(); + return Objects.equals("prod", activeProfile); + } + +} diff --git a/tashow-framework/tashow-common/src/main/java/com/tashow/cloud/common/util/string/StrUtils.java b/tashow-framework/tashow-common/src/main/java/com/tashow/cloud/common/util/string/StrUtils.java new file mode 100644 index 0000000..f88dd7a --- /dev/null +++ b/tashow-framework/tashow-common/src/main/java/com/tashow/cloud/common/util/string/StrUtils.java @@ -0,0 +1,80 @@ +package com.tashow.cloud.common.util.string; + +import cn.hutool.core.text.StrPool; +import cn.hutool.core.util.ArrayUtil; +import cn.hutool.core.util.StrUtil; + +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +/** + * 字符串工具类 + * + * @author 芋道源码 + */ +public class StrUtils { + + public static String maxLength(CharSequence str, int maxLength) { + return StrUtil.maxLength(str, maxLength - 3); // -3 的原因,是该方法会补充 ... 恰好 + } + + /** + * 给定字符串是否以任何一个字符串开始 + * 给定字符串和数组为空都返回 false + * + * @param str 给定字符串 + * @param prefixes 需要检测的开始字符串 + * @since 3.0.6 + */ + public static boolean startWithAny(String str, Collection prefixes) { + if (StrUtil.isEmpty(str) || ArrayUtil.isEmpty(prefixes)) { + return false; + } + + for (CharSequence suffix : prefixes) { + if (StrUtil.startWith(str, suffix, false)) { + return true; + } + } + return false; + } + + public static List splitToLong(String value, CharSequence separator) { + long[] longs = StrUtil.splitToLong(value, separator); + return Arrays.stream(longs).boxed().collect(Collectors.toList()); + } + + public static Set splitToLongSet(String value) { + return splitToLongSet(value, StrPool.COMMA); + } + + public static Set splitToLongSet(String value, CharSequence separator) { + long[] longs = StrUtil.splitToLong(value, separator); + return Arrays.stream(longs).boxed().collect(Collectors.toSet()); + } + + public static List splitToInteger(String value, CharSequence separator) { + int[] integers = StrUtil.splitToInt(value, separator); + return Arrays.stream(integers).boxed().collect(Collectors.toList()); + } + + /** + * 移除字符串中,包含指定字符串的行 + * + * @param content 字符串 + * @param sequence 包含的字符串 + * @return 移除后的字符串 + */ + public static String removeLineContains(String content, String sequence) { + if (StrUtil.isEmpty(content) || StrUtil.isEmpty(sequence)) { + return content; + } + return Arrays.stream(content.split("\n")) + .filter(line -> !line.contains(sequence)) + .collect(Collectors.joining("\n")); + } + +} diff --git a/tashow-framework/tashow-common/src/main/java/com/tashow/cloud/common/util/validation/ValidationUtils.java b/tashow-framework/tashow-common/src/main/java/com/tashow/cloud/common/util/validation/ValidationUtils.java new file mode 100644 index 0000000..502d5e9 --- /dev/null +++ b/tashow-framework/tashow-common/src/main/java/com/tashow/cloud/common/util/validation/ValidationUtils.java @@ -0,0 +1,55 @@ +package com.tashow.cloud.common.util.validation; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.lang.Assert; +import jakarta.validation.ConstraintViolation; +import jakarta.validation.ConstraintViolationException; +import jakarta.validation.Validation; +import jakarta.validation.Validator; +import org.springframework.util.StringUtils; + +import java.util.Set; +import java.util.regex.Pattern; + +/** + * 校验工具类 + * + * @author 芋道源码 + */ +public class ValidationUtils { + + private static final Pattern PATTERN_MOBILE = Pattern.compile("^(?:(?:\\+|00)86)?1(?:(?:3[\\d])|(?:4[0,1,4-9])|(?:5[0-3,5-9])|(?:6[2,5-7])|(?:7[0-8])|(?:8[\\d])|(?:9[0-3,5-9]))\\d{8}$"); + + private static final Pattern PATTERN_URL = Pattern.compile("^(https?|ftp|file)://[-a-zA-Z0-9+&@#/%?=~_|!:,.;]*[-a-zA-Z0-9+&@#/%=~_|]"); + + private static final Pattern PATTERN_XML_NCNAME = Pattern.compile("[a-zA-Z_][\\-_.0-9_a-zA-Z$]*"); + + public static boolean isMobile(String mobile) { + return StringUtils.hasText(mobile) + && PATTERN_MOBILE.matcher(mobile).matches(); + } + + public static boolean isURL(String url) { + return StringUtils.hasText(url) + && PATTERN_URL.matcher(url).matches(); + } + + public static boolean isXmlNCName(String str) { + return StringUtils.hasText(str) + && PATTERN_XML_NCNAME.matcher(str).matches(); + } + + public static void validate(Object object, Class... groups) { + Validator validator = Validation.buildDefaultValidatorFactory().getValidator(); + Assert.notNull(validator); + validate(validator, object, groups); + } + + public static void validate(Validator validator, Object object, Class... groups) { + Set> constraintViolations = validator.validate(object, groups); + if (CollUtil.isNotEmpty(constraintViolations)) { + throw new ConstraintViolationException(constraintViolations); + } + } + +} diff --git a/tashow-framework/tashow-common/src/main/java/com/tashow/cloud/common/validation/InEnum.java b/tashow-framework/tashow-common/src/main/java/com/tashow/cloud/common/validation/InEnum.java new file mode 100644 index 0000000..d6ef1b6 --- /dev/null +++ b/tashow-framework/tashow-common/src/main/java/com/tashow/cloud/common/validation/InEnum.java @@ -0,0 +1,35 @@ +package com.tashow.cloud.common.validation; + +import com.tashow.cloud.common.core.ArrayValuable; +import jakarta.validation.Constraint; +import jakarta.validation.Payload; + +import java.lang.annotation.*; + +@Target({ + ElementType.METHOD, + ElementType.FIELD, + ElementType.ANNOTATION_TYPE, + ElementType.CONSTRUCTOR, + ElementType.PARAMETER, + ElementType.TYPE_USE +}) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@Constraint( + validatedBy = {InEnumValidator.class, InEnumCollectionValidator.class} +) +public @interface InEnum { + + /** + * @return 实现 ArrayValuable 接口的类 + */ + Class> value(); + + String message() default "必须在指定范围 {value}"; + + Class[] groups() default {}; + + Class[] payload() default {}; + +} diff --git a/tashow-framework/tashow-common/src/main/java/com/tashow/cloud/common/validation/InEnumCollectionValidator.java b/tashow-framework/tashow-common/src/main/java/com/tashow/cloud/common/validation/InEnumCollectionValidator.java new file mode 100644 index 0000000..bb83bb3 --- /dev/null +++ b/tashow-framework/tashow-common/src/main/java/com/tashow/cloud/common/validation/InEnumCollectionValidator.java @@ -0,0 +1,44 @@ +package com.tashow.cloud.common.validation; + +import cn.hutool.core.collection.CollUtil; +import com.tashow.cloud.common.core.ArrayValuable; +import jakarta.validation.ConstraintValidator; +import jakarta.validation.ConstraintValidatorContext; + +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.List; + +public class InEnumCollectionValidator implements ConstraintValidator> { + + private List values; + + @Override + public void initialize(InEnum annotation) { + ArrayValuable[] values = annotation.value().getEnumConstants(); + if (values.length == 0) { + this.values = Collections.emptyList(); + } else { + this.values = Arrays.asList(values[0].array()); + } + } + + @Override + public boolean isValid(Collection list, ConstraintValidatorContext context) { + if (list == null) { + return true; + } + // 校验通过 + if (CollUtil.containsAll(values, list)) { + return true; + } + // 校验不通过,自定义提示语句 + context.disableDefaultConstraintViolation(); // 禁用默认的 message 的值 + context.buildConstraintViolationWithTemplate(context.getDefaultConstraintMessageTemplate() + .replaceAll("\\{value}", CollUtil.join(list, ","))).addConstraintViolation(); // 重新添加错误提示语句 + return false; + } + +} + diff --git a/tashow-framework/tashow-common/src/main/java/com/tashow/cloud/common/validation/InEnumValidator.java b/tashow-framework/tashow-common/src/main/java/com/tashow/cloud/common/validation/InEnumValidator.java new file mode 100644 index 0000000..878ca94 --- /dev/null +++ b/tashow-framework/tashow-common/src/main/java/com/tashow/cloud/common/validation/InEnumValidator.java @@ -0,0 +1,43 @@ +package com.tashow.cloud.common.validation; + +import com.tashow.cloud.common.core.ArrayValuable; +import jakarta.validation.ConstraintValidator; +import jakarta.validation.ConstraintValidatorContext; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +public class InEnumValidator implements ConstraintValidator { + + private List values; + + @Override + public void initialize(InEnum annotation) { + ArrayValuable[] values = annotation.value().getEnumConstants(); + if (values.length == 0) { + this.values = Collections.emptyList(); + } else { + this.values = Arrays.asList(values[0].array()); + } + } + + @Override + public boolean isValid(Object value, ConstraintValidatorContext context) { + // 为空时,默认不校验,即认为通过 + if (value == null) { + return true; + } + // 校验通过 + if (values.contains(value)) { + return true; + } + // 校验不通过,自定义提示语句 + context.disableDefaultConstraintViolation(); // 禁用默认的 message 的值 + context.buildConstraintViolationWithTemplate(context.getDefaultConstraintMessageTemplate() + .replaceAll("\\{value}", values.toString())).addConstraintViolation(); // 重新添加错误提示语句 + return false; + } + +} + diff --git a/tashow-framework/tashow-common/src/main/java/com/tashow/cloud/common/validation/Mobile.java b/tashow-framework/tashow-common/src/main/java/com/tashow/cloud/common/validation/Mobile.java new file mode 100644 index 0000000..2be7f50 --- /dev/null +++ b/tashow-framework/tashow-common/src/main/java/com/tashow/cloud/common/validation/Mobile.java @@ -0,0 +1,29 @@ +package com.tashow.cloud.common.validation; + +import jakarta.validation.Constraint; +import jakarta.validation.Payload; + +import java.lang.annotation.*; + +@Target({ + ElementType.METHOD, + ElementType.FIELD, + ElementType.ANNOTATION_TYPE, + ElementType.CONSTRUCTOR, + ElementType.PARAMETER, + ElementType.TYPE_USE +}) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@Constraint( + validatedBy = MobileValidator.class +) +public @interface Mobile { + + String message() default "手机号格式不正确"; + + Class[] groups() default {}; + + Class[] payload() default {}; + +} diff --git a/tashow-framework/tashow-common/src/main/java/com/tashow/cloud/common/validation/MobileValidator.java b/tashow-framework/tashow-common/src/main/java/com/tashow/cloud/common/validation/MobileValidator.java new file mode 100644 index 0000000..8eff9fd --- /dev/null +++ b/tashow-framework/tashow-common/src/main/java/com/tashow/cloud/common/validation/MobileValidator.java @@ -0,0 +1,24 @@ +package com.tashow.cloud.common.validation; + +import cn.hutool.core.util.StrUtil; +import com.tashow.cloud.common.util.validation.ValidationUtils; +import jakarta.validation.ConstraintValidator; +import jakarta.validation.ConstraintValidatorContext; + +public class MobileValidator implements ConstraintValidator { + + @Override + public void initialize(Mobile annotation) { + } + + @Override + public boolean isValid(String value, ConstraintValidatorContext context) { + // 如果手机号为空,默认不校验,即校验通过 + if (StrUtil.isEmpty(value)) { + return true; + } + // 校验手机 + return ValidationUtils.isMobile(value); + } + +} diff --git a/tashow-framework/tashow-common/src/main/java/com/tashow/cloud/common/validation/Telephone.java b/tashow-framework/tashow-common/src/main/java/com/tashow/cloud/common/validation/Telephone.java new file mode 100644 index 0000000..9e40d98 --- /dev/null +++ b/tashow-framework/tashow-common/src/main/java/com/tashow/cloud/common/validation/Telephone.java @@ -0,0 +1,29 @@ +package com.tashow.cloud.common.validation; + +import jakarta.validation.Constraint; +import jakarta.validation.Payload; + +import java.lang.annotation.*; + +@Target({ + ElementType.METHOD, + ElementType.FIELD, + ElementType.ANNOTATION_TYPE, + ElementType.CONSTRUCTOR, + ElementType.PARAMETER, + ElementType.TYPE_USE +}) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@Constraint( + validatedBy = TelephoneValidator.class +) +public @interface Telephone { + + String message() default "电话格式不正确"; + + Class[] groups() default {}; + + Class[] payload() default {}; + +} diff --git a/tashow-framework/tashow-common/src/main/java/com/tashow/cloud/common/validation/TelephoneValidator.java b/tashow-framework/tashow-common/src/main/java/com/tashow/cloud/common/validation/TelephoneValidator.java new file mode 100644 index 0000000..ac6a169 --- /dev/null +++ b/tashow-framework/tashow-common/src/main/java/com/tashow/cloud/common/validation/TelephoneValidator.java @@ -0,0 +1,24 @@ +package com.tashow.cloud.common.validation; + +import cn.hutool.core.text.CharSequenceUtil; +import cn.hutool.core.util.PhoneUtil; +import jakarta.validation.ConstraintValidator; +import jakarta.validation.ConstraintValidatorContext; + +public class TelephoneValidator implements ConstraintValidator { + + @Override + public void initialize(Telephone annotation) { + } + + @Override + public boolean isValid(String value, ConstraintValidatorContext context) { + // 如果手机号为空,默认不校验,即校验通过 + if (CharSequenceUtil.isEmpty(value)) { + return true; + } + // 校验手机 + return PhoneUtil.isTel(value) || PhoneUtil.isPhone(value); + } + +} diff --git a/tashow-framework/tashow-common/src/main/java/com/tashow/cloud/common/validation/package-info.java b/tashow-framework/tashow-common/src/main/java/com/tashow/cloud/common/validation/package-info.java new file mode 100644 index 0000000..678ba37 --- /dev/null +++ b/tashow-framework/tashow-common/src/main/java/com/tashow/cloud/common/validation/package-info.java @@ -0,0 +1,4 @@ +/** + * 使用 Hibernate Validator 实现参数校验 + */ +package com.tashow.cloud.common.validation; diff --git a/tashow-framework/tashow-data-canal/pom.xml b/tashow-framework/tashow-data-canal/pom.xml new file mode 100644 index 0000000..f3daf9d --- /dev/null +++ b/tashow-framework/tashow-data-canal/pom.xml @@ -0,0 +1,49 @@ + + + 4.0.0 + + com.tashow.cloud + tashow-framework + ${revision} + + tashow-data-canal + jar + + ${project.artifactId} + canal 封装拓展 + + + + com.alibaba.otter + canal.client + 1.1.0 + + + + + com.baomidou + dynamic-datasource-spring-boot-starter + 4.2.0 + + + + javax.annotation + javax.annotation-api + 1.3.2 + + + + com.tashow.cloud + tashow-common + + + + + org.projectlombok + lombok + true + + + diff --git a/tashow-framework/tashow-data-canal/src/main/java/com/tashow/cloud/canal/config/CanalAutoConfiguration.java b/tashow-framework/tashow-data-canal/src/main/java/com/tashow/cloud/canal/config/CanalAutoConfiguration.java new file mode 100644 index 0000000..038eedd --- /dev/null +++ b/tashow-framework/tashow-data-canal/src/main/java/com/tashow/cloud/canal/config/CanalAutoConfiguration.java @@ -0,0 +1,22 @@ +package com.tashow.cloud.canal.config; + +import com.tashow.cloud.canal.service.CanalSyncService; +import com.tashow.cloud.canal.service.SqlExecutorService; +import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@AutoConfiguration +public class CanalAutoConfiguration { + + @Bean + public CanalSyncService canalSyncService() { + return new CanalSyncService(); + } + + @Bean + public SqlExecutorService getdb() { + return new SqlExecutorService(); + } +} diff --git a/tashow-framework/tashow-data-canal/src/main/java/com/tashow/cloud/canal/service/CanalSyncService.java b/tashow-framework/tashow-data-canal/src/main/java/com/tashow/cloud/canal/service/CanalSyncService.java new file mode 100644 index 0000000..64c6a87 --- /dev/null +++ b/tashow-framework/tashow-data-canal/src/main/java/com/tashow/cloud/canal/service/CanalSyncService.java @@ -0,0 +1,188 @@ +package com.tashow.cloud.canal.service; + +import com.alibaba.otter.canal.client.CanalConnector; +import com.alibaba.otter.canal.client.CanalConnectors; +import com.alibaba.otter.canal.protocol.CanalEntry.*; +import com.alibaba.otter.canal.protocol.Message; +import com.baomidou.dynamic.datasource.annotation.DS; +import com.baomidou.dynamic.datasource.toolkit.DynamicDataSourceContextHolder; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.jdbc.core.BatchPreparedStatementSetter; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.stereotype.Service; + +import javax.annotation.PostConstruct; +import java.net.InetSocketAddress; +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.util.*; +import java.util.concurrent.ConcurrentLinkedQueue; + +@Service +public class CanalSyncService { + + private static final Logger log = LoggerFactory.getLogger(CanalSyncService.class); + + private static final Queue SQL_QUEUE = new ConcurrentLinkedQueue<>(); + + @Autowired + private JdbcTemplate jdbcTemplate; + @Autowired + private SqlExecutorService sqlExecutorService; + + @PostConstruct + public void start() { + new Thread(this::runCanalClient).start(); + } + + private void runCanalClient() { + CanalConnector connector = CanalConnectors.newSingleConnector( + new InetSocketAddress("43.139.42.137", 11111), + "example", + "", + "" + ); + int batchSize = 1000; + + try { + connector.connect(); + connector.subscribe("tashow-platform\\..*"); + connector.rollback(); + + while (true) { + Message message = connector.getWithoutAck(batchSize); + log.info("Received message id: {}, entries size: {}", message.getId(), message.getEntries().size()); + long batchId = message.getId(); + int size = message.getEntries().size(); + + if (batchId == -1 || size == 0) { + Thread.sleep(1000); + continue; + } + + dataHandle(message.getEntries()); + connector.ack(batchId); + + if (!SQL_QUEUE.isEmpty()) { + executeQueueSql(); + } + } + } catch (Exception e) { + log.error("Canal client error occurred.", e); + } finally { + connector.disconnect(); + } + } + + private void dataHandle(List entries) { + for (Entry entry : entries) { + if (entry.getEntryType() != EntryType.ROWDATA) continue; + + try { + RowChange rowChange = RowChange.parseFrom(entry.getStoreValue()); + EventType eventType = rowChange.getEventType(); + String schemaName = entry.getHeader().getSchemaName(); + String tableName = entry.getHeader().getTableName(); + + log.info("schema: {}, table: {}, type: {}", schemaName, tableName, eventType); + + if (eventType == EventType.DELETE) { + saveDeleteSql(entry); + } else if (eventType == EventType.UPDATE) { + saveUpdateSql(entry); + } else if (eventType == EventType.INSERT) { + saveInsertSql(entry); + } + + } catch (Exception e) { + log.error("Error handling entry: {}", entry.toString(), e); + } + } + } + + private void saveInsertSql(Entry entry) throws Exception { + RowChange rowChange = RowChange.parseFrom(entry.getStoreValue()); + String tableName = entry.getHeader().getTableName(); + + for (RowData rowData : rowChange.getRowDatasList()) { + List columns = rowData.getAfterColumnsList(); + + List columnNames = new ArrayList<>(); + List values = new ArrayList<>(); + + for (Column col : columns) { + columnNames.add(col.getName()); + values.add(col.getValue()); + } + + String sql = "INSERT INTO " + tableName + " (" + + String.join(",", columnNames) + ") VALUES ("; + + StringBuilder placeholders = new StringBuilder(); + for (int i = 0; i < values.size(); i++) { + placeholders.append("?,"); + } + if (placeholders.length() > 0) placeholders.deleteCharAt(placeholders.length() - 1); + sql += placeholders + ")"; + + SQL_QUEUE.add(new SqlTask(sql, values.toArray())); + } + } + + private void saveUpdateSql(Entry entry) throws Exception { + RowChange rowChange = RowChange.parseFrom(entry.getStoreValue()); + String tableName = entry.getHeader().getTableName(); + + for (RowData rowData : rowChange.getRowDatasList()) { + List newColumns = rowData.getAfterColumnsList(); + List oldColumns = rowData.getBeforeColumnsList(); + + List updateColumns = new ArrayList<>(); + List params = new ArrayList<>(); + + for (Column col : newColumns) { + updateColumns.add(col.getName() + "=?"); + params.add(col.getValue()); + } + + Optional primaryKeyOpt = oldColumns.stream().filter(Column::getIsKey).findFirst(); + Column primaryKey = primaryKeyOpt.orElseThrow(() -> new RuntimeException("未找到主键")); + + params.add(primaryKey.getValue()); + + String sql = "UPDATE " + tableName + " SET " + + String.join(",", updateColumns) + + " WHERE " + primaryKey.getName() + "=?"; + + SQL_QUEUE.add(new SqlTask(sql, params.toArray())); + } + } + + private void saveDeleteSql(Entry entry) throws Exception { + RowChange rowChange = RowChange.parseFrom(entry.getStoreValue()); + String tableName = entry.getHeader().getTableName(); + + for (RowData rowData : rowChange.getRowDatasList()) { + List beforeColumns = rowData.getBeforeColumnsList(); + + Optional primaryKeyOpt = beforeColumns.stream().filter(Column::getIsKey).findFirst(); + Column primaryKey = primaryKeyOpt.orElseThrow(() -> new RuntimeException("未找到主键")); + + String sql = "DELETE FROM " + tableName + " WHERE " + primaryKey.getName() + "=?"; + SQL_QUEUE.add(new SqlTask(sql, primaryKey.getValue())); + } + } + + private void executeQueueSql() { + List tasks = new ArrayList<>(); + SqlTask task; + while ((task = SQL_QUEUE.poll()) != null) { + tasks.add(task); + } + if (!tasks.isEmpty()) { + sqlExecutorService.executeBatch(tasks); + } + } +} \ No newline at end of file diff --git a/tashow-framework/tashow-data-canal/src/main/java/com/tashow/cloud/canal/service/CanalSyncServiceTest.java b/tashow-framework/tashow-data-canal/src/main/java/com/tashow/cloud/canal/service/CanalSyncServiceTest.java new file mode 100644 index 0000000..9a526da --- /dev/null +++ b/tashow-framework/tashow-data-canal/src/main/java/com/tashow/cloud/canal/service/CanalSyncServiceTest.java @@ -0,0 +1,156 @@ +/* +package com.tashow.cloud.canal.service; + +import com.alibaba.otter.canal.client.CanalConnector; +import com.alibaba.otter.canal.client.CanalConnectors; +import com.alibaba.otter.canal.protocol.CanalEntry.*; +import com.alibaba.otter.canal.protocol.Message; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.stereotype.Service; + +import javax.annotation.PostConstruct; +import java.net.InetSocketAddress; +import java.util.List; +import java.util.Queue; +import java.util.concurrent.ConcurrentLinkedQueue; + +@Service +public class CanalSyncServiceTest { + + private static final Queue SQL_QUEUE = new ConcurrentLinkedQueue<>(); + @Autowired + private JdbcTemplate jdbcTemplate; + @Autowired + private Canaldb canaldb; + + @PostConstruct + public void start() { + new Thread(this::runCanalClient).start(); + } + + private void runCanalClient() { + CanalConnector connector = CanalConnectors.newSingleConnector( + new InetSocketAddress("43.139.42.137", 11111), + "example", + "", + "" + ); + int batchSize = 1000; + + try { + connector.connect(); + connector.subscribe("tashow-platform\\..*"); + connector.rollback(); + + while (true) { + Message message = connector.getWithoutAck(batchSize); + System.out.println("Received message id: " + message.getId() + ", entries size: " + message.getEntries().size()); + long batchId = message.getId(); + int size = message.getEntries().size(); + + if (batchId == -1 || size == 0) { + Thread.sleep(1000); + continue; + } + + dataHandle(message.getEntries()); + connector.ack(batchId); + + if (SQL_QUEUE.size() > 0) { + executeQueueSql(); + } + } + } catch (Exception e) { + e.printStackTrace(); + } finally { + connector.disconnect(); + } + } + + private void dataHandle(List entries) { + for (Entry entry : entries) { + if (entry.getEntryType() != EntryType.ROWDATA) continue; + + try { + RowChange rowChange = RowChange.parseFrom(entry.getStoreValue()); + EventType eventType = rowChange.getEventType(); + String schemaName = entry.getHeader().getSchemaName(); + String tableName = entry.getHeader().getTableName(); + + System.out.println("schema: " + schemaName + ", table: " + tableName + ", type: " + eventType); + + if (eventType == EventType.DELETE) { + saveDeleteSql(entry); + } else if (eventType == EventType.UPDATE) { + saveUpdateSql(entry); + } else if (eventType == EventType.INSERT) { + saveInsertSql(entry); + } + + } catch (Exception e) { + e.printStackTrace(); + } + } + } + + private void saveInsertSql(Entry entry) throws Exception { + RowChange rowChange = RowChange.parseFrom(entry.getStoreValue()); + for (RowData rowData : rowChange.getRowDatasList()) { + List columns = rowData.getAfterColumnsList(); + StringBuilder sql = new StringBuilder("INSERT INTO ") + .append(entry.getHeader().getTableName()).append(" (") + .append(columns.stream().map(Column::getName).reduce((a, b) -> a + "," + b).orElse("")) + .append(") VALUES (") + .append(columns.stream().map(c -> "'" + c.getValue() + "'").reduce((a, b) -> a + "," + b).orElse("")) + .append(");"); + + SQL_QUEUE.add(sql.toString()); + } + } + + private void saveUpdateSql(Entry entry) throws Exception { + RowChange rowChange = RowChange.parseFrom(entry.getStoreValue()); + for (RowData rowData : rowChange.getRowDatasList()) { + List newColumns = rowData.getAfterColumnsList(); + List oldColumns = rowData.getBeforeColumnsList(); + + StringBuilder setClause = new StringBuilder(); + for (Column col : newColumns) { + setClause.append(col.getName()).append("='").append(col.getValue()).append("', "); + } + if (setClause.length() > 0) setClause.setLength(setClause.length() - 2); + + String whereClause = oldColumns.stream() + .filter(Column::getIsKey) + .map(c -> c.getName() + "='" + c.getValue() + "'") + .findFirst() + .orElseThrow(() -> new RuntimeException("未找到主键")); + + SQL_QUEUE.add("UPDATE " + entry.getHeader().getTableName() + " SET " + setClause + " WHERE " + whereClause + ";"); + } + } + + private void saveDeleteSql(Entry entry) throws Exception { + RowChange rowChange = RowChange.parseFrom(entry.getStoreValue()); + for (RowData rowData : rowChange.getRowDatasList()) { + String whereClause = rowData.getBeforeColumnsList().stream() + .filter(Column::getIsKey) + .map(c -> c.getName() + "='" + c.getValue() + "'") + .findFirst() + .orElseThrow(() -> new RuntimeException("未找到主键")); + + SQL_QUEUE.add("DELETE FROM " + entry.getHeader().getTableName() + " WHERE " + whereClause + ";"); + } + } + + + private void executeQueueSql() { + int size = SQL_QUEUE.size(); + for (int i = 0; i < size; i++) { + String sql = SQL_QUEUE.poll(); + canaldb.execute(sql); + } + } +} +*/ diff --git a/tashow-framework/tashow-data-canal/src/main/java/com/tashow/cloud/canal/service/Canaldb.java b/tashow-framework/tashow-data-canal/src/main/java/com/tashow/cloud/canal/service/Canaldb.java new file mode 100644 index 0000000..7b51600 --- /dev/null +++ b/tashow-framework/tashow-data-canal/src/main/java/com/tashow/cloud/canal/service/Canaldb.java @@ -0,0 +1,27 @@ +/* +package com.tashow.cloud.canal.service; + +import com.baomidou.dynamic.datasource.annotation.DS; +import com.baomidou.dynamic.datasource.toolkit.DynamicDataSourceContextHolder; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.stereotype.Service; + +@Service +public class Canaldb { + + @Autowired + private JdbcTemplate jdbcTemplate; + @DS("slave") + public void execute(String sql) { + try { + String ds = DynamicDataSourceContextHolder.peek(); // 调试查看当前数据源 + System.out.println("当前数据源:" + ds); + System.out.println("[execute]----> " + sql); + jdbcTemplate.execute(sql); + } catch (Exception e) { + e.printStackTrace(); + } + } +} +*/ diff --git a/tashow-framework/tashow-data-canal/src/main/java/com/tashow/cloud/canal/service/SqlExecutorService.java b/tashow-framework/tashow-data-canal/src/main/java/com/tashow/cloud/canal/service/SqlExecutorService.java new file mode 100644 index 0000000..3ba0286 --- /dev/null +++ b/tashow-framework/tashow-data-canal/src/main/java/com/tashow/cloud/canal/service/SqlExecutorService.java @@ -0,0 +1,56 @@ +package com.tashow.cloud.canal.service; + + +import com.baomidou.dynamic.datasource.toolkit.DynamicDataSourceContextHolder; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.jdbc.core.BatchPreparedStatementSetter; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.stereotype.Service; + +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.util.List; + +@Service +public class SqlExecutorService { + + private static final Logger log = LoggerFactory.getLogger(SqlExecutorService.class); + + @Autowired + private JdbcTemplate jdbcTemplate; + + public void executeBatch(List tasks) { + if (tasks == null || tasks.isEmpty()) return; + + DynamicDataSourceContextHolder.push("slave"); + try { + // 提取所有 SQL 模板(假设它们都是一样的) + String sqlTemplate = tasks.get(0).getSql(); + + // 执行批量更新 + jdbcTemplate.batchUpdate(sqlTemplate, new BatchPreparedStatementSetter() { + @Override + public void setValues(PreparedStatement ps, int i) throws SQLException { + SqlTask task = tasks.get(i); + Object[] args = task.getArgs(); + for (int j = 0; j < args.length; j++) { + ps.setObject(j + 1, args[j]); + } + } + + @Override + public int getBatchSize() { + return tasks.size(); + } + }); + + log.info("✅ 成功执行 {} 条 SQL", tasks.size()); + } catch (Exception e) { + log.error("❌ 批量执行 SQL 失败", e); + } finally { + DynamicDataSourceContextHolder.poll(); + } + } +} \ No newline at end of file diff --git a/tashow-framework/tashow-data-canal/src/main/java/com/tashow/cloud/canal/service/SqlTask.java b/tashow-framework/tashow-data-canal/src/main/java/com/tashow/cloud/canal/service/SqlTask.java new file mode 100644 index 0000000..c6f62c2 --- /dev/null +++ b/tashow-framework/tashow-data-canal/src/main/java/com/tashow/cloud/canal/service/SqlTask.java @@ -0,0 +1,19 @@ +package com.tashow.cloud.canal.service; + +public class SqlTask { + private final String sql; + private final Object[] args; + + public SqlTask(String sql, Object... args) { + this.sql = sql; + this.args = args; + } + + public String getSql() { + return sql; + } + + public Object[] getArgs() { + return args; + } +} \ No newline at end of file diff --git a/tashow-framework/tashow-data-canal/src/main/java/com/tashow/cloud/canal/service/SqlTaskQueue.java b/tashow-framework/tashow-data-canal/src/main/java/com/tashow/cloud/canal/service/SqlTaskQueue.java new file mode 100644 index 0000000..edf2489 --- /dev/null +++ b/tashow-framework/tashow-data-canal/src/main/java/com/tashow/cloud/canal/service/SqlTaskQueue.java @@ -0,0 +1,32 @@ +package com.tashow.cloud.canal.service; + +import java.util.List; +import java.util.Queue; +import java.util.concurrent.ConcurrentLinkedQueue; + +public class SqlTaskQueue { + private static final Queue queue = new ConcurrentLinkedQueue<>(); + + public static void add(SqlTask task) { + queue.add(task); + } + + public static boolean isEmpty() { + return queue.isEmpty(); + } + + public static SqlTask poll() { + return queue.poll(); + } + + public static int size() { + return queue.size(); + } + + public static List drainAll() { + List list = new java.util.ArrayList<>(); + queue.forEach(list::add); + queue.clear(); + return list; + } +} diff --git a/tashow-framework/tashow-data-canal/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/tashow-framework/tashow-data-canal/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports new file mode 100644 index 0000000..361a8ce --- /dev/null +++ b/tashow-framework/tashow-data-canal/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -0,0 +1 @@ +com.tashow.cloud.canal.config.CanalAutoConfiguration \ No newline at end of file diff --git a/tashow-framework/tashow-data-es/pom.xml b/tashow-framework/tashow-data-es/pom.xml new file mode 100644 index 0000000..5cb06d8 --- /dev/null +++ b/tashow-framework/tashow-data-es/pom.xml @@ -0,0 +1,41 @@ + + + 4.0.0 + + com.tashow.cloud + tashow-framework + ${revision} + + tashow-data-es + jar + + ${project.artifactId} + es 封装拓展 + + + + + org.springframework.boot + spring-boot-starter-data-elasticsearch + + + javax.annotation + javax.annotation-api + 1.3.2 + + + + com.tashow.cloud + tashow-common + + + + + org.projectlombok + lombok + true + + + diff --git a/tashow-framework/tashow-data-es/src/main/java/com/tashow/cloud/es/config/ElasticsearchAutoConfiguration.java b/tashow-framework/tashow-data-es/src/main/java/com/tashow/cloud/es/config/ElasticsearchAutoConfiguration.java new file mode 100644 index 0000000..992e232 --- /dev/null +++ b/tashow-framework/tashow-data-es/src/main/java/com/tashow/cloud/es/config/ElasticsearchAutoConfiguration.java @@ -0,0 +1,61 @@ +package com.tashow.cloud.es.config; + +import co.elastic.clients.elasticsearch.ElasticsearchClient; +import co.elastic.clients.json.jackson.JacksonJsonpMapper; +import co.elastic.clients.transport.ElasticsearchTransport; +import co.elastic.clients.transport.rest_client.RestClientTransport; +import lombok.extern.slf4j.Slf4j; +import org.apache.http.HttpHost; +import org.elasticsearch.client.RestClient; +import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.context.annotation.Bean; + +import javax.annotation.PreDestroy; + +@Slf4j +@AutoConfiguration +public class ElasticsearchAutoConfiguration { + + private RestClient restClient; + + @Bean + public ElasticsearchClient elasticsearchClient(ElasticsearchProperties properties) { + // 1. 构建 HTTP 主机数组 + HttpHost[] hosts = properties.getUris().stream() + .map(uri -> { + if (!uri.startsWith("http")) { + throw new IllegalArgumentException("URI 必须包含协议 (http/https)"); + } + return HttpHost.create(uri); + }) + .toArray(HttpHost[]::new); + + // 2. 创建低级 REST 客户端 (无认证) + this.restClient = RestClient.builder(hosts) + .setRequestConfigCallback(requestConfigBuilder -> requestConfigBuilder + .setConnectTimeout(properties.getConnectTimeout()) + .setSocketTimeout(properties.getSocketTimeout())) + .build(); + + // 3. 创建 Transport 层 + ElasticsearchTransport transport = new RestClientTransport( + restClient, + new JacksonJsonpMapper() // 使用 Jackson 处理 JSON + ); + + log.info("[Elasticsearch] 客户端初始化完成,节点: {}", properties.getUris()); + return new ElasticsearchClient(transport); + } + + @PreDestroy + public void destroy() { + if (restClient != null) { + try { + restClient.close(); + log.info("[Elasticsearch] 客户端已关闭"); + } catch (Exception e) { + log.error("[Elasticsearch] 客户端关闭异常", e); + } + } + } +} \ No newline at end of file diff --git a/tashow-framework/tashow-data-es/src/main/java/com/tashow/cloud/es/config/ElasticsearchConfig.java b/tashow-framework/tashow-data-es/src/main/java/com/tashow/cloud/es/config/ElasticsearchConfig.java new file mode 100644 index 0000000..6e3979f --- /dev/null +++ b/tashow-framework/tashow-data-es/src/main/java/com/tashow/cloud/es/config/ElasticsearchConfig.java @@ -0,0 +1,31 @@ +/* +package com.tashow.cloud.es.config; + +import co.elastic.clients.elasticsearch.ElasticsearchClient; +import co.elastic.clients.json.jackson.JacksonJsonpMapper; +import co.elastic.clients.transport.ElasticsearchTransport; +import co.elastic.clients.transport.rest_client.RestClientTransport; +import org.apache.http.HttpHost; +import org.elasticsearch.client.RestClient; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class ElasticsearchConfig { + + @Bean + public ElasticsearchClient elasticsearchClient() { + // 创建低级客户端 + RestClient restClient = RestClient.builder( + new HttpHost("43.139.42.137", 9200) + ).build(); + + // 使用 Jackson 映射器创建传输层 + ElasticsearchTransport transport = new RestClientTransport( + restClient, new JacksonJsonpMapper() + ); + + // 创建高级客户端 + return new ElasticsearchClient(transport); + } +}*/ diff --git a/tashow-framework/tashow-data-es/src/main/java/com/tashow/cloud/es/config/ElasticsearchConfigTest.java b/tashow-framework/tashow-data-es/src/main/java/com/tashow/cloud/es/config/ElasticsearchConfigTest.java new file mode 100644 index 0000000..debaab0 --- /dev/null +++ b/tashow-framework/tashow-data-es/src/main/java/com/tashow/cloud/es/config/ElasticsearchConfigTest.java @@ -0,0 +1,65 @@ +/* +package com.tashow.cloud.es.config; + +import co.elastic.clients.elasticsearch.ElasticsearchClient; +import co.elastic.clients.json.jackson.JacksonJsonpMapper; +import co.elastic.clients.transport.ElasticsearchTransport; +import co.elastic.clients.transport.rest_client.RestClientTransport; +import lombok.extern.slf4j.Slf4j; +import org.apache.http.HttpHost; +import org.apache.http.auth.AuthScope; +import org.apache.http.auth.UsernamePasswordCredentials; +import org.apache.http.client.CredentialsProvider; +import org.apache.http.impl.client.BasicCredentialsProvider; +import org.elasticsearch.client.RestClient; +import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Bean; + +import java.util.Arrays; + +@Slf4j +@AutoConfiguration +@EnableConfigurationProperties(ElasticsearchProperties.class) +public class ElasticsearchConfigTest { + + @Bean + public ElasticsearchClient elasticsearchClient(ElasticsearchProperties properties) { + // 1. 创建低级 REST 客户端 + RestClient restClient = RestClient.builder(buildHttpHosts(properties)) + .setHttpClientConfigCallback(httpClientBuilder -> { + // 认证配置 + if (properties.getUsername() != null) { + CredentialsProvider credsProvider = new BasicCredentialsProvider(); + credsProvider.setCredentials( + AuthScope.ANY, + new UsernamePasswordCredentials(properties.getUsername(), properties.getPassword()) + ); + httpClientBuilder.setDefaultCredentialsProvider(credsProvider); + } + return httpClientBuilder; + }) + .build(); + + // 2. 创建 Transport 层 + ElasticsearchTransport transport = new RestClientTransport( + restClient, + new JacksonJsonpMapper() // 使用 Jackson 处理 JSON + ); + + // 3. 返回新客户端 + return new ElasticsearchClient(transport); + } + + private HttpHost[] buildHttpHosts(ElasticsearchProperties properties) { + return Arrays.stream(properties.getUris()) + .map(uri -> { + try { + return HttpHost.create(uri); + } catch (Exception e) { + throw new IllegalArgumentException("Invalid Elasticsearch URI: " + uri, e); + } + }) + .toArray(HttpHost[]::new); + } +}*/ diff --git a/tashow-framework/tashow-data-es/src/main/java/com/tashow/cloud/es/config/ElasticsearchProperties.java b/tashow-framework/tashow-data-es/src/main/java/com/tashow/cloud/es/config/ElasticsearchProperties.java new file mode 100644 index 0000000..2a52ff6 --- /dev/null +++ b/tashow-framework/tashow-data-es/src/main/java/com/tashow/cloud/es/config/ElasticsearchProperties.java @@ -0,0 +1,31 @@ +package com.tashow.cloud.es.config; + + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; + +import java.util.List; + +@Data +public class ElasticsearchProperties { + + /** + * 是否启用 Elasticsearch + */ + private Boolean enabled = true; + + /** + * 节点地址列表 (格式: http://ip:port) + */ + private List uris = List.of("http://43.139.42.137:9200"); + + /** + * 连接超时时间 (ms) + */ + private Integer connectTimeout = 3000; + + /** + * 通信超时时间 (ms) + */ + private Integer socketTimeout = 10000; +} diff --git a/tashow-framework/tashow-data-es/src/main/java/com/tashow/cloud/es/service/ElasticsearchService.java b/tashow-framework/tashow-data-es/src/main/java/com/tashow/cloud/es/service/ElasticsearchService.java new file mode 100644 index 0000000..b1488df --- /dev/null +++ b/tashow-framework/tashow-data-es/src/main/java/com/tashow/cloud/es/service/ElasticsearchService.java @@ -0,0 +1,34 @@ +package com.tashow.cloud.es.service; + +import co.elastic.clients.elasticsearch.ElasticsearchClient; +import co.elastic.clients.elasticsearch.core.IndexResponse; +import org.springframework.stereotype.Service; + +import java.io.IOException; + +@Service +public class ElasticsearchService { + + private final ElasticsearchClient client; + + public ElasticsearchService(ElasticsearchClient client) { + this.client = client; + } + + /** + * 向 Elasticsearch 索引中插入数据 + * + * @param indexName 索引名称 + * @param id 文档 ID + * @param jsonData JSON 格式的文档数据 + * @return 插入结果 + * @throws IOException 如果发生 I/O 错误 + */ + public IndexResponse insertDocument(String indexName, String id, String jsonData) throws IOException { + return client.index(i -> i + .index(indexName) + .id(id) + .document(jsonData) + ); + } +} diff --git a/tashow-framework/tashow-data-es/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/tashow-framework/tashow-data-es/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports new file mode 100644 index 0000000..00c3efd --- /dev/null +++ b/tashow-framework/tashow-data-es/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -0,0 +1,3 @@ +com.tashow.cloud.es.config.ElasticsearchAutoConfiguration +com.tashow.cloud.es.service.ElasticsearchService +com.tashow.cloud.es.config.ElasticsearchProperties diff --git a/tashow-framework/tashow-data-excel/pom.xml b/tashow-framework/tashow-data-excel/pom.xml new file mode 100644 index 0000000..262ee18 --- /dev/null +++ b/tashow-framework/tashow-data-excel/pom.xml @@ -0,0 +1,73 @@ + + + + com.tashow.cloud + tashow-framework + ${revision} + + 4.0.0 + tashow-data-excel + jar + + ${project.artifactId} + Excel 拓展 + + + + com.tashow.cloud + tashow-common + + + + + org.springframework.boot + spring-boot-starter + + + + + com.tashow.cloud + tashow-framework-rpc + true + + + + + com.tashow.cloud + tashow-system-api + ${revision} + + + + + org.springframework + spring-web + provided + + + + jakarta.servlet + jakarta.servlet-api + provided + + + + + com.alibaba + easyexcel + + + + com.google.guava + guava + + + + org.apache.commons + commons-compress + + + + diff --git a/tashow-framework/tashow-data-excel/src/main/java/com/tashow/cloud/excel/dict/config/DictAutoConfiguration.java b/tashow-framework/tashow-data-excel/src/main/java/com/tashow/cloud/excel/dict/config/DictAutoConfiguration.java new file mode 100644 index 0000000..7d8b452 --- /dev/null +++ b/tashow-framework/tashow-data-excel/src/main/java/com/tashow/cloud/excel/dict/config/DictAutoConfiguration.java @@ -0,0 +1,18 @@ +package com.tashow.cloud.excel.dict.config; + +import com.tashow.cloud.excel.dict.core.DictFrameworkUtils; +import com.tashow.cloud.systemapi.api.dict.DictDataApi; +import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.context.annotation.Bean; + +@AutoConfiguration +public class DictAutoConfiguration { + + @Bean + @SuppressWarnings("InstantiationOfUtilityClass") + public DictFrameworkUtils dictUtils(DictDataApi dictDataApi) { + DictFrameworkUtils.init(dictDataApi); + return new DictFrameworkUtils(); + } + +} diff --git a/tashow-framework/tashow-data-excel/src/main/java/com/tashow/cloud/excel/dict/config/DictRpcAutoConfiguration.java b/tashow-framework/tashow-data-excel/src/main/java/com/tashow/cloud/excel/dict/config/DictRpcAutoConfiguration.java new file mode 100644 index 0000000..a7ad326 --- /dev/null +++ b/tashow-framework/tashow-data-excel/src/main/java/com/tashow/cloud/excel/dict/config/DictRpcAutoConfiguration.java @@ -0,0 +1,15 @@ +package com.tashow.cloud.excel.dict.config; + +import com.tashow.cloud.systemapi.api.dict.DictDataApi; +import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.cloud.openfeign.EnableFeignClients; + +/** + * 字典用到 Feign 的配置项 + * + * @author 芋道源码 + */ +@AutoConfiguration +@EnableFeignClients(clients = DictDataApi.class) // 主要是引入相关的 API 服务 +public class DictRpcAutoConfiguration { +} diff --git a/tashow-framework/tashow-data-excel/src/main/java/com/tashow/cloud/excel/dict/core/DictFrameworkUtils.java b/tashow-framework/tashow-data-excel/src/main/java/com/tashow/cloud/excel/dict/core/DictFrameworkUtils.java new file mode 100644 index 0000000..92a0e91 --- /dev/null +++ b/tashow-framework/tashow-data-excel/src/main/java/com/tashow/cloud/excel/dict/core/DictFrameworkUtils.java @@ -0,0 +1,96 @@ +package com.tashow.cloud.excel.dict.core; + +import cn.hutool.core.util.ObjectUtil; +import com.google.common.cache.CacheLoader; +import com.google.common.cache.LoadingCache; +import com.tashow.cloud.common.core.KeyValue; +import com.tashow.cloud.common.util.cache.CacheUtils; +import com.tashow.cloud.systemapi.api.dict.DictDataApi; +import com.tashow.cloud.systemapi.api.dict.dto.DictDataRespDTO; +import lombok.SneakyThrows; +import lombok.extern.slf4j.Slf4j; + +import java.time.Duration; +import java.util.List; + +/** + * 字典工具类 + * + * @author 芋道源码 + */ +@Slf4j +public class DictFrameworkUtils { + + private static DictDataApi dictDataApi; + + private static final DictDataRespDTO DICT_DATA_NULL = new DictDataRespDTO(); + + // TODO @puhui999:GET_DICT_DATA_CACHE、GET_DICT_DATA_LIST_CACHE、PARSE_DICT_DATA_CACHE 这 3 个缓存是有点重叠,可以思考下,有没可能减少 1 个。微信讨论好私聊,再具体改哈 + /** + * 针对 {@link #getDictDataLabel(String, String)} 的缓存 + */ + private static final LoadingCache, DictDataRespDTO> GET_DICT_DATA_CACHE = CacheUtils.buildAsyncReloadingCache( + Duration.ofMinutes(1L), // 过期时间 1 分钟 + new CacheLoader, DictDataRespDTO>() { + + @Override + public DictDataRespDTO load(KeyValue key) { + return ObjectUtil.defaultIfNull(dictDataApi.getDictData(key.getKey(), key.getValue()).getCheckedData(), DICT_DATA_NULL); + } + + }); + + /** + * 针对 {@link #getDictDataLabelList(String)} 的缓存 + */ + private static final LoadingCache> GET_DICT_DATA_LIST_CACHE = CacheUtils.buildAsyncReloadingCache( + Duration.ofMinutes(1L), // 过期时间 1 分钟 + new CacheLoader>() { + + @Override + public List load(String dictType) { + return dictDataApi.getDictDataLabelList(dictType); + } + + }); + + /** + * 针对 {@link #parseDictDataValue(String, String)} 的缓存 + */ + private static final LoadingCache, DictDataRespDTO> PARSE_DICT_DATA_CACHE = CacheUtils.buildAsyncReloadingCache( + Duration.ofMinutes(1L), // 过期时间 1 分钟 + new CacheLoader, DictDataRespDTO>() { + + @Override + public DictDataRespDTO load(KeyValue key) { + return ObjectUtil.defaultIfNull(dictDataApi.parseDictData(key.getKey(), key.getValue()).getCheckedData(), DICT_DATA_NULL); + } + + }); + + public static void init(DictDataApi dictDataApi) { + DictFrameworkUtils.dictDataApi = dictDataApi; + log.info("[init][初始化 DictFrameworkUtils 成功]"); + } + + @SneakyThrows + public static String getDictDataLabel(String dictType, Integer value) { + return GET_DICT_DATA_CACHE.get(new KeyValue<>(dictType, String.valueOf(value))).getLabel(); + } + + @SneakyThrows + public static String getDictDataLabel(String dictType, String value) { + return GET_DICT_DATA_CACHE.get(new KeyValue<>(dictType, value)).getLabel(); + } + + @SneakyThrows + public static List getDictDataLabelList(String dictType) { + return GET_DICT_DATA_LIST_CACHE.get(dictType); + } + + @SneakyThrows + public static String parseDictDataValue(String dictType, String label) { + return PARSE_DICT_DATA_CACHE.get(new KeyValue<>(dictType, label)).getValue(); + } + +} diff --git a/tashow-framework/tashow-data-excel/src/main/java/com/tashow/cloud/excel/dict/package-info.java b/tashow-framework/tashow-data-excel/src/main/java/com/tashow/cloud/excel/dict/package-info.java new file mode 100644 index 0000000..1ef27fe --- /dev/null +++ b/tashow-framework/tashow-data-excel/src/main/java/com/tashow/cloud/excel/dict/package-info.java @@ -0,0 +1,6 @@ +/** + * 字典数据模块,提供 {@link com.tashow.cloud.excel.dict.core.DictFrameworkUtils} 工具类 + * + * 通过将字典缓存在内存中,保证性能 + */ +package com.tashow.cloud.excel.dict; diff --git a/tashow-framework/tashow-data-excel/src/main/java/com/tashow/cloud/excel/excel/core/annotations/DictFormat.java b/tashow-framework/tashow-data-excel/src/main/java/com/tashow/cloud/excel/excel/core/annotations/DictFormat.java new file mode 100644 index 0000000..c364f8d --- /dev/null +++ b/tashow-framework/tashow-data-excel/src/main/java/com/tashow/cloud/excel/excel/core/annotations/DictFormat.java @@ -0,0 +1,22 @@ +package com.tashow.cloud.excel.excel.core.annotations; + +import java.lang.annotation.*; + +/** + * 字典格式化 + * + * 实现将字典数据的值,格式化成字典数据的标签 + */ +@Target({ElementType.FIELD}) +@Retention(RetentionPolicy.RUNTIME) +@Inherited +public @interface DictFormat { + + /** + * 例如说,SysDictTypeConstants、InfDictTypeConstants + * + * @return 字典类型 + */ + String value(); + +} diff --git a/tashow-framework/tashow-data-excel/src/main/java/com/tashow/cloud/excel/excel/core/annotations/ExcelColumnSelect.java b/tashow-framework/tashow-data-excel/src/main/java/com/tashow/cloud/excel/excel/core/annotations/ExcelColumnSelect.java new file mode 100644 index 0000000..347c02e --- /dev/null +++ b/tashow-framework/tashow-data-excel/src/main/java/com/tashow/cloud/excel/excel/core/annotations/ExcelColumnSelect.java @@ -0,0 +1,27 @@ +package com.tashow.cloud.excel.excel.core.annotations; + +import java.lang.annotation.*; + +/** + * 给 Excel 列添加下拉选择数据 + * + * 其中 {@link #dictType()} 和 {@link #functionName()} 二选一 + * + * @author HUIHUI + */ +@Target({ElementType.FIELD}) +@Retention(RetentionPolicy.RUNTIME) +@Inherited +public @interface ExcelColumnSelect { + + /** + * @return 字典类型 + */ + String dictType() default ""; + + /** + * @return 获取下拉数据源的方法名称 + */ + String functionName() default ""; + +} diff --git a/tashow-framework/tashow-data-excel/src/main/java/com/tashow/cloud/excel/excel/core/convert/AreaConvert.java b/tashow-framework/tashow-data-excel/src/main/java/com/tashow/cloud/excel/excel/core/convert/AreaConvert.java new file mode 100644 index 0000000..b5aafcb --- /dev/null +++ b/tashow-framework/tashow-data-excel/src/main/java/com/tashow/cloud/excel/excel/core/convert/AreaConvert.java @@ -0,0 +1,46 @@ +package com.tashow.cloud.excel.excel.core.convert; + +import cn.hutool.core.convert.Convert; +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.property.ExcelContentProperty; +import com.tashow.cloud.common.core.Area; +import com.tashow.cloud.common.util.ip.AreaUtils; +import lombok.extern.slf4j.Slf4j; + +/** + * Excel 数据地区转换器 + * + * @author HUIHUI + */ +@Slf4j +public class AreaConvert implements Converter { + + @Override + public Class supportJavaTypeKey() { + throw new UnsupportedOperationException("暂不支持,也不需要"); + } + + @Override + public CellDataTypeEnum supportExcelTypeKey() { + throw new UnsupportedOperationException("暂不支持,也不需要"); + } + + @Override + public Object convertToJavaData(ReadCellData readCellData, ExcelContentProperty contentProperty, + GlobalConfiguration globalConfiguration) { + // 解析地区编号 + String label = readCellData.getStringValue(); + Area area = AreaUtils.parseArea(label); + if (area == null) { + log.error("[convertToJavaData][label({}) 解析不掉]", label); + return null; + } + // 将 value 转换成对应的属性 + Class fieldClazz = contentProperty.getField().getType(); + return Convert.convert(fieldClazz, area.getId()); + } + +} diff --git a/tashow-framework/tashow-data-excel/src/main/java/com/tashow/cloud/excel/excel/core/convert/DictConvert.java b/tashow-framework/tashow-data-excel/src/main/java/com/tashow/cloud/excel/excel/core/convert/DictConvert.java new file mode 100644 index 0000000..5462bf3 --- /dev/null +++ b/tashow-framework/tashow-data-excel/src/main/java/com/tashow/cloud/excel/excel/core/convert/DictConvert.java @@ -0,0 +1,72 @@ +package com.tashow.cloud.excel.excel.core.convert; + +import cn.hutool.core.convert.Convert; +import com.tashow.cloud.excel.dict.core.DictFrameworkUtils; +import com.tashow.cloud.excel.excel.core.annotations.DictFormat; +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; + +/** + * Excel 数据字典转换器 + * + * @author 芋道源码 + */ +@Slf4j +public class DictConvert implements Converter { + + @Override + public Class supportJavaTypeKey() { + throw new UnsupportedOperationException("暂不支持,也不需要"); + } + + @Override + public CellDataTypeEnum supportExcelTypeKey() { + throw new UnsupportedOperationException("暂不支持,也不需要"); + } + + @Override + public Object convertToJavaData(ReadCellData readCellData, ExcelContentProperty contentProperty, + GlobalConfiguration globalConfiguration) { + // 使用字典解析 + String type = getType(contentProperty); + String label = readCellData.getStringValue(); + String value = DictFrameworkUtils.parseDictDataValue(type, label); + if (value == null) { + log.error("[convertToJavaData][type({}) 解析不掉 label({})]", type, label); + return null; + } + // 将 String 的 value 转换成对应的属性 + Class fieldClazz = contentProperty.getField().getType(); + return Convert.convert(fieldClazz, value); + } + + @Override + public WriteCellData convertToExcelData(Object object, ExcelContentProperty contentProperty, + GlobalConfiguration globalConfiguration) { + // 空时,返回空 + if (object == null) { + return new WriteCellData<>(""); + } + + // 使用字典格式化 + String type = getType(contentProperty); + String value = String.valueOf(object); + String label = DictFrameworkUtils.getDictDataLabel(type, value); + if (label == null) { + log.error("[convertToExcelData][type({}) 转换不了 label({})]", type, value); + return new WriteCellData<>(""); + } + // 生成 Excel 小表格 + return new WriteCellData<>(label); + } + + private static String getType(ExcelContentProperty contentProperty) { + return contentProperty.getField().getAnnotation(DictFormat.class).value(); + } + +} diff --git a/tashow-framework/tashow-data-excel/src/main/java/com/tashow/cloud/excel/excel/core/convert/JsonConvert.java b/tashow-framework/tashow-data-excel/src/main/java/com/tashow/cloud/excel/excel/core/convert/JsonConvert.java new file mode 100644 index 0000000..653dfef --- /dev/null +++ b/tashow-framework/tashow-data-excel/src/main/java/com/tashow/cloud/excel/excel/core/convert/JsonConvert.java @@ -0,0 +1,34 @@ +package com.tashow.cloud.excel.excel.core.convert; + +import com.tashow.cloud.common.util.json.JsonUtils; +import com.alibaba.excel.converters.Converter; +import com.alibaba.excel.enums.CellDataTypeEnum; +import com.alibaba.excel.metadata.GlobalConfiguration; +import com.alibaba.excel.metadata.data.WriteCellData; +import com.alibaba.excel.metadata.property.ExcelContentProperty; + +/** + * Excel Json 转换器 + * + * @author 芋道源码 + */ +public class JsonConvert implements Converter { + + @Override + public Class supportJavaTypeKey() { + throw new UnsupportedOperationException("暂不支持,也不需要"); + } + + @Override + public CellDataTypeEnum supportExcelTypeKey() { + throw new UnsupportedOperationException("暂不支持,也不需要"); + } + + @Override + public WriteCellData convertToExcelData(Object value, ExcelContentProperty contentProperty, + GlobalConfiguration globalConfiguration) { + // 生成 Excel 小表格 + return new WriteCellData<>(JsonUtils.toJsonString(value)); + } + +} diff --git a/tashow-framework/tashow-data-excel/src/main/java/com/tashow/cloud/excel/excel/core/convert/MoneyConvert.java b/tashow-framework/tashow-data-excel/src/main/java/com/tashow/cloud/excel/excel/core/convert/MoneyConvert.java new file mode 100644 index 0000000..6c8ab82 --- /dev/null +++ b/tashow-framework/tashow-data-excel/src/main/java/com/tashow/cloud/excel/excel/core/convert/MoneyConvert.java @@ -0,0 +1,39 @@ +package com.tashow.cloud.excel.excel.core.convert; + +import com.alibaba.excel.converters.Converter; +import com.alibaba.excel.enums.CellDataTypeEnum; +import com.alibaba.excel.metadata.GlobalConfiguration; +import com.alibaba.excel.metadata.data.WriteCellData; +import com.alibaba.excel.metadata.property.ExcelContentProperty; + +import java.math.BigDecimal; +import java.math.RoundingMode; + +/** + * 金额转换器 + * + * 金额单位:分 + * + * @author 芋道源码 + */ +public class MoneyConvert implements Converter { + + @Override + public Class supportJavaTypeKey() { + throw new UnsupportedOperationException("暂不支持,也不需要"); + } + + @Override + public CellDataTypeEnum supportExcelTypeKey() { + throw new UnsupportedOperationException("暂不支持,也不需要"); + } + + @Override + public WriteCellData convertToExcelData(Integer value, ExcelContentProperty contentProperty, + GlobalConfiguration globalConfiguration) { + BigDecimal result = BigDecimal.valueOf(value) + .divide(new BigDecimal(100), 2, RoundingMode.HALF_UP); + return new WriteCellData<>(result.toString()); + } + +} diff --git a/tashow-framework/tashow-data-excel/src/main/java/com/tashow/cloud/excel/excel/core/function/ExcelColumnSelectFunction.java b/tashow-framework/tashow-data-excel/src/main/java/com/tashow/cloud/excel/excel/core/function/ExcelColumnSelectFunction.java new file mode 100644 index 0000000..07f4908 --- /dev/null +++ b/tashow-framework/tashow-data-excel/src/main/java/com/tashow/cloud/excel/excel/core/function/ExcelColumnSelectFunction.java @@ -0,0 +1,28 @@ +package com.tashow.cloud.excel.excel.core.function; + +import java.util.List; + +/** + * Excel 列下拉数据源获取接口 + * + * 为什么不直接解析字典还搞个接口?考虑到有的下拉数据不是从字典中获取的所有需要做一个兼容 + + * @author HUIHUI + */ +public interface ExcelColumnSelectFunction { + + /** + * 获得方法名称 + * + * @return 方法名称 + */ + String getName(); + + /** + * 获得列下拉数据源 + * + * @return 下拉数据源 + */ + List getOptions(); + +} diff --git a/tashow-framework/tashow-data-excel/src/main/java/com/tashow/cloud/excel/excel/core/handler/SelectSheetWriteHandler.java b/tashow-framework/tashow-data-excel/src/main/java/com/tashow/cloud/excel/excel/core/handler/SelectSheetWriteHandler.java new file mode 100644 index 0000000..057a262 --- /dev/null +++ b/tashow-framework/tashow-data-excel/src/main/java/com/tashow/cloud/excel/excel/core/handler/SelectSheetWriteHandler.java @@ -0,0 +1,158 @@ +package com.tashow.cloud.excel.excel.core.handler; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.lang.Assert; +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.extra.spring.SpringUtil; +import cn.hutool.poi.excel.ExcelUtil; +import com.tashow.cloud.common.core.KeyValue; +import com.tashow.cloud.excel.dict.core.DictFrameworkUtils; +import com.tashow.cloud.excel.excel.core.annotations.ExcelColumnSelect; +import com.tashow.cloud.excel.excel.core.function.ExcelColumnSelectFunction; +import com.alibaba.excel.annotation.ExcelProperty; +import com.alibaba.excel.write.handler.SheetWriteHandler; +import com.alibaba.excel.write.metadata.holder.WriteSheetHolder; +import com.alibaba.excel.write.metadata.holder.WriteWorkbookHolder; +import lombok.extern.slf4j.Slf4j; +import org.apache.poi.hssf.usermodel.HSSFDataValidation; +import org.apache.poi.ss.usermodel.*; +import org.apache.poi.ss.util.CellRangeAddressList; + +import java.lang.reflect.Field; +import java.util.Comparator; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import static com.tashow.cloud.common.util.collection.CollectionUtils.convertList; + +/** + * 基于固定 sheet 实现下拉框 + * + * @author HUIHUI + */ +@Slf4j +public class SelectSheetWriteHandler implements SheetWriteHandler { + + /** + * 数据起始行从 0 开始 + * + * 约定:本项目第一行有标题所以从 1 开始如果您的 Excel 有多行标题请自行更改 + */ + public static final int FIRST_ROW = 1; + /** + * 下拉列需要创建下拉框的行数,默认两千行如需更多请自行调整 + */ + public static final int LAST_ROW = 2000; + + private static final String DICT_SHEET_NAME = "字典sheet"; + + /** + * key: 列 value: 下拉数据源 + */ + private final Map> selectMap = new HashMap<>(); + + public SelectSheetWriteHandler(Class head) { + // 解析下拉数据 + int colIndex = 0; + for (Field field : head.getDeclaredFields()) { + if (field.isAnnotationPresent(ExcelColumnSelect.class)) { + ExcelProperty excelProperty = field.getAnnotation(ExcelProperty.class); + if (excelProperty != null && excelProperty.index() != -1) { + colIndex = excelProperty.index(); + } + getSelectDataList(colIndex, field); + } + colIndex++; + } + } + + /** + * 获得下拉数据,并添加到 {@link #selectMap} 中 + * + * @param colIndex 列索引 + * @param field 字段 + */ + private void getSelectDataList(int colIndex, Field field) { + ExcelColumnSelect columnSelect = field.getAnnotation(ExcelColumnSelect.class); + String dictType = columnSelect.dictType(); + String functionName = columnSelect.functionName(); + Assert.isTrue(ObjectUtil.isNotEmpty(dictType) || ObjectUtil.isNotEmpty(functionName), + "Field({}) 的 @ExcelColumnSelect 注解,dictType 和 functionName 不能同时为空", field.getName()); + + // 情况一:使用 dictType 获得下拉数据 + if (StrUtil.isNotEmpty(dictType)) { // 情况一: 字典数据 (默认) + selectMap.put(colIndex, DictFrameworkUtils.getDictDataLabelList(dictType)); + return; + } + + // 情况二:使用 functionName 获得下拉数据 + Map functionMap = SpringUtil.getApplicationContext().getBeansOfType(ExcelColumnSelectFunction.class); + ExcelColumnSelectFunction function = CollUtil.findOne(functionMap.values(), item -> item.getName().equals(functionName)); + Assert.notNull(function, "未找到对应的 function({})", functionName); + selectMap.put(colIndex, function.getOptions()); + } + + @Override + public void afterSheetCreate(WriteWorkbookHolder writeWorkbookHolder, WriteSheetHolder writeSheetHolder) { + if (CollUtil.isEmpty(selectMap)) { + return; + } + + // 1. 获取相应操作对象 + DataValidationHelper helper = writeSheetHolder.getSheet().getDataValidationHelper(); // 需要设置下拉框的 sheet 页的数据验证助手 + Workbook workbook = writeWorkbookHolder.getWorkbook(); // 获得工作簿 + List>> keyValues = convertList(selectMap.entrySet(), entry -> new KeyValue<>(entry.getKey(), entry.getValue())); + keyValues.sort(Comparator.comparing(item -> item.getValue().size())); // 升序不然创建下拉会报错 + + // 2. 创建数据字典的 sheet 页 + Sheet dictSheet = workbook.createSheet(DICT_SHEET_NAME); + for (KeyValue> keyValue : keyValues) { + int rowLength = keyValue.getValue().size(); + // 2.1 设置字典 sheet 页的值,每一列一个字典项 + for (int i = 0; i < rowLength; i++) { + Row row = dictSheet.getRow(i); + if (row == null) { + row = dictSheet.createRow(i); + } + row.createCell(keyValue.getKey()).setCellValue(keyValue.getValue().get(i)); + } + // 2.2 设置单元格下拉选择 + setColumnSelect(writeSheetHolder, workbook, helper, keyValue); + } + } + + /** + * 设置单元格下拉选择 + */ + private static void setColumnSelect(WriteSheetHolder writeSheetHolder, Workbook workbook, DataValidationHelper helper, + KeyValue> keyValue) { + // 1.1 创建可被其他单元格引用的名称 + Name name = workbook.createName(); + String excelColumn = ExcelUtil.indexToColName(keyValue.getKey()); + // 1.2 下拉框数据来源 eg:字典sheet!$B1:$B2 + String refers = DICT_SHEET_NAME + "!$" + excelColumn + "$1:$" + excelColumn + "$" + keyValue.getValue().size(); + name.setNameName("dict" + keyValue.getKey()); // 设置名称的名字 + name.setRefersToFormula(refers); // 设置公式 + + // 2.1 设置约束 + DataValidationConstraint constraint = helper.createFormulaListConstraint("dict" + keyValue.getKey()); // 设置引用约束 + // 设置下拉单元格的首行、末行、首列、末列 + CellRangeAddressList rangeAddressList = new CellRangeAddressList(FIRST_ROW, LAST_ROW, + keyValue.getKey(), keyValue.getKey()); + DataValidation validation = helper.createValidation(constraint, rangeAddressList); + if (validation instanceof HSSFDataValidation) { + validation.setSuppressDropDownArrow(false); + } else { + validation.setSuppressDropDownArrow(true); + validation.setShowErrorBox(true); + } + // 2.2 阻止输入非下拉框的值 + validation.setErrorStyle(DataValidation.ErrorStyle.STOP); + validation.createErrorBox("提示", "此值不存在于下拉选择中!"); + // 2.3 添加下拉框约束 + writeSheetHolder.getSheet().addValidationData(validation); + } + +} diff --git a/tashow-framework/tashow-data-excel/src/main/java/com/tashow/cloud/excel/excel/core/util/ExcelUtils.java b/tashow-framework/tashow-data-excel/src/main/java/com/tashow/cloud/excel/excel/core/util/ExcelUtils.java new file mode 100644 index 0000000..c344b3a --- /dev/null +++ b/tashow-framework/tashow-data-excel/src/main/java/com/tashow/cloud/excel/excel/core/util/ExcelUtils.java @@ -0,0 +1,53 @@ +package com.tashow.cloud.excel.excel.core.util; + +import com.tashow.cloud.excel.excel.core.handler.SelectSheetWriteHandler; +import com.alibaba.excel.EasyExcel; +import com.alibaba.excel.converters.longconverter.LongStringConverter; +import com.alibaba.excel.write.style.column.LongestMatchColumnWidthStyleStrategy; +import jakarta.servlet.http.HttpServletResponse; +import org.springframework.web.multipart.MultipartFile; + +import java.io.IOException; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.util.List; + +/** + * Excel 工具类 + * + * @author 芋道源码 + */ +public class ExcelUtils { + + /** + * 将列表以 Excel 响应给前端 + * + * @param response 响应 + * @param filename 文件名 + * @param sheetName Excel sheet 名 + * @param head Excel head 头 + * @param data 数据列表哦 + * @param 泛型,保证 head 和 data 类型的一致性 + * @throws IOException 写入失败的情况 + */ + public static void write(HttpServletResponse response, String filename, String sheetName, + Class head, List data) throws IOException { + // 输出 Excel + EasyExcel.write(response.getOutputStream(), head) + .autoCloseStream(false) // 不要自动关闭,交给 Servlet 自己处理 + .registerWriteHandler(new LongestMatchColumnWidthStyleStrategy()) // 基于 column 长度,自动适配。最大 255 宽度 + .registerWriteHandler(new SelectSheetWriteHandler(head)) // 基于固定 sheet 实现下拉框 + .registerConverter(new LongStringConverter()) // 避免 Long 类型丢失精度 + .sheet(sheetName).doWrite(data); + // 设置 header 和 contentType。写在最后的原因是,避免报错时,响应 contentType 已经被修改了 + response.addHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(filename, StandardCharsets.UTF_8.name())); + response.setContentType("application/vnd.ms-excel;charset=UTF-8"); + } + + public static List read(MultipartFile file, Class head) throws IOException { + return EasyExcel.read(file.getInputStream(), head, null) + .autoCloseStream(false) // 不要自动关闭,交给 Servlet 自己处理 + .doReadAllSync(); + } + +} diff --git a/tashow-framework/tashow-data-excel/src/main/java/com/tashow/cloud/excel/excel/package-info.java b/tashow-framework/tashow-data-excel/src/main/java/com/tashow/cloud/excel/excel/package-info.java new file mode 100644 index 0000000..ba99a4c --- /dev/null +++ b/tashow-framework/tashow-data-excel/src/main/java/com/tashow/cloud/excel/excel/package-info.java @@ -0,0 +1,4 @@ +/** + * 基于 EasyExcel 实现 Excel 相关的操作 + */ +package com.tashow.cloud.excel.excel; diff --git a/tashow-framework/tashow-data-excel/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/tashow-framework/tashow-data-excel/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports new file mode 100644 index 0000000..b5e7b16 --- /dev/null +++ b/tashow-framework/tashow-data-excel/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -0,0 +1,2 @@ +com.tashow.cloud.excel.dict.config.DictRpcAutoConfiguration +com.tashow.cloud.excel.dict.config.DictAutoConfiguration diff --git a/tashow-framework/tashow-data-mybatis/pom.xml b/tashow-framework/tashow-data-mybatis/pom.xml new file mode 100644 index 0000000..f45fe8a --- /dev/null +++ b/tashow-framework/tashow-data-mybatis/pom.xml @@ -0,0 +1,109 @@ + + + + com.tashow.cloud + tashow-framework + ${revision} + + 4.0.0 + tashow-data-mybatis + jar + + ${project.artifactId} + 数据库连接池、多数据源、事务、MyBatis 拓展 + + + + com.tashow.cloud + tashow-common + + + + + com.tashow.cloud + tashow-framework-web + provided + + + + + com.mysql + mysql-connector-j + + + com.oracle.database.jdbc + ojdbc8 + true + + + + org.postgresql + postgresql + true + + + com.microsoft.sqlserver + mssql-jdbc + true + + + com.dameng + DmJdbcDriver18 + true + + + cn.com.kingbase + kingbase8 + true + + + org.opengauss + opengauss-jdbc + true + + + + com.alibaba + druid-spring-boot-3-starter + + + com.baomidou + mybatis-plus-spring-boot3-starter + + + com.baomidou + mybatis-plus-jsqlparser + + + com.baomidou + dynamic-datasource-spring-boot3-starter + + + org.springframework.boot + spring-boot-starter-undertow + + + + + + com.github.yulichang + mybatis-plus-join-boot-starter + + + + com.fhs-opensource + easy-trans-spring-boot-starter + + + com.fhs-opensource + easy-trans-mybatis-plus-extend + + + + diff --git a/tashow-framework/tashow-data-mybatis/src/main/java/com/tashow/cloud/mybatis/datasource/config/DataSourceAutoConfiguration.java b/tashow-framework/tashow-data-mybatis/src/main/java/com/tashow/cloud/mybatis/datasource/config/DataSourceAutoConfiguration.java new file mode 100644 index 0000000..07b2b8a --- /dev/null +++ b/tashow-framework/tashow-data-mybatis/src/main/java/com/tashow/cloud/mybatis/datasource/config/DataSourceAutoConfiguration.java @@ -0,0 +1,40 @@ +package com.tashow.cloud.mybatis.datasource.config; + +import com.alibaba.druid.spring.boot3.autoconfigure.properties.DruidStatProperties; +import com.tashow.cloud.mybatis.datasource.core.filter.DruidAdRemoveFilter; +import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.boot.web.servlet.FilterRegistrationBean; +import org.springframework.context.annotation.Bean; +import org.springframework.transaction.annotation.EnableTransactionManagement; + +/** + * 数据库配置类 + * + * @author 芋道源码 + */ +@AutoConfiguration +@EnableTransactionManagement(proxyTargetClass = true) // 启动事务管理 +@EnableConfigurationProperties(DruidStatProperties.class) +public class DataSourceAutoConfiguration { + + /** + * 创建 DruidAdRemoveFilter 过滤器,过滤 common.js 的广告 + */ + @Bean + @ConditionalOnProperty(name = "spring.datasource.druid.stat-view-servlet.enabled", havingValue = "true") + public FilterRegistrationBean druidAdRemoveFilterFilter(DruidStatProperties properties) { + // 获取 druid web 监控页面的参数 + DruidStatProperties.StatViewServlet config = properties.getStatViewServlet(); + // 提取 common.js 的配置路径 + String pattern = config.getUrlPattern() != null ? config.getUrlPattern() : "/druid/*"; + String commonJsPattern = pattern.replaceAll("\\*", "js/common.js"); + // 创建 DruidAdRemoveFilter Bean + FilterRegistrationBean registrationBean = new FilterRegistrationBean<>(); + registrationBean.setFilter(new DruidAdRemoveFilter()); + registrationBean.addUrlPatterns(commonJsPattern); + return registrationBean; + } + +} diff --git a/tashow-framework/tashow-data-mybatis/src/main/java/com/tashow/cloud/mybatis/datasource/core/enums/DataSourceEnum.java b/tashow-framework/tashow-data-mybatis/src/main/java/com/tashow/cloud/mybatis/datasource/core/enums/DataSourceEnum.java new file mode 100644 index 0000000..662d24e --- /dev/null +++ b/tashow-framework/tashow-data-mybatis/src/main/java/com/tashow/cloud/mybatis/datasource/core/enums/DataSourceEnum.java @@ -0,0 +1,22 @@ +package com.tashow.cloud.mybatis.datasource.core.enums; + +/** + * 对应于多数据源中不同数据源配置 + * + * 通过在方法上,使用 {@link com.baomidou.dynamic.datasource.annotation.DS} 注解,设置使用的数据源。 + * 注意,默认是 {@link #MASTER} 数据源 + * + * 对应官方文档为 http://dynamic-datasource.com/guide/customize/Annotation.html + */ +public interface DataSourceEnum { + + /** + * 主库,推荐使用 {@link com.baomidou.dynamic.datasource.annotation.Master} 注解 + */ + String MASTER = "master"; + /** + * 从库,推荐使用 {@link com.baomidou.dynamic.datasource.annotation.Slave} 注解 + */ + String SLAVE = "slave"; + +} diff --git a/tashow-framework/tashow-data-mybatis/src/main/java/com/tashow/cloud/mybatis/datasource/core/filter/DruidAdRemoveFilter.java b/tashow-framework/tashow-data-mybatis/src/main/java/com/tashow/cloud/mybatis/datasource/core/filter/DruidAdRemoveFilter.java new file mode 100644 index 0000000..5d4369c --- /dev/null +++ b/tashow-framework/tashow-data-mybatis/src/main/java/com/tashow/cloud/mybatis/datasource/core/filter/DruidAdRemoveFilter.java @@ -0,0 +1,40 @@ +package com.tashow.cloud.mybatis.datasource.core.filter; + + + +import com.alibaba.druid.util.Utils; +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.springframework.web.filter.OncePerRequestFilter; + +import java.io.IOException; + +/** + * Druid 底部广告过滤器 + * + * @author 芋道源码 + */ +public class DruidAdRemoveFilter extends OncePerRequestFilter { + + /** + * common.js 的路径 + */ + private static final String COMMON_JS_ILE_PATH = "support/http/resources/js/common.js"; + + @Override + protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) + throws ServletException, IOException { + chain.doFilter(request, response); + // 重置缓冲区,响应头不会被重置 + response.resetBuffer(); + // 获取 common.js + String text = Utils.readFromResource(COMMON_JS_ILE_PATH); + // 正则替换 banner, 除去底部的广告信息 + text = text.replaceAll("
", ""); + text = text.replaceAll("powered.*?shrek.wang", ""); + response.getWriter().write(text); + } + +} diff --git a/tashow-framework/tashow-data-mybatis/src/main/java/com/tashow/cloud/mybatis/datasource/package-info.java b/tashow-framework/tashow-data-mybatis/src/main/java/com/tashow/cloud/mybatis/datasource/package-info.java new file mode 100644 index 0000000..58d9abc --- /dev/null +++ b/tashow-framework/tashow-data-mybatis/src/main/java/com/tashow/cloud/mybatis/datasource/package-info.java @@ -0,0 +1,5 @@ +/** + * 数据库连接池,采用 Druid + * 多数据源,采用爆米花 + */ +package com.tashow.cloud.mybatis.datasource; diff --git a/tashow-framework/tashow-data-mybatis/src/main/java/com/tashow/cloud/mybatis/mybatis/config/BaseMybatisAutoConfiguration.java b/tashow-framework/tashow-data-mybatis/src/main/java/com/tashow/cloud/mybatis/mybatis/config/BaseMybatisAutoConfiguration.java new file mode 100644 index 0000000..7600b7d --- /dev/null +++ b/tashow-framework/tashow-data-mybatis/src/main/java/com/tashow/cloud/mybatis/mybatis/config/BaseMybatisAutoConfiguration.java @@ -0,0 +1,76 @@ +package com.tashow.cloud.mybatis.mybatis.config; + +import cn.hutool.core.util.StrUtil; +import com.baomidou.mybatisplus.annotation.DbType; +import com.baomidou.mybatisplus.autoconfigure.MybatisPlusAutoConfiguration; +import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler; +import com.baomidou.mybatisplus.core.incrementer.IKeyGenerator; +import com.baomidou.mybatisplus.extension.incrementer.*; +import com.baomidou.mybatisplus.extension.parser.JsqlParserGlobal; +import com.baomidou.mybatisplus.extension.parser.cache.JdkSerialCaffeineJsqlParseCache; +import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor; +import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor; +import com.tashow.cloud.mybatis.mybatis.core.handler.DefaultDBFieldHandler; +import org.apache.ibatis.annotations.Mapper; +import org.mybatis.spring.annotation.MapperScan; +import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.context.annotation.Bean; +import org.springframework.core.env.ConfigurableEnvironment; + +import java.util.concurrent.TimeUnit; + +/** + * MyBaits 配置类 + * + * @author 芋道源码 + */ +@AutoConfiguration(before = MybatisPlusAutoConfiguration.class) // 目的:先于 MyBatis Plus 自动配置,避免 @MapperScan 可能扫描不到 Mapper 打印 warn 日志 +@MapperScan(value = "${tashow.info.base-package}", annotationClass = Mapper.class, + lazyInitialization = "${mybatis.lazy-initialization:false}") // Mapper 懒加载,目前仅用于单元测试 +public class BaseMybatisAutoConfiguration { + + static { + // 动态 SQL 智能优化支持本地缓存加速解析,更完善的租户复杂 XML 动态 SQL 支持,静态注入缓存 + JsqlParserGlobal.setJsqlParseCache(new JdkSerialCaffeineJsqlParseCache( + (cache) -> cache.maximumSize(1024) + .expireAfterWrite(5, TimeUnit.SECONDS)) + ); + } + + @Bean + public MybatisPlusInterceptor mybatisPlusInterceptor() { + MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor(); + mybatisPlusInterceptor.addInnerInterceptor(new PaginationInnerInterceptor()); // 分页插件 + return mybatisPlusInterceptor; + } + + @Bean + public MetaObjectHandler defaultMetaObjectHandler() { + return new DefaultDBFieldHandler(); // 自动填充参数类 + } + + @Bean + @ConditionalOnProperty(prefix = "mybatis-plus.global-config.db-config", name = "id-type", havingValue = "INPUT") + public IKeyGenerator keyGenerator(ConfigurableEnvironment environment) { + DbType dbType = IdTypeEnvironmentPostProcessor.getDbType(environment); + if (dbType != null) { + switch (dbType) { + case POSTGRE_SQL: + return new PostgreKeyGenerator(); + case ORACLE: + case ORACLE_12C: + return new OracleKeyGenerator(); + case H2: + return new H2KeyGenerator(); + case KINGBASE_ES: + return new KingbaseKeyGenerator(); + case DM: + return new DmKeyGenerator(); + } + } + // 找不到合适的 IKeyGenerator 实现类 + throw new IllegalArgumentException(StrUtil.format("DbType{} 找不到合适的 IKeyGenerator 实现类", dbType)); + } + +} diff --git a/tashow-framework/tashow-data-mybatis/src/main/java/com/tashow/cloud/mybatis/mybatis/config/IdTypeEnvironmentPostProcessor.java b/tashow-framework/tashow-data-mybatis/src/main/java/com/tashow/cloud/mybatis/mybatis/config/IdTypeEnvironmentPostProcessor.java new file mode 100644 index 0000000..9b80f81 --- /dev/null +++ b/tashow-framework/tashow-data-mybatis/src/main/java/com/tashow/cloud/mybatis/mybatis/config/IdTypeEnvironmentPostProcessor.java @@ -0,0 +1,108 @@ +package com.tashow.cloud.mybatis.mybatis.config; + +import cn.hutool.core.util.StrUtil; +import com.baomidou.mybatisplus.annotation.DbType; +import com.baomidou.mybatisplus.annotation.IdType; +import com.tashow.cloud.common.util.collection.SetUtils; +import com.tashow.cloud.mybatis.mybatis.core.util.JdbcUtils; +import lombok.extern.slf4j.Slf4j; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.env.EnvironmentPostProcessor; +import org.springframework.core.env.ConfigurableEnvironment; + +import java.util.Set; + +/** + * 当 IdType 为 {@link IdType#NONE} 时,根据 PRIMARY 数据源所使用的数据库,自动设置 + * + * @author 芋道源码 + */ +@Slf4j +public class IdTypeEnvironmentPostProcessor implements EnvironmentPostProcessor { + + private static final String ID_TYPE_KEY = "mybatis-plus.global-config.db-config.id-type"; + + private static final String DATASOURCE_DYNAMIC_KEY = "spring.datasource.dynamic"; + + private static final String QUARTZ_JOB_STORE_DRIVER_KEY = "spring.quartz.properties.org.quartz.jobStore.driverDelegateClass"; + + private static final Set INPUT_ID_TYPES = SetUtils.asSet(DbType.ORACLE, DbType.ORACLE_12C, + DbType.POSTGRE_SQL, DbType.KINGBASE_ES, DbType.DB2, DbType.H2); + + @Override + public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) { + // 如果获取不到 DbType,则不进行处理 + DbType dbType = getDbType(environment); + if (dbType == null) { + return; + } + + // 设置 Quartz JobStore 对应的 Driver + // TODO 芋艿:暂时没有找到特别合适的地方,先放在这里 + setJobStoreDriverIfPresent(environment, dbType); + + // 如果非 NONE,则不进行处理 + IdType idType = getIdType(environment); + if (idType != IdType.NONE) { + return; + } + // 情况一,用户输入 ID,适合 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库 + if (INPUT_ID_TYPES.contains(dbType)) { + setIdType(environment, IdType.INPUT); + return; + } + // 情况二,自增 ID,适合 MySQL、DM 达梦等直接自增的数据库 + setIdType(environment, IdType.AUTO); + } + + public IdType getIdType(ConfigurableEnvironment environment) { + return environment.getProperty(ID_TYPE_KEY, IdType.class); + } + + public void setIdType(ConfigurableEnvironment environment, IdType idType) { + environment.getSystemProperties().put(ID_TYPE_KEY, idType); + log.info("[setIdType][修改 MyBatis Plus 的 idType 为({})]", idType); + } + + public void setJobStoreDriverIfPresent(ConfigurableEnvironment environment, DbType dbType) { + String driverClass = environment.getProperty(QUARTZ_JOB_STORE_DRIVER_KEY); + if (StrUtil.isNotEmpty(driverClass)) { + return; + } + // 根据 dbType 类型,获取对应的 driverClass + switch (dbType) { + case POSTGRE_SQL: + driverClass = "org.quartz.impl.jdbcjobstore.PostgreSQLDelegate"; + break; + case ORACLE: + case ORACLE_12C: + driverClass = "org.quartz.impl.jdbcjobstore.oracle.OracleDelegate"; + break; + case SQL_SERVER: + case SQL_SERVER2005: + driverClass = "org.quartz.impl.jdbcjobstore.MSSQLDelegate"; + break; + case DM: + case KINGBASE_ES: + driverClass = "org.quartz.impl.jdbcjobstore.StdJDBCDelegate"; + break; + } + // 设置 driverClass 变量 + if (StrUtil.isNotEmpty(driverClass)) { + environment.getSystemProperties().put(QUARTZ_JOB_STORE_DRIVER_KEY, driverClass); + } + } + + public static DbType getDbType(ConfigurableEnvironment environment) { + String primary = environment.getProperty(DATASOURCE_DYNAMIC_KEY + "." + "primary"); + if (StrUtil.isEmpty(primary)) { + return null; + } + String url = environment.getProperty(DATASOURCE_DYNAMIC_KEY + ".datasource." + primary + ".url"); + if (StrUtil.isEmpty(url)) { + return null; + } + return JdbcUtils.getDbType(url); + } + +} diff --git a/tashow-framework/tashow-data-mybatis/src/main/java/com/tashow/cloud/mybatis/mybatis/core/dataobject/BaseDO.java b/tashow-framework/tashow-data-mybatis/src/main/java/com/tashow/cloud/mybatis/mybatis/core/dataobject/BaseDO.java new file mode 100644 index 0000000..92fa0e9 --- /dev/null +++ b/tashow-framework/tashow-data-mybatis/src/main/java/com/tashow/cloud/mybatis/mybatis/core/dataobject/BaseDO.java @@ -0,0 +1,56 @@ +package com.tashow.cloud.mybatis.mybatis.core.dataobject; + +import com.baomidou.mybatisplus.annotation.FieldFill; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableLogic; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fhs.core.trans.vo.TransPojo; +import lombok.Data; +import org.apache.ibatis.type.JdbcType; + +import java.io.Serializable; +import java.time.LocalDateTime; + +/** + * 基础实体对象 + * + * 为什么实现 {@link TransPojo} 接口? + * 因为使用 Easy-Trans TransType.SIMPLE 模式,集成 MyBatis Plus 查询 + * + * @author 芋道源码 + */ +@Data +@JsonIgnoreProperties(value = "transMap") // 由于 Easy-Trans 会添加 transMap 属性,避免 Jackson 在 Spring Cache 反序列化报错 +public abstract class BaseDO implements Serializable, TransPojo { + + /** + * 创建时间 + */ + @TableField(fill = FieldFill.INSERT) + private LocalDateTime createTime; + /** + * 最后更新时间 + */ + @TableField(fill = FieldFill.INSERT_UPDATE) + private LocalDateTime updateTime; + /** + * 创建者,目前使用 SysUser 的 id 编号 + * + * 使用 String 类型的原因是,未来可能会存在非数值的情况,留好拓展性。 + */ + @TableField(fill = FieldFill.INSERT, jdbcType = JdbcType.VARCHAR) + private String creator; + /** + * 更新者,目前使用 SysUser 的 id 编号 + * + * 使用 String 类型的原因是,未来可能会存在非数值的情况,留好拓展性。 + */ + @TableField(fill = FieldFill.INSERT_UPDATE, jdbcType = JdbcType.VARCHAR) + private String updater; + /** + * 是否删除 + */ + @TableLogic + private Integer deleted; + +} diff --git a/tashow-framework/tashow-data-mybatis/src/main/java/com/tashow/cloud/mybatis/mybatis/core/enums/DbTypeEnum.java b/tashow-framework/tashow-data-mybatis/src/main/java/com/tashow/cloud/mybatis/mybatis/core/enums/DbTypeEnum.java new file mode 100644 index 0000000..d2839b6 --- /dev/null +++ b/tashow-framework/tashow-data-mybatis/src/main/java/com/tashow/cloud/mybatis/mybatis/core/enums/DbTypeEnum.java @@ -0,0 +1,95 @@ +package com.tashow.cloud.mybatis.mybatis.core.enums; + +import cn.hutool.core.util.StrUtil; +import com.baomidou.mybatisplus.annotation.DbType; +import lombok.AllArgsConstructor; +import lombok.Getter; + +import java.util.Arrays; +import java.util.Map; +import java.util.Optional; +import java.util.function.Function; +import java.util.stream.Collectors; + +/** + * 针对 MyBatis Plus 的 {@link DbType} 增强,补充更多信息 + */ +@Getter +@AllArgsConstructor +public enum DbTypeEnum { + + /** + * H2 + * + * 注意:H2 不支持 find_in_set 函数 + */ + H2(DbType.H2, "H2", ""), + + /** + * MySQL + */ + MY_SQL(DbType.MYSQL, "MySQL", "FIND_IN_SET('#{value}', #{column}) <> 0"), + + /** + * Oracle + */ + ORACLE(DbType.ORACLE, "Oracle", "FIND_IN_SET('#{value}', #{column}) <> 0"), + + /** + * PostgreSQL + * + * 华为 openGauss 使用 ProductName 与 PostgreSQL 相同 + */ + POSTGRE_SQL(DbType.POSTGRE_SQL,"PostgreSQL", "POSITION('#{value}' IN #{column}) <> 0"), + + /** + * SQL Server + */ + SQL_SERVER(DbType.SQL_SERVER, "Microsoft SQL Server", "CHARINDEX(',' + #{value} + ',', ',' + #{column} + ',') <> 0"), + /** + * SQL Server 2005 + */ + SQL_SERVER2005(DbType.SQL_SERVER2005, "Microsoft SQL Server 2005", "CHARINDEX(',' + #{value} + ',', ',' + #{column} + ',') <> 0"), + + /** + * 达梦 + */ + DM(DbType.DM, "DM DBMS", "FIND_IN_SET('#{value}', #{column}) <> 0"), + + /** + * 人大金仓 + */ + KINGBASE_ES(DbType.KINGBASE_ES, "KingbaseES", "POSITION('#{value}' IN #{column}) <> 0"), + ; + + public static final Map MAP_BY_NAME = Arrays.stream(values()) + .collect(Collectors.toMap(DbTypeEnum::getProductName, Function.identity())); + + public static final Map MAP_BY_MP = Arrays.stream(values()) + .collect(Collectors.toMap(DbTypeEnum::getMpDbType, Function.identity())); + + /** + * MyBatis Plus 类型 + */ + private final DbType mpDbType; + /** + * 数据库产品名 + */ + private final String productName; + /** + * SQL FIND_IN_SET 模板 + */ + private final String findInSetTemplate; + + public static DbType find(String databaseProductName) { + if (StrUtil.isBlank(databaseProductName)) { + return null; + } + return MAP_BY_NAME.get(databaseProductName).getMpDbType(); + } + + public static String getFindInSetTemplate(DbType dbType) { + return Optional.of(MAP_BY_MP.get(dbType).getFindInSetTemplate()) + .orElseThrow(() -> new IllegalArgumentException("FIND_IN_SET not supported")); + } +} diff --git a/tashow-framework/tashow-data-mybatis/src/main/java/com/tashow/cloud/mybatis/mybatis/core/handler/DefaultDBFieldHandler.java b/tashow-framework/tashow-data-mybatis/src/main/java/com/tashow/cloud/mybatis/mybatis/core/handler/DefaultDBFieldHandler.java new file mode 100644 index 0000000..ea2a027 --- /dev/null +++ b/tashow-framework/tashow-data-mybatis/src/main/java/com/tashow/cloud/mybatis/mybatis/core/handler/DefaultDBFieldHandler.java @@ -0,0 +1,62 @@ +package com.tashow.cloud.mybatis.mybatis.core.handler; + +import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler; +import com.tashow.cloud.mybatis.mybatis.core.dataobject.BaseDO; +import com.tashow.cloud.web.web.core.util.WebFrameworkUtils; +import org.apache.ibatis.reflection.MetaObject; + +import java.time.LocalDateTime; +import java.util.Objects; + +/** + * 通用参数填充实现类 + * + * 如果没有显式的对通用参数进行赋值,这里会对通用参数进行填充、赋值 + * + * @author hexiaowu + */ +public class DefaultDBFieldHandler implements MetaObjectHandler { + + @Override + public void insertFill(MetaObject metaObject) { + if (Objects.nonNull(metaObject) && metaObject.getOriginalObject() instanceof BaseDO) { + BaseDO baseDO = (BaseDO) metaObject.getOriginalObject(); + + LocalDateTime current = LocalDateTime.now(); + // 创建时间为空,则以当前时间为插入时间 + if (Objects.isNull(baseDO.getCreateTime())) { + baseDO.setCreateTime(current); + } + // 更新时间为空,则以当前时间为更新时间 + if (Objects.isNull(baseDO.getUpdateTime())) { + baseDO.setUpdateTime(current); + } + + Long userId = WebFrameworkUtils.getLoginUserId(); + // 当前登录用户不为空,创建人为空,则当前登录用户为创建人 + if (Objects.nonNull(userId) && Objects.isNull(baseDO.getCreator())) { + baseDO.setCreator(userId.toString()); + } + // 当前登录用户不为空,更新人为空,则当前登录用户为更新人 + if (Objects.nonNull(userId) && Objects.isNull(baseDO.getUpdater())) { + baseDO.setUpdater(userId.toString()); + } + } + } + + @Override + public void updateFill(MetaObject metaObject) { + // 更新时间为空,则以当前时间为更新时间 + Object modifyTime = getFieldValByName("updateTime", metaObject); + if (Objects.isNull(modifyTime)) { + setFieldValByName("updateTime", LocalDateTime.now(), metaObject); + } + + // 当前登录用户不为空,更新人为空,则当前登录用户为更新人 + Object modifier = getFieldValByName("updater", metaObject); + Long userId = WebFrameworkUtils.getLoginUserId(); + if (Objects.nonNull(userId) && Objects.isNull(modifier)) { + setFieldValByName("updater", userId.toString(), metaObject); + } + } +} diff --git a/tashow-framework/tashow-data-mybatis/src/main/java/com/tashow/cloud/mybatis/mybatis/core/mapper/BaseMapperX.java b/tashow-framework/tashow-data-mybatis/src/main/java/com/tashow/cloud/mybatis/mybatis/core/mapper/BaseMapperX.java new file mode 100644 index 0000000..01bb70d --- /dev/null +++ b/tashow-framework/tashow-data-mybatis/src/main/java/com/tashow/cloud/mybatis/mybatis/core/mapper/BaseMapperX.java @@ -0,0 +1,192 @@ +package com.tashow.cloud.mybatis.mybatis.core.mapper; + +import cn.hutool.core.collection.CollUtil; +import com.baomidou.mybatisplus.annotation.DbType; +import com.baomidou.mybatisplus.core.conditions.Wrapper; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.core.toolkit.support.SFunction; +import com.baomidou.mybatisplus.extension.toolkit.Db; +import com.github.yulichang.base.MPJBaseMapper; +import com.github.yulichang.interfaces.MPJBaseJoin; +import com.github.yulichang.wrapper.MPJLambdaWrapper; +import com.tashow.cloud.common.pojo.PageParam; +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.common.pojo.SortablePageParam; +import com.tashow.cloud.common.pojo.SortingField; +import com.tashow.cloud.mybatis.mybatis.core.util.JdbcUtils; +import com.tashow.cloud.mybatis.mybatis.core.util.MyBatisUtils; +import org.apache.ibatis.annotations.Param; + +import java.util.Collection; +import java.util.List; + +/** + * 在 MyBatis Plus 的 BaseMapper 的基础上拓展,提供更多的能力 + * + * 1. {@link BaseMapper} 为 MyBatis Plus 的基础接口,提供基础的 CRUD 能力 + * 2. {@link MPJBaseMapper} 为 MyBatis Plus Join 的基础接口,提供连表 Join 能力 + */ +public interface BaseMapperX extends MPJBaseMapper { + + default PageResult selectPage(SortablePageParam pageParam, @Param("ew") Wrapper queryWrapper) { + return selectPage(pageParam, pageParam.getSortingFields(), queryWrapper); + } + + default PageResult selectPage(PageParam pageParam, @Param("ew") Wrapper queryWrapper) { + return selectPage(pageParam, null, queryWrapper); + } + + default PageResult selectPage(PageParam pageParam, Collection sortingFields, @Param("ew") Wrapper queryWrapper) { + // 特殊:不分页,直接查询全部 + if (PageParam.PAGE_SIZE_NONE.equals(pageParam.getPageSize())) { + List list = selectList(queryWrapper); + return new PageResult<>(list, (long) list.size()); + } + + // MyBatis Plus 查询 + IPage mpPage = MyBatisUtils.buildPage(pageParam, sortingFields); + selectPage(mpPage, queryWrapper); + // 转换返回 + return new PageResult<>(mpPage.getRecords(), mpPage.getTotal()); + } + + default PageResult selectJoinPage(PageParam pageParam, Class clazz, MPJLambdaWrapper lambdaWrapper) { + // 特殊:不分页,直接查询全部 + if (PageParam.PAGE_SIZE_NONE.equals(pageParam.getPageSize())) { + List list = selectJoinList(clazz, lambdaWrapper); + return new PageResult<>(list, (long) list.size()); + } + + // MyBatis Plus Join 查询 + IPage mpPage = MyBatisUtils.buildPage(pageParam); + mpPage = selectJoinPage(mpPage, clazz, lambdaWrapper); + // 转换返回 + return new PageResult<>(mpPage.getRecords(), mpPage.getTotal()); + } + + default PageResult selectJoinPage(PageParam pageParam, Class resultTypeClass, MPJBaseJoin joinQueryWrapper) { + IPage mpPage = MyBatisUtils.buildPage(pageParam); + selectJoinPage(mpPage, resultTypeClass, joinQueryWrapper); + // 转换返回 + return new PageResult<>(mpPage.getRecords(), mpPage.getTotal()); + } + + default T selectOne(String field, Object value) { + return selectOne(new QueryWrapper().eq(field, value)); + } + + default T selectOne(SFunction field, Object value) { + return selectOne(new LambdaQueryWrapper().eq(field, value)); + } + + default T selectOne(String field1, Object value1, String field2, Object value2) { + return selectOne(new QueryWrapper().eq(field1, value1).eq(field2, value2)); + } + + default T selectOne(SFunction field1, Object value1, SFunction field2, Object value2) { + return selectOne(new LambdaQueryWrapper().eq(field1, value1).eq(field2, value2)); + } + + default T selectOne(SFunction field1, Object value1, SFunction field2, Object value2, + SFunction field3, Object value3) { + return selectOne(new LambdaQueryWrapper().eq(field1, value1).eq(field2, value2) + .eq(field3, value3)); + } + + default Long selectCount() { + return selectCount(new QueryWrapper<>()); + } + + default Long selectCount(String field, Object value) { + return selectCount(new QueryWrapper().eq(field, value)); + } + + default Long selectCount(SFunction field, Object value) { + return selectCount(new LambdaQueryWrapper().eq(field, value)); + } + + default List selectList() { + return selectList(new QueryWrapper<>()); + } + + default List selectList(String field, Object value) { + return selectList(new QueryWrapper().eq(field, value)); + } + + default List selectList(SFunction field, Object value) { + return selectList(new LambdaQueryWrapper().eq(field, value)); + } + + default List selectList(String field, Collection values) { + if (CollUtil.isEmpty(values)) { + return CollUtil.newArrayList(); + } + return selectList(new QueryWrapper().in(field, values)); + } + + default List selectList(SFunction field, Collection values) { + if (CollUtil.isEmpty(values)) { + return CollUtil.newArrayList(); + } + return selectList(new LambdaQueryWrapper().in(field, values)); + } + + default List selectList(SFunction field1, Object value1, SFunction field2, Object value2) { + return selectList(new LambdaQueryWrapper().eq(field1, value1).eq(field2, value2)); + } + + /** + * 批量插入,适合大量数据插入 + * + * @param entities 实体们 + */ + default Boolean insertBatch(Collection entities) { + // 特殊:SQL Server 批量插入后,获取 id 会报错,因此通过循环处理 + DbType dbType = JdbcUtils.getDbType(); + if (JdbcUtils.isSQLServer(dbType)) { + entities.forEach(this::insert); + return CollUtil.isNotEmpty(entities); + } + return Db.saveBatch(entities); + } + + /** + * 批量插入,适合大量数据插入 + * + * @param entities 实体们 + * @param size 插入数量 Db.saveBatch 默认为 1000 + */ + default Boolean insertBatch(Collection entities, int size) { + // 特殊:SQL Server 批量插入后,获取 id 会报错,因此通过循环处理 + DbType dbType = JdbcUtils.getDbType(); + if (JdbcUtils.isSQLServer(dbType)) { + entities.forEach(this::insert); + return CollUtil.isNotEmpty(entities); + } + return Db.saveBatch(entities, size); + } + + default int updateBatch(T update) { + return update(update, new QueryWrapper<>()); + } + + default Boolean updateBatch(Collection entities) { + return Db.updateBatchById(entities); + } + + default Boolean updateBatch(Collection entities, int size) { + return Db.updateBatchById(entities, size); + } + + default int delete(String field, String value) { + return delete(new QueryWrapper().eq(field, value)); + } + + default int delete(SFunction field, Object value) { + return delete(new LambdaQueryWrapper().eq(field, value)); + } + +} diff --git a/tashow-framework/tashow-data-mybatis/src/main/java/com/tashow/cloud/mybatis/mybatis/core/query/LambdaQueryWrapperX.java b/tashow-framework/tashow-data-mybatis/src/main/java/com/tashow/cloud/mybatis/mybatis/core/query/LambdaQueryWrapperX.java new file mode 100644 index 0000000..ac60b99 --- /dev/null +++ b/tashow-framework/tashow-data-mybatis/src/main/java/com/tashow/cloud/mybatis/mybatis/core/query/LambdaQueryWrapperX.java @@ -0,0 +1,135 @@ +package com.tashow.cloud.mybatis.mybatis.core.query; + +import cn.hutool.core.util.ArrayUtil; +import cn.hutool.core.util.ObjectUtil; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.toolkit.support.SFunction; +import com.tashow.cloud.common.util.collection.ArrayUtils; +import org.springframework.util.StringUtils; + +import java.util.Collection; + +/** + * 拓展 MyBatis Plus QueryWrapper 类,主要增加如下功能: + *

+ * 1. 拼接条件的方法,增加 xxxIfPresent 方法,用于判断值不存在的时候,不要拼接到条件中。 + * + * @param 数据类型 + */ +public class LambdaQueryWrapperX extends LambdaQueryWrapper { + + public LambdaQueryWrapperX likeIfPresent(SFunction column, String val) { + if (StringUtils.hasText(val)) { + return (LambdaQueryWrapperX) super.like(column, val); + } + return this; + } + + public LambdaQueryWrapperX inIfPresent(SFunction column, Collection values) { + if (ObjectUtil.isAllNotEmpty(values) && !ArrayUtil.isEmpty(values)) { + return (LambdaQueryWrapperX) super.in(column, values); + } + return this; + } + + public LambdaQueryWrapperX inIfPresent(SFunction column, Object... values) { + if (ObjectUtil.isAllNotEmpty(values) && !ArrayUtil.isEmpty(values)) { + return (LambdaQueryWrapperX) super.in(column, values); + } + return this; + } + + public LambdaQueryWrapperX eqIfPresent(SFunction column, Object val) { + if (ObjectUtil.isNotEmpty(val)) { + return (LambdaQueryWrapperX) super.eq(column, val); + } + return this; + } + + public LambdaQueryWrapperX neIfPresent(SFunction column, Object val) { + if (ObjectUtil.isNotEmpty(val)) { + return (LambdaQueryWrapperX) super.ne(column, val); + } + return this; + } + + public LambdaQueryWrapperX gtIfPresent(SFunction column, Object val) { + if (val != null) { + return (LambdaQueryWrapperX) super.gt(column, val); + } + return this; + } + + public LambdaQueryWrapperX geIfPresent(SFunction column, Object val) { + if (val != null) { + return (LambdaQueryWrapperX) super.ge(column, val); + } + return this; + } + + public LambdaQueryWrapperX ltIfPresent(SFunction column, Object val) { + if (val != null) { + return (LambdaQueryWrapperX) super.lt(column, val); + } + return this; + } + + public LambdaQueryWrapperX leIfPresent(SFunction column, Object val) { + if (val != null) { + return (LambdaQueryWrapperX) super.le(column, val); + } + return this; + } + + public LambdaQueryWrapperX betweenIfPresent(SFunction column, Object val1, Object val2) { + if (val1 != null && val2 != null) { + return (LambdaQueryWrapperX) super.between(column, val1, val2); + } + if (val1 != null) { + return (LambdaQueryWrapperX) ge(column, val1); + } + if (val2 != null) { + return (LambdaQueryWrapperX) le(column, val2); + } + return this; + } + + public LambdaQueryWrapperX betweenIfPresent(SFunction column, Object[] values) { + Object val1 = ArrayUtils.get(values, 0); + Object val2 = ArrayUtils.get(values, 1); + return betweenIfPresent(column, val1, val2); + } + + // ========== 重写父类方法,方便链式调用 ========== + + @Override + public LambdaQueryWrapperX eq(boolean condition, SFunction column, Object val) { + super.eq(condition, column, val); + return this; + } + + @Override + public LambdaQueryWrapperX eq(SFunction column, Object val) { + super.eq(column, val); + return this; + } + + @Override + public LambdaQueryWrapperX orderByDesc(SFunction column) { + super.orderByDesc(true, column); + return this; + } + + @Override + public LambdaQueryWrapperX last(String lastSql) { + super.last(lastSql); + return this; + } + + @Override + public LambdaQueryWrapperX in(SFunction column, Collection coll) { + super.in(column, coll); + return this; + } + +} diff --git a/tashow-framework/tashow-data-mybatis/src/main/java/com/tashow/cloud/mybatis/mybatis/core/query/MPJLambdaWrapperX.java b/tashow-framework/tashow-data-mybatis/src/main/java/com/tashow/cloud/mybatis/mybatis/core/query/MPJLambdaWrapperX.java new file mode 100644 index 0000000..1863e1b --- /dev/null +++ b/tashow-framework/tashow-data-mybatis/src/main/java/com/tashow/cloud/mybatis/mybatis/core/query/MPJLambdaWrapperX.java @@ -0,0 +1,313 @@ +package com.tashow.cloud.mybatis.mybatis.core.query; + +import cn.hutool.core.util.ArrayUtil; +import cn.hutool.core.util.ObjectUtil; +import com.baomidou.mybatisplus.core.toolkit.support.SFunction; +import com.github.yulichang.toolkit.MPJWrappers; +import com.github.yulichang.wrapper.MPJLambdaWrapper; +import com.tashow.cloud.common.util.collection.ArrayUtils; +import org.springframework.util.StringUtils; + +import java.util.Collection; +import java.util.function.Consumer; + +/** + * 拓展 MyBatis Plus Join QueryWrapper 类,主要增加如下功能: + *

+ * 1. 拼接条件的方法,增加 xxxIfPresent 方法,用于判断值不存在的时候,不要拼接到条件中。 + * + * @param 数据类型 + */ +public class MPJLambdaWrapperX extends MPJLambdaWrapper { + + public MPJLambdaWrapperX likeIfPresent(SFunction column, String val) { + MPJWrappers.lambdaJoin().like(column, val); + if (StringUtils.hasText(val)) { + return (MPJLambdaWrapperX) super.like(column, val); + } + return this; + } + + public MPJLambdaWrapperX inIfPresent(SFunction column, Collection values) { + if (ObjectUtil.isAllNotEmpty(values) && !ArrayUtil.isEmpty(values)) { + return (MPJLambdaWrapperX) super.in(column, values); + } + return this; + } + + public MPJLambdaWrapperX inIfPresent(SFunction column, Object... values) { + if (ObjectUtil.isAllNotEmpty(values) && !ArrayUtil.isEmpty(values)) { + return (MPJLambdaWrapperX) super.in(column, values); + } + return this; + } + + public MPJLambdaWrapperX eqIfPresent(SFunction column, Object val) { + if (ObjectUtil.isNotEmpty(val)) { + return (MPJLambdaWrapperX) super.eq(column, val); + } + return this; + } + + public MPJLambdaWrapperX neIfPresent(SFunction column, Object val) { + if (ObjectUtil.isNotEmpty(val)) { + return (MPJLambdaWrapperX) super.ne(column, val); + } + return this; + } + + public MPJLambdaWrapperX gtIfPresent(SFunction column, Object val) { + if (val != null) { + return (MPJLambdaWrapperX) super.gt(column, val); + } + return this; + } + + public MPJLambdaWrapperX geIfPresent(SFunction column, Object val) { + if (val != null) { + return (MPJLambdaWrapperX) super.ge(column, val); + } + return this; + } + + public MPJLambdaWrapperX ltIfPresent(SFunction column, Object val) { + if (val != null) { + return (MPJLambdaWrapperX) super.lt(column, val); + } + return this; + } + + public MPJLambdaWrapperX leIfPresent(SFunction column, Object val) { + if (val != null) { + return (MPJLambdaWrapperX) super.le(column, val); + } + return this; + } + + public MPJLambdaWrapperX betweenIfPresent(SFunction column, Object val1, Object val2) { + if (val1 != null && val2 != null) { + return (MPJLambdaWrapperX) super.between(column, val1, val2); + } + if (val1 != null) { + return (MPJLambdaWrapperX) ge(column, val1); + } + if (val2 != null) { + return (MPJLambdaWrapperX) le(column, val2); + } + return this; + } + + public MPJLambdaWrapperX betweenIfPresent(SFunction column, Object[] values) { + Object val1 = ArrayUtils.get(values, 0); + Object val2 = ArrayUtils.get(values, 1); + return betweenIfPresent(column, val1, val2); + } + + // ========== 重写父类方法,方便链式调用 ========== + + @Override + public MPJLambdaWrapperX eq(boolean condition, SFunction column, Object val) { + super.eq(condition, column, val); + return this; + } + + @Override + public MPJLambdaWrapperX eq(SFunction column, Object val) { + super.eq(column, val); + return this; + } + + @Override + public MPJLambdaWrapperX orderByDesc(SFunction column) { + //noinspection unchecked + super.orderByDesc(true, column); + return this; + } + + @Override + public MPJLambdaWrapperX last(String lastSql) { + super.last(lastSql); + return this; + } + + @Override + public MPJLambdaWrapperX in(SFunction column, Collection coll) { + super.in(column, coll); + return this; + } + + @Override + public MPJLambdaWrapperX selectAll(Class clazz) { + super.selectAll(clazz); + return this; + } + + @Override + public MPJLambdaWrapperX selectAll(Class clazz, String prefix) { + super.selectAll(clazz, prefix); + return this; + } + + @Override + public MPJLambdaWrapperX selectAs(SFunction column, String alias) { + super.selectAs(column, alias); + return this; + } + + @Override + public MPJLambdaWrapperX selectAs(String column, SFunction alias) { + super.selectAs(column, alias); + return this; + } + + @Override + public MPJLambdaWrapperX selectAs(SFunction column, SFunction alias) { + super.selectAs(column, alias); + return this; + } + + @Override + public MPJLambdaWrapperX selectAs(String index, SFunction column, SFunction alias) { + super.selectAs(index, column, alias); + return this; + } + + @Override + public MPJLambdaWrapperX selectAsClass(Class source, Class tag) { + super.selectAsClass(source, tag); + return this; + } + + @Override + public MPJLambdaWrapperX selectSub(Class clazz, Consumer> consumer, SFunction alias) { + super.selectSub(clazz, consumer, alias); + return this; + } + + @Override + public MPJLambdaWrapperX selectSub(Class clazz, String st, Consumer> consumer, SFunction alias) { + super.selectSub(clazz, st, consumer, alias); + return this; + } + + @Override + public MPJLambdaWrapperX selectCount(SFunction column) { + super.selectCount(column); + return this; + } + + @Override + public MPJLambdaWrapperX selectCount(Object column, String alias) { + super.selectCount(column, alias); + return this; + } + + @Override + public MPJLambdaWrapperX selectCount(Object column, SFunction alias) { + super.selectCount(column, alias); + return this; + } + + @Override + public MPJLambdaWrapperX selectCount(SFunction column, String alias) { + super.selectCount(column, alias); + return this; + } + + @Override + public MPJLambdaWrapperX selectCount(SFunction column, SFunction alias) { + super.selectCount(column, alias); + return this; + } + + @Override + public MPJLambdaWrapperX selectSum(SFunction column) { + super.selectSum(column); + return this; + } + + @Override + public MPJLambdaWrapperX selectSum(SFunction column, String alias) { + super.selectSum(column, alias); + return this; + } + + @Override + public MPJLambdaWrapperX selectSum(SFunction column, SFunction alias) { + super.selectSum(column, alias); + return this; + } + + @Override + public MPJLambdaWrapperX selectMax(SFunction column) { + super.selectMax(column); + return this; + } + + @Override + public MPJLambdaWrapperX selectMax(SFunction column, String alias) { + super.selectMax(column, alias); + return this; + } + + @Override + public MPJLambdaWrapperX selectMax(SFunction column, SFunction alias) { + super.selectMax(column, alias); + return this; + } + + @Override + public MPJLambdaWrapperX selectMin(SFunction column) { + super.selectMin(column); + return this; + } + + @Override + public MPJLambdaWrapperX selectMin(SFunction column, String alias) { + super.selectMin(column, alias); + return this; + } + + @Override + public MPJLambdaWrapperX selectMin(SFunction column, SFunction alias) { + super.selectMin(column, alias); + return this; + } + + @Override + public MPJLambdaWrapperX selectAvg(SFunction column) { + super.selectAvg(column); + return this; + } + + @Override + public MPJLambdaWrapperX selectAvg(SFunction column, String alias) { + super.selectAvg(column, alias); + return this; + } + + @Override + public MPJLambdaWrapperX selectAvg(SFunction column, SFunction alias) { + super.selectAvg(column, alias); + return this; + } + + @Override + public MPJLambdaWrapperX selectLen(SFunction column) { + super.selectLen(column); + return this; + } + + @Override + public MPJLambdaWrapperX selectLen(SFunction column, String alias) { + super.selectLen(column, alias); + return this; + } + + @Override + public MPJLambdaWrapperX selectLen(SFunction column, SFunction alias) { + super.selectLen(column, alias); + return this; + } + +} diff --git a/tashow-framework/tashow-data-mybatis/src/main/java/com/tashow/cloud/mybatis/mybatis/core/query/QueryWrapperX.java b/tashow-framework/tashow-data-mybatis/src/main/java/com/tashow/cloud/mybatis/mybatis/core/query/QueryWrapperX.java new file mode 100644 index 0000000..aec8880 --- /dev/null +++ b/tashow-framework/tashow-data-mybatis/src/main/java/com/tashow/cloud/mybatis/mybatis/core/query/QueryWrapperX.java @@ -0,0 +1,166 @@ +package com.tashow.cloud.mybatis.mybatis.core.query; + +import com.baomidou.mybatisplus.annotation.DbType; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.core.toolkit.ArrayUtils; +import com.baomidou.mybatisplus.core.toolkit.CollectionUtils; +import com.tashow.cloud.mybatis.mybatis.core.util.JdbcUtils; +import org.springframework.util.StringUtils; + +import java.util.Collection; + +/** + * 拓展 MyBatis Plus QueryWrapper 类,主要增加如下功能: + * + * 1. 拼接条件的方法,增加 xxxIfPresent 方法,用于判断值不存在的时候,不要拼接到条件中。 + * + * @param 数据类型 + */ +public class QueryWrapperX extends QueryWrapper { + + public QueryWrapperX likeIfPresent(String column, String val) { + if (StringUtils.hasText(val)) { + return (QueryWrapperX) super.like(column, val); + } + return this; + } + + public QueryWrapperX inIfPresent(String column, Collection values) { + if (!CollectionUtils.isEmpty(values)) { + return (QueryWrapperX) super.in(column, values); + } + return this; + } + + public QueryWrapperX inIfPresent(String column, Object... values) { + if (!ArrayUtils.isEmpty(values)) { + return (QueryWrapperX) super.in(column, values); + } + return this; + } + + public QueryWrapperX eqIfPresent(String column, Object val) { + if (val != null) { + return (QueryWrapperX) super.eq(column, val); + } + return this; + } + + public QueryWrapperX neIfPresent(String column, Object val) { + if (val != null) { + return (QueryWrapperX) super.ne(column, val); + } + return this; + } + + public QueryWrapperX gtIfPresent(String column, Object val) { + if (val != null) { + return (QueryWrapperX) super.gt(column, val); + } + return this; + } + + public QueryWrapperX geIfPresent(String column, Object val) { + if (val != null) { + return (QueryWrapperX) super.ge(column, val); + } + return this; + } + + public QueryWrapperX ltIfPresent(String column, Object val) { + if (val != null) { + return (QueryWrapperX) super.lt(column, val); + } + return this; + } + + public QueryWrapperX leIfPresent(String column, Object val) { + if (val != null) { + return (QueryWrapperX) super.le(column, val); + } + return this; + } + + public QueryWrapperX betweenIfPresent(String column, Object val1, Object val2) { + if (val1 != null && val2 != null) { + return (QueryWrapperX) super.between(column, val1, val2); + } + if (val1 != null) { + return (QueryWrapperX) ge(column, val1); + } + if (val2 != null) { + return (QueryWrapperX) le(column, val2); + } + return this; + } + + public QueryWrapperX betweenIfPresent(String column, Object[] values) { + if (values!= null && values.length != 0 && values[0] != null && values[1] != null) { + return (QueryWrapperX) super.between(column, values[0], values[1]); + } + if (values!= null && values.length != 0 && values[0] != null) { + return (QueryWrapperX) ge(column, values[0]); + } + if (values!= null && values.length != 0 && values[1] != null) { + return (QueryWrapperX) le(column, values[1]); + } + return this; + } + + // ========== 重写父类方法,方便链式调用 ========== + + @Override + public QueryWrapperX eq(boolean condition, String column, Object val) { + super.eq(condition, column, val); + return this; + } + + @Override + public QueryWrapperX eq(String column, Object val) { + super.eq(column, val); + return this; + } + + @Override + public QueryWrapperX orderByDesc(String column) { + super.orderByDesc(true, column); + return this; + } + + @Override + public QueryWrapperX last(String lastSql) { + super.last(lastSql); + return this; + } + + @Override + public QueryWrapperX in(String column, Collection coll) { + super.in(column, coll); + return this; + } + + /** + * 设置只返回最后一条 + * + * TODO 芋艿:不是完美解,需要在思考下。如果使用多数据源,并且数据源是多种类型时,可能会存在问题:实现之返回一条的语法不同 + * + * @return this + */ + public QueryWrapperX limitN(int n) { + DbType dbType = JdbcUtils.getDbType(); + switch (dbType) { + case ORACLE: + case ORACLE_12C: + super.le("ROWNUM", n); + break; + case SQL_SERVER: + case SQL_SERVER2005: + super.select("TOP " + n + " *"); // 由于 SQL Server 是通过 SELECT TOP 1 实现限制一条,所以只好使用 * 查询剩余字段 + break; + default: // MySQL、PostgreSQL、DM 达梦、KingbaseES 大金都是采用 LIMIT 实现 + super.last("LIMIT " + n); + } + return this; + } + +} diff --git a/tashow-framework/tashow-data-mybatis/src/main/java/com/tashow/cloud/mybatis/mybatis/core/type/EncryptTypeHandler.java b/tashow-framework/tashow-data-mybatis/src/main/java/com/tashow/cloud/mybatis/mybatis/core/type/EncryptTypeHandler.java new file mode 100644 index 0000000..a7072e0 --- /dev/null +++ b/tashow-framework/tashow-data-mybatis/src/main/java/com/tashow/cloud/mybatis/mybatis/core/type/EncryptTypeHandler.java @@ -0,0 +1,75 @@ +package com.tashow.cloud.mybatis.mybatis.core.type; + +import cn.hutool.core.lang.Assert; +import cn.hutool.crypto.SecureUtil; +import cn.hutool.crypto.symmetric.AES; +import cn.hutool.extra.spring.SpringUtil; +import org.apache.ibatis.type.BaseTypeHandler; +import org.apache.ibatis.type.JdbcType; + +import java.sql.CallableStatement; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; + +/** + * 字段字段的 TypeHandler 实现类,基于 {@link cn.hutool.crypto.symmetric.AES} 实现 + * 可通过 jasypt.encryptor.password 配置项,设置密钥 + * + * @author 芋道源码 + */ +public class EncryptTypeHandler extends BaseTypeHandler { + + private static final String ENCRYPTOR_PROPERTY_NAME = "mybatis-plus.encryptor.password"; + + private static AES aes; + + @Override + public void setNonNullParameter(PreparedStatement ps, int i, String parameter, JdbcType jdbcType) throws SQLException { + ps.setString(i, encrypt(parameter)); + } + + @Override + public String getNullableResult(ResultSet rs, String columnName) throws SQLException { + String value = rs.getString(columnName); + return decrypt(value); + } + + @Override + public String getNullableResult(ResultSet rs, int columnIndex) throws SQLException { + String value = rs.getString(columnIndex); + return decrypt(value); + } + + @Override + public String getNullableResult(CallableStatement cs, int columnIndex) throws SQLException { + String value = cs.getString(columnIndex); + return decrypt(value); + } + + private static String decrypt(String value) { + if (value == null) { + return null; + } + return getEncryptor().decryptStr(value); + } + + public static String encrypt(String rawValue) { + if (rawValue == null) { + return null; + } + return getEncryptor().encryptBase64(rawValue); + } + + private static AES getEncryptor() { + if (aes != null) { + return aes; + } + // 构建 AES + String password = SpringUtil.getProperty(ENCRYPTOR_PROPERTY_NAME); + Assert.notEmpty(password, "配置项({}) 不能为空", ENCRYPTOR_PROPERTY_NAME); + aes = SecureUtil.aes(password.getBytes()); + return aes; + } + +} diff --git a/tashow-framework/tashow-data-mybatis/src/main/java/com/tashow/cloud/mybatis/mybatis/core/type/IntegerListTypeHandler.java b/tashow-framework/tashow-data-mybatis/src/main/java/com/tashow/cloud/mybatis/mybatis/core/type/IntegerListTypeHandler.java new file mode 100644 index 0000000..c6d0892 --- /dev/null +++ b/tashow-framework/tashow-data-mybatis/src/main/java/com/tashow/cloud/mybatis/mybatis/core/type/IntegerListTypeHandler.java @@ -0,0 +1,56 @@ +package com.tashow.cloud.mybatis.mybatis.core.type; + +import cn.hutool.core.collection.CollUtil; +import com.tashow.cloud.common.util.string.StrUtils; +import org.apache.ibatis.type.JdbcType; +import org.apache.ibatis.type.MappedJdbcTypes; +import org.apache.ibatis.type.MappedTypes; +import org.apache.ibatis.type.TypeHandler; + +import java.sql.CallableStatement; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.List; + +/** + * List 的类型转换器实现类,对应数据库的 varchar 类型 + * + * @author jason + */ +@MappedJdbcTypes(JdbcType.VARCHAR) +@MappedTypes(List.class) +public class IntegerListTypeHandler implements TypeHandler> { + + private static final String COMMA = ","; + + @Override + public void setParameter(PreparedStatement ps, int i, List strings, JdbcType jdbcType) throws SQLException { + ps.setString(i, CollUtil.join(strings, COMMA)); + } + + @Override + public List getResult(ResultSet rs, String columnName) throws SQLException { + String value = rs.getString(columnName); + return getResult(value); + } + + @Override + public List getResult(ResultSet rs, int columnIndex) throws SQLException { + String value = rs.getString(columnIndex); + return getResult(value); + } + + @Override + public List getResult(CallableStatement cs, int columnIndex) throws SQLException { + String value = cs.getString(columnIndex); + return getResult(value); + } + + private List getResult(String value) { + if (value == null) { + return null; + } + return StrUtils.splitToInteger(value, COMMA); + } +} diff --git a/tashow-framework/tashow-data-mybatis/src/main/java/com/tashow/cloud/mybatis/mybatis/core/type/LongListTypeHandler.java b/tashow-framework/tashow-data-mybatis/src/main/java/com/tashow/cloud/mybatis/mybatis/core/type/LongListTypeHandler.java new file mode 100644 index 0000000..eaa8a36 --- /dev/null +++ b/tashow-framework/tashow-data-mybatis/src/main/java/com/tashow/cloud/mybatis/mybatis/core/type/LongListTypeHandler.java @@ -0,0 +1,57 @@ +package com.tashow.cloud.mybatis.mybatis.core.type; + +import cn.hutool.core.collection.CollUtil; +import com.tashow.cloud.common.util.string.StrUtils; +import org.apache.ibatis.type.JdbcType; +import org.apache.ibatis.type.MappedJdbcTypes; +import org.apache.ibatis.type.MappedTypes; +import org.apache.ibatis.type.TypeHandler; + +import java.sql.CallableStatement; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.List; + +/** + * List 的类型转换器实现类,对应数据库的 varchar 类型 + * + * @author 芋道源码 + */ +@MappedJdbcTypes(JdbcType.VARCHAR) +@MappedTypes(List.class) +public class LongListTypeHandler implements TypeHandler> { + + private static final String COMMA = ","; + + @Override + public void setParameter(PreparedStatement ps, int i, List strings, JdbcType jdbcType) throws SQLException { + // 设置占位符 + ps.setString(i, CollUtil.join(strings, COMMA)); + } + + @Override + public List getResult(ResultSet rs, String columnName) throws SQLException { + String value = rs.getString(columnName); + return getResult(value); + } + + @Override + public List getResult(ResultSet rs, int columnIndex) throws SQLException { + String value = rs.getString(columnIndex); + return getResult(value); + } + + @Override + public List getResult(CallableStatement cs, int columnIndex) throws SQLException { + String value = cs.getString(columnIndex); + return getResult(value); + } + + private List getResult(String value) { + if (value == null) { + return null; + } + return StrUtils.splitToLong(value, COMMA); + } +} diff --git a/tashow-framework/tashow-data-mybatis/src/main/java/com/tashow/cloud/mybatis/mybatis/core/type/StringListTypeHandler.java b/tashow-framework/tashow-data-mybatis/src/main/java/com/tashow/cloud/mybatis/mybatis/core/type/StringListTypeHandler.java new file mode 100644 index 0000000..b138579 --- /dev/null +++ b/tashow-framework/tashow-data-mybatis/src/main/java/com/tashow/cloud/mybatis/mybatis/core/type/StringListTypeHandler.java @@ -0,0 +1,58 @@ +package com.tashow.cloud.mybatis.mybatis.core.type; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.util.StrUtil; +import org.apache.ibatis.type.JdbcType; +import org.apache.ibatis.type.MappedJdbcTypes; +import org.apache.ibatis.type.MappedTypes; +import org.apache.ibatis.type.TypeHandler; + +import java.sql.CallableStatement; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.List; + +/** + * List 的类型转换器实现类,对应数据库的 varchar 类型 + * + * @author 永不言败 + * @since 2022 3/23 12:50:15 + */ +@MappedJdbcTypes(JdbcType.VARCHAR) +@MappedTypes(List.class) +public class StringListTypeHandler implements TypeHandler> { + + private static final String COMMA = ","; + + @Override + public void setParameter(PreparedStatement ps, int i, List strings, JdbcType jdbcType) throws SQLException { + // 设置占位符 + ps.setString(i, CollUtil.join(strings, COMMA)); + } + + @Override + public List getResult(ResultSet rs, String columnName) throws SQLException { + String value = rs.getString(columnName); + return getResult(value); + } + + @Override + public List getResult(ResultSet rs, int columnIndex) throws SQLException { + String value = rs.getString(columnIndex); + return getResult(value); + } + + @Override + public List getResult(CallableStatement cs, int columnIndex) throws SQLException { + String value = cs.getString(columnIndex); + return getResult(value); + } + + private List getResult(String value) { + if (value == null) { + return null; + } + return StrUtil.splitTrim(value, COMMA); + } +} diff --git a/tashow-framework/tashow-data-mybatis/src/main/java/com/tashow/cloud/mybatis/mybatis/core/util/JdbcUtils.java b/tashow-framework/tashow-data-mybatis/src/main/java/com/tashow/cloud/mybatis/mybatis/core/util/JdbcUtils.java new file mode 100644 index 0000000..aa1739c --- /dev/null +++ b/tashow-framework/tashow-data-mybatis/src/main/java/com/tashow/cloud/mybatis/mybatis/core/util/JdbcUtils.java @@ -0,0 +1,89 @@ +package com.tashow.cloud.mybatis.mybatis.core.util; + +import com.baomidou.dynamic.datasource.DynamicRoutingDataSource; +import com.baomidou.mybatisplus.annotation.DbType; +import com.tashow.cloud.common.util.object.ObjectUtils; +import com.tashow.cloud.common.util.spring.SpringUtils; +import com.tashow.cloud.mybatis.mybatis.core.enums.DbTypeEnum; +import org.springframework.beans.factory.NoSuchBeanDefinitionException; + +import javax.sql.DataSource; +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.SQLException; + +/** + * JDBC 工具类 + * + * @author 芋道源码 + */ +public class JdbcUtils { + + /** + * 判断连接是否正确 + * + * @param url 数据源连接 + * @param username 账号 + * @param password 密码 + * @return 是否正确 + */ + public static boolean isConnectionOK(String url, String username, String password) { + try (Connection ignored = DriverManager.getConnection(url, username, password)) { + return true; + } catch (Exception ex) { + return false; + } + } + + /** + * 获得 URL 对应的 DB 类型 + * + * @param url URL + * @return DB 类型 + */ + public static DbType getDbType(String url) { + return com.baomidou.mybatisplus.extension.toolkit.JdbcUtils.getDbType(url); + } + + /** + * 通过当前数据库连接获得对应的 DB 类型 + * + * @return DB 类型 + */ + public static DbType getDbType() { + DataSource dataSource; + try { + DynamicRoutingDataSource dynamicRoutingDataSource = SpringUtils.getBean(DynamicRoutingDataSource.class); + dataSource = dynamicRoutingDataSource.determineDataSource(); + } catch (NoSuchBeanDefinitionException e) { + dataSource = SpringUtils.getBean(DataSource.class); + } + try (Connection conn = dataSource.getConnection()) { + return DbTypeEnum.find(conn.getMetaData().getDatabaseProductName()); + } catch (SQLException e) { + throw new IllegalArgumentException(e.getMessage()); + } + } + + /** + * 判断 JDBC 连接是否为 SQLServer 数据库 + * + * @param url JDBC 连接 + * @return 是否为 SQLServer 数据库 + */ + public static boolean isSQLServer(String url) { + DbType dbType = getDbType(url); + return isSQLServer(dbType); + } + + /** + * 判断 JDBC 连接是否为 SQLServer 数据库 + * + * @param dbType DB 类型 + * @return 是否为 SQLServer 数据库 + */ + public static boolean isSQLServer(DbType dbType) { + return ObjectUtils.equalsAny(dbType, DbType.SQL_SERVER, DbType.SQL_SERVER2005); + } + +} diff --git a/tashow-framework/tashow-data-mybatis/src/main/java/com/tashow/cloud/mybatis/mybatis/core/util/MyBatisUtils.java b/tashow-framework/tashow-data-mybatis/src/main/java/com/tashow/cloud/mybatis/mybatis/core/util/MyBatisUtils.java new file mode 100644 index 0000000..3f1443d --- /dev/null +++ b/tashow-framework/tashow-data-mybatis/src/main/java/com/tashow/cloud/mybatis/mybatis/core/util/MyBatisUtils.java @@ -0,0 +1,106 @@ +package com.tashow.cloud.mybatis.mybatis.core.util; + +import cn.hutool.core.collection.CollectionUtil; +import cn.hutool.core.util.StrUtil; +import com.baomidou.mybatisplus.annotation.DbType; +import com.baomidou.mybatisplus.core.metadata.OrderItem; +import com.baomidou.mybatisplus.core.toolkit.StringPool; +import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor; +import com.baomidou.mybatisplus.extension.plugins.inner.InnerInterceptor; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.tashow.cloud.common.pojo.PageParam; +import com.tashow.cloud.common.pojo.SortingField; +import com.tashow.cloud.mybatis.mybatis.core.enums.DbTypeEnum; +import net.sf.jsqlparser.expression.Alias; +import net.sf.jsqlparser.schema.Column; +import net.sf.jsqlparser.schema.Table; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.stream.Collectors; + +/** + * MyBatis 工具类 + */ +public class MyBatisUtils { + + private static final String MYSQL_ESCAPE_CHARACTER = "`"; + + public static Page buildPage(PageParam pageParam) { + return buildPage(pageParam, null); + } + + public static Page buildPage(PageParam pageParam, Collection sortingFields) { + // 页码 + 数量 + Page page = new Page<>(pageParam.getPageNo(), pageParam.getPageSize()); + // 排序字段 + if (!CollectionUtil.isEmpty(sortingFields)) { + page.addOrder(sortingFields.stream().map(sortingField -> SortingField.ORDER_ASC.equals(sortingField.getOrder()) + ? OrderItem.asc(StrUtil.toUnderlineCase(sortingField.getField())) + : OrderItem.desc(StrUtil.toUnderlineCase(sortingField.getField()))) + .collect(Collectors.toList())); + } + return page; + } + + /** + * 将拦截器添加到链中 + * 由于 MybatisPlusInterceptor 不支持添加拦截器,所以只能全量设置 + * + * @param interceptor 链 + * @param inner 拦截器 + * @param index 位置 + */ + public static void addInterceptor(MybatisPlusInterceptor interceptor, InnerInterceptor inner, int index) { + List inners = new ArrayList<>(interceptor.getInterceptors()); + inners.add(index, inner); + interceptor.setInterceptors(inners); + } + + /** + * 获得 Table 对应的表名 + *

+ * 兼容 MySQL 转义表名 `t_xxx` + * + * @param table 表 + * @return 去除转移字符后的表名 + */ + public static String getTableName(Table table) { + String tableName = table.getName(); + if (tableName.startsWith(MYSQL_ESCAPE_CHARACTER) && tableName.endsWith(MYSQL_ESCAPE_CHARACTER)) { + tableName = tableName.substring(1, tableName.length() - 1); + } + return tableName; + } + + /** + * 构建 Column 对象 + * + * @param tableName 表名 + * @param tableAlias 别名 + * @param column 字段名 + * @return Column 对象 + */ + public static Column buildColumn(String tableName, Alias tableAlias, String column) { + if (tableAlias != null) { + tableName = tableAlias.getName(); + } + return new Column(tableName + StringPool.DOT + column); + } + + /** + * 跨数据库的 find_in_set 实现 + * + * @param column 字段名称 + * @param value 查询值(不带单引号) + * @return sql + */ + public static String findInSet(String column, Object value) { + DbType dbType = JdbcUtils.getDbType(); + return DbTypeEnum.getFindInSetTemplate(dbType) + .replace("#{column}", column) + .replace("#{value}", StrUtil.toString(value)); + } + +} diff --git a/tashow-framework/tashow-data-mybatis/src/main/java/com/tashow/cloud/mybatis/mybatis/package-info.java b/tashow-framework/tashow-data-mybatis/src/main/java/com/tashow/cloud/mybatis/mybatis/package-info.java new file mode 100644 index 0000000..64ce74e --- /dev/null +++ b/tashow-framework/tashow-data-mybatis/src/main/java/com/tashow/cloud/mybatis/mybatis/package-info.java @@ -0,0 +1,4 @@ +/** + * 使用 MyBatis Plus 提升使用 MyBatis 的开发效率 + */ +package com.tashow.cloud.mybatis.mybatis; diff --git a/tashow-framework/tashow-data-mybatis/src/main/java/com/tashow/cloud/mybatis/translate/config/TranslateAutoConfiguration.java b/tashow-framework/tashow-data-mybatis/src/main/java/com/tashow/cloud/mybatis/translate/config/TranslateAutoConfiguration.java new file mode 100644 index 0000000..03ea639 --- /dev/null +++ b/tashow-framework/tashow-data-mybatis/src/main/java/com/tashow/cloud/mybatis/translate/config/TranslateAutoConfiguration.java @@ -0,0 +1,18 @@ +package com.tashow.cloud.mybatis.translate.config; + +import com.fhs.trans.service.impl.TransService; +import com.tashow.cloud.mybatis.translate.core.TranslateUtils; +import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.context.annotation.Bean; + +@AutoConfiguration +public class TranslateAutoConfiguration { + + @Bean + @SuppressWarnings({"InstantiationOfUtilityClass", "SpringJavaInjectionPointsAutowiringInspection"}) + public TranslateUtils translateUtils(TransService transService) { + TranslateUtils.init(transService); + return new TranslateUtils(); + } + +} diff --git a/tashow-framework/tashow-data-mybatis/src/main/java/com/tashow/cloud/mybatis/translate/core/TranslateUtils.java b/tashow-framework/tashow-data-mybatis/src/main/java/com/tashow/cloud/mybatis/translate/core/TranslateUtils.java new file mode 100644 index 0000000..4b76b24 --- /dev/null +++ b/tashow-framework/tashow-data-mybatis/src/main/java/com/tashow/cloud/mybatis/translate/core/TranslateUtils.java @@ -0,0 +1,37 @@ +package com.tashow.cloud.mybatis.translate.core; + +import cn.hutool.core.collection.CollUtil; +import com.fhs.core.trans.vo.VO; +import com.fhs.trans.service.impl.TransService; + +import java.util.List; + +/** + * VO 数据翻译 Utils + * + * @author 芋道源码 + */ +public class TranslateUtils { + + private static TransService transService; + + public static void init(TransService transService) { + TranslateUtils.transService = transService; + } + + /** + * 数据翻译 + * + * 使用场景:无法使用 @TransMethodResult 注解的场景,只能通过手动触发翻译 + * + * @param data 数据 + * @return 翻译结果 + */ + public static List translate(List data) { + if (CollUtil.isNotEmpty((data))) { + transService.transBatch(data); + } + return data; + } + +} diff --git a/tashow-framework/tashow-data-mybatis/src/main/java/com/tashow/cloud/mybatis/translate/package-info.java b/tashow-framework/tashow-data-mybatis/src/main/java/com/tashow/cloud/mybatis/translate/package-info.java new file mode 100644 index 0000000..a24154e --- /dev/null +++ b/tashow-framework/tashow-data-mybatis/src/main/java/com/tashow/cloud/mybatis/translate/package-info.java @@ -0,0 +1,4 @@ +/** + * 使用 Easy-Trans 提升使用 VO 数据翻译的开发效率 + */ +package com.tashow.cloud.mybatis.translate; diff --git a/tashow-framework/tashow-data-mybatis/src/main/resources/META-INF/spring.factories b/tashow-framework/tashow-data-mybatis/src/main/resources/META-INF/spring.factories new file mode 100644 index 0000000..8ff75f4 --- /dev/null +++ b/tashow-framework/tashow-data-mybatis/src/main/resources/META-INF/spring.factories @@ -0,0 +1,2 @@ +org.springframework.boot.env.EnvironmentPostProcessor=\ + com.tashow.cloud.mybatis.mybatis.config.IdTypeEnvironmentPostProcessor diff --git a/tashow-framework/tashow-data-mybatis/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/tashow-framework/tashow-data-mybatis/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports new file mode 100644 index 0000000..8a31041 --- /dev/null +++ b/tashow-framework/tashow-data-mybatis/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -0,0 +1,3 @@ +com.tashow.cloud.mybatis.datasource.config.DataSourceAutoConfiguration +com.tashow.cloud.mybatis.mybatis.config.BaseMybatisAutoConfiguration +com.tashow.cloud.mybatis.translate.config.TranslateAutoConfiguration diff --git a/tashow-framework/tashow-data-permission/pom.xml b/tashow-framework/tashow-data-permission/pom.xml new file mode 100644 index 0000000..84f3240 --- /dev/null +++ b/tashow-framework/tashow-data-permission/pom.xml @@ -0,0 +1,52 @@ + + + 4.0.0 + + com.tashow.cloud + tashow-framework + ${revision} + + tashow-data-permission + jar + + ${project.artifactId} + 数据权限 + + + + com.tashow.cloud + tashow-common + + + + + com.tashow.cloud + tashow-framework-security + true + + + + + com.tashow.cloud + tashow-data-mybatis + + + + + com.tashow.cloud + tashow-framework-rpc + true + + + + + com.tashow.cloud + tashow-system-api + ${revision} + + + + + diff --git a/tashow-framework/tashow-data-permission/src/main/java/com/tashow/cloud/permission/config/DataPermissionAutoConfiguration.java b/tashow-framework/tashow-data-permission/src/main/java/com/tashow/cloud/permission/config/DataPermissionAutoConfiguration.java new file mode 100644 index 0000000..6cbc372 --- /dev/null +++ b/tashow-framework/tashow-data-permission/src/main/java/com/tashow/cloud/permission/config/DataPermissionAutoConfiguration.java @@ -0,0 +1,46 @@ +package com.tashow.cloud.permission.config; + +import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor; +import com.baomidou.mybatisplus.extension.plugins.inner.DataPermissionInterceptor; +import com.tashow.cloud.permission.core.rule.DataPermissionRuleFactory; +import com.tashow.cloud.permission.core.rule.DataPermissionRuleFactoryImpl; +import com.tashow.cloud.permission.core.aop.DataPermissionAnnotationAdvisor; +import com.tashow.cloud.permission.core.db.DataPermissionRuleHandler; +import com.tashow.cloud.permission.core.rule.DataPermissionRule; +import com.tashow.cloud.mybatis.mybatis.core.util.MyBatisUtils; +import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.context.annotation.Bean; + +import java.util.List; + +/** + * 数据权限的自动配置类 + * + * @author 芋道源码 + */ +@AutoConfiguration +public class DataPermissionAutoConfiguration { + + @Bean + public DataPermissionRuleFactory dataPermissionRuleFactory(List rules) { + return new DataPermissionRuleFactoryImpl(rules); + } + + @Bean + public DataPermissionRuleHandler dataPermissionRuleHandler(MybatisPlusInterceptor interceptor, + DataPermissionRuleFactory ruleFactory) { + // 创建 DataPermissionInterceptor 拦截器 + DataPermissionRuleHandler handler = new DataPermissionRuleHandler(ruleFactory); + DataPermissionInterceptor inner = new DataPermissionInterceptor(handler); + // 添加到 interceptor 中 + // 需要加在首个,主要是为了在分页插件前面。这个是 MyBatis Plus 的规定 + MyBatisUtils.addInterceptor(interceptor, inner, 0); + return handler; + } + + @Bean + public DataPermissionAnnotationAdvisor dataPermissionAnnotationAdvisor() { + return new DataPermissionAnnotationAdvisor(); + } + +} diff --git a/tashow-framework/tashow-data-permission/src/main/java/com/tashow/cloud/permission/config/DataPermissionRpcAutoConfiguration.java b/tashow-framework/tashow-data-permission/src/main/java/com/tashow/cloud/permission/config/DataPermissionRpcAutoConfiguration.java new file mode 100644 index 0000000..29cadca --- /dev/null +++ b/tashow-framework/tashow-data-permission/src/main/java/com/tashow/cloud/permission/config/DataPermissionRpcAutoConfiguration.java @@ -0,0 +1,35 @@ +package com.tashow.cloud.permission.config; + +import com.tashow.cloud.permission.core.rpc.DataPermissionRequestInterceptor; +import com.tashow.cloud.permission.core.rpc.DataPermissionRpcWebFilter; +import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.web.servlet.FilterRegistrationBean; +import org.springframework.context.annotation.Bean; + +import static com.tashow.cloud.common.enums.WebFilterOrderEnum.TENANT_CONTEXT_FILTER; + + +/** + * 数据权限针对 RPC 的自动配置类 + * + * @author 芋道源码 + */ +@AutoConfiguration +@ConditionalOnClass(name = "feign.RequestInterceptor") +public class DataPermissionRpcAutoConfiguration { + + @Bean + public DataPermissionRequestInterceptor dataPermissionRequestInterceptor() { + return new DataPermissionRequestInterceptor(); + } + + @Bean + public FilterRegistrationBean dataPermissionRpcFilter() { + FilterRegistrationBean registrationBean = new FilterRegistrationBean<>(); + registrationBean.setFilter(new DataPermissionRpcWebFilter()); + registrationBean.setOrder(TENANT_CONTEXT_FILTER - 1); // 顺序没有绝对的要求,在租户 Filter 前面稳妥点 + return registrationBean; + } + +} diff --git a/tashow-framework/tashow-data-permission/src/main/java/com/tashow/cloud/permission/config/DeptDataPermissionAutoConfiguration.java b/tashow-framework/tashow-data-permission/src/main/java/com/tashow/cloud/permission/config/DeptDataPermissionAutoConfiguration.java new file mode 100644 index 0000000..cc57f7b --- /dev/null +++ b/tashow-framework/tashow-data-permission/src/main/java/com/tashow/cloud/permission/config/DeptDataPermissionAutoConfiguration.java @@ -0,0 +1,44 @@ +package com.tashow.cloud.permission.config; + +import cn.hutool.extra.spring.SpringUtil; +import com.tashow.cloud.permission.core.rule.dept.DeptDataPermissionRule; +import com.tashow.cloud.permission.core.rule.dept.DeptDataPermissionRuleCustomizer; +import com.tashow.cloud.security.security.core.LoginUser; +import com.tashow.cloud.systemapi.api.permission.PermissionApi; +import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.context.annotation.Bean; + +import java.util.List; + +/** + * 基于部门的数据权限 AutoConfiguration + * + * @author 芋道源码 + */ +@AutoConfiguration +@ConditionalOnClass(LoginUser.class) +@ConditionalOnBean(value = DeptDataPermissionRuleCustomizer.class) +public class DeptDataPermissionAutoConfiguration { + + @Bean + public DeptDataPermissionRule deptDataPermissionRule(PermissionApi permissionApi, + List customizers) { + // Cloud 专属逻辑:优先使用本地的 PermissionApi 实现类,而不是 Feign 调用 + // 原因:在创建租户时,租户还没创建好,导致 Feign 调用获取数据权限时,报“租户不存在”的错误 + try { + PermissionApi permissionApiImpl = SpringUtil.getBean("permissionApiImpl", PermissionApi.class); + if (permissionApiImpl != null) { + permissionApi = permissionApiImpl; + } + } catch (Exception ignored) {} + + // 创建 DeptDataPermissionRule 对象 + DeptDataPermissionRule rule = new DeptDataPermissionRule(permissionApi); + // 补全表配置 + customizers.forEach(customizer -> customizer.customize(rule)); + return rule; + } + +} diff --git a/tashow-framework/tashow-data-permission/src/main/java/com/tashow/cloud/permission/core/annotation/DataPermission.java b/tashow-framework/tashow-data-permission/src/main/java/com/tashow/cloud/permission/core/annotation/DataPermission.java new file mode 100644 index 0000000..c48c7ed --- /dev/null +++ b/tashow-framework/tashow-data-permission/src/main/java/com/tashow/cloud/permission/core/annotation/DataPermission.java @@ -0,0 +1,35 @@ +package com.tashow.cloud.permission.core.annotation; + +import com.tashow.cloud.permission.core.rule.DataPermissionRule; + +import java.lang.annotation.*; + +/** + * 数据权限注解 + * 可声明在类或者方法上,标识使用的数据权限规则 + * + * @author 芋道源码 + */ +@Target({ElementType.TYPE, ElementType.METHOD}) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface DataPermission { + + /** + * 当前类或方法是否开启数据权限 + * 即使不添加 @DataPermission 注解,默认是开启状态 + * 可通过设置 enable 为 false 禁用 + */ + boolean enable() default true; + + /** + * 生效的数据权限规则数组,优先级高于 {@link #excludeRules()} + */ + Class[] includeRules() default {}; + + /** + * 排除的数据权限规则数组,优先级最低 + */ + Class[] excludeRules() default {}; + +} diff --git a/tashow-framework/tashow-data-permission/src/main/java/com/tashow/cloud/permission/core/aop/DataPermissionAnnotationAdvisor.java b/tashow-framework/tashow-data-permission/src/main/java/com/tashow/cloud/permission/core/aop/DataPermissionAnnotationAdvisor.java new file mode 100644 index 0000000..c18aa69 --- /dev/null +++ b/tashow-framework/tashow-data-permission/src/main/java/com/tashow/cloud/permission/core/aop/DataPermissionAnnotationAdvisor.java @@ -0,0 +1,36 @@ +package com.tashow.cloud.permission.core.aop; + +import com.tashow.cloud.permission.core.annotation.DataPermission; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import org.aopalliance.aop.Advice; +import org.springframework.aop.Pointcut; +import org.springframework.aop.support.AbstractPointcutAdvisor; +import org.springframework.aop.support.ComposablePointcut; +import org.springframework.aop.support.annotation.AnnotationMatchingPointcut; + +/** + * {@link DataPermission} 注解的 Advisor 实现类 + * + * @author 芋道源码 + */ +@Getter +@EqualsAndHashCode(callSuper = true) +public class DataPermissionAnnotationAdvisor extends AbstractPointcutAdvisor { + + private final Advice advice; + + private final Pointcut pointcut; + + public DataPermissionAnnotationAdvisor() { + this.advice = new DataPermissionAnnotationInterceptor(); + this.pointcut = this.buildPointcut(); + } + + protected Pointcut buildPointcut() { + Pointcut classPointcut = new AnnotationMatchingPointcut(DataPermission.class, true); + Pointcut methodPointcut = new AnnotationMatchingPointcut(null, DataPermission.class, true); + return new ComposablePointcut(classPointcut).union(methodPointcut); + } + +} diff --git a/tashow-framework/tashow-data-permission/src/main/java/com/tashow/cloud/permission/core/aop/DataPermissionAnnotationInterceptor.java b/tashow-framework/tashow-data-permission/src/main/java/com/tashow/cloud/permission/core/aop/DataPermissionAnnotationInterceptor.java new file mode 100644 index 0000000..a468960 --- /dev/null +++ b/tashow-framework/tashow-data-permission/src/main/java/com/tashow/cloud/permission/core/aop/DataPermissionAnnotationInterceptor.java @@ -0,0 +1,72 @@ +package com.tashow.cloud.permission.core.aop; + +import com.tashow.cloud.permission.core.annotation.DataPermission; +import lombok.Getter; +import org.aopalliance.intercept.MethodInterceptor; +import org.aopalliance.intercept.MethodInvocation; +import org.springframework.core.MethodClassKey; +import org.springframework.core.annotation.AnnotationUtils; + +import java.lang.reflect.Method; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + * {@link DataPermission} 注解的拦截器 + * 1. 在执行方法前,将 @DataPermission 注解入栈 + * 2. 在执行方法后,将 @DataPermission 注解出栈 + * + * @author 芋道源码 + */ +@DataPermission // 该注解,用于 {@link DATA_PERMISSION_NULL} 的空对象 +public class DataPermissionAnnotationInterceptor implements MethodInterceptor { + + /** + * DataPermission 空对象,用于方法无 {@link DataPermission} 注解时,使用 DATA_PERMISSION_NULL 进行占位 + */ + static final DataPermission DATA_PERMISSION_NULL = DataPermissionAnnotationInterceptor.class.getAnnotation(DataPermission.class); + + @Getter + private final Map dataPermissionCache = new ConcurrentHashMap<>(); + + @Override + public Object invoke(MethodInvocation methodInvocation) throws Throwable { + // 入栈 + DataPermission dataPermission = this.findAnnotation(methodInvocation); + if (dataPermission != null) { + DataPermissionContextHolder.add(dataPermission); + } + try { + // 执行逻辑 + return methodInvocation.proceed(); + } finally { + // 出栈 + if (dataPermission != null) { + DataPermissionContextHolder.remove(); + } + } + } + + private DataPermission findAnnotation(MethodInvocation methodInvocation) { + // 1. 从缓存中获取 + Method method = methodInvocation.getMethod(); + Object targetObject = methodInvocation.getThis(); + Class clazz = targetObject != null ? targetObject.getClass() : method.getDeclaringClass(); + MethodClassKey methodClassKey = new MethodClassKey(method, clazz); + DataPermission dataPermission = dataPermissionCache.get(methodClassKey); + if (dataPermission != null) { + return dataPermission != DATA_PERMISSION_NULL ? dataPermission : null; + } + + // 2.1 从方法中获取 + dataPermission = AnnotationUtils.findAnnotation(method, DataPermission.class); + // 2.2 从类上获取 + if (dataPermission == null) { + dataPermission = AnnotationUtils.findAnnotation(clazz, DataPermission.class); + } + // 2.3 添加到缓存中 + dataPermissionCache.put(methodClassKey, dataPermission != null ? dataPermission : DATA_PERMISSION_NULL); + return dataPermission; + } + +} diff --git a/tashow-framework/tashow-data-permission/src/main/java/com/tashow/cloud/permission/core/aop/DataPermissionContextHolder.java b/tashow-framework/tashow-data-permission/src/main/java/com/tashow/cloud/permission/core/aop/DataPermissionContextHolder.java new file mode 100644 index 0000000..58d51d4 --- /dev/null +++ b/tashow-framework/tashow-data-permission/src/main/java/com/tashow/cloud/permission/core/aop/DataPermissionContextHolder.java @@ -0,0 +1,72 @@ +package com.tashow.cloud.permission.core.aop; + +import com.tashow.cloud.permission.core.annotation.DataPermission; +import com.alibaba.ttl.TransmittableThreadLocal; + +import java.util.LinkedList; +import java.util.List; + +/** + * {@link DataPermission} 注解的 Context 上下文 + * + * @author 芋道源码 + */ +public class DataPermissionContextHolder { + + /** + * 使用 List 的原因,可能存在方法的嵌套调用 + */ + private static final ThreadLocal> DATA_PERMISSIONS = + TransmittableThreadLocal.withInitial(LinkedList::new); + + /** + * 获得当前的 DataPermission 注解 + * + * @return DataPermission 注解 + */ + public static DataPermission get() { + return DATA_PERMISSIONS.get().peekLast(); + } + + /** + * 入栈 DataPermission 注解 + * + * @param dataPermission DataPermission 注解 + */ + public static void add(DataPermission dataPermission) { + DATA_PERMISSIONS.get().addLast(dataPermission); + } + + /** + * 出栈 DataPermission 注解 + * + * @return DataPermission 注解 + */ + public static DataPermission remove() { + DataPermission dataPermission = DATA_PERMISSIONS.get().removeLast(); + // 无元素时,清空 ThreadLocal + if (DATA_PERMISSIONS.get().isEmpty()) { + DATA_PERMISSIONS.remove(); + } + return dataPermission; + } + + /** + * 获得所有 DataPermission + * + * @return DataPermission 队列 + */ + public static List getAll() { + return DATA_PERMISSIONS.get(); + } + + /** + * 清空上下文 + * + * 目前仅仅用于单测 + */ + public static void clear() { + DATA_PERMISSIONS.remove(); + } + +} diff --git a/tashow-framework/tashow-data-permission/src/main/java/com/tashow/cloud/permission/core/db/DataPermissionRuleHandler.java b/tashow-framework/tashow-data-permission/src/main/java/com/tashow/cloud/permission/core/db/DataPermissionRuleHandler.java new file mode 100644 index 0000000..2fbf3fd --- /dev/null +++ b/tashow-framework/tashow-data-permission/src/main/java/com/tashow/cloud/permission/core/db/DataPermissionRuleHandler.java @@ -0,0 +1,57 @@ +package com.tashow.cloud.permission.core.db; + +import cn.hutool.core.collection.CollUtil; +import com.tashow.cloud.mybatis.mybatis.core.util.MyBatisUtils; +import com.tashow.cloud.permission.core.rule.DataPermissionRule; +import com.tashow.cloud.permission.core.rule.DataPermissionRuleFactory; +import com.baomidou.mybatisplus.extension.plugins.handler.MultiDataPermissionHandler; +import lombok.RequiredArgsConstructor; +import net.sf.jsqlparser.expression.Expression; +import net.sf.jsqlparser.expression.operators.conditional.AndExpression; +import net.sf.jsqlparser.schema.Table; + +import java.util.List; + +/** + * 基于 {@link DataPermissionRule} 的数据权限处理器 + * + * 它的底层,是基于 MyBatis Plus 的 数据权限插件 + * 核心原理:它会在 SQL 执行前拦截 SQL 语句,并根据用户权限动态添加权限相关的 SQL 片段。这样,只有用户有权限访问的数据才会被查询出来 + * + * @author 芋道源码 + */ +@RequiredArgsConstructor +public class DataPermissionRuleHandler implements MultiDataPermissionHandler { + + private final DataPermissionRuleFactory ruleFactory; + + @Override + public Expression getSqlSegment(Table table, Expression where, String mappedStatementId) { + // 获得 Mapper 对应的数据权限的规则 + List rules = ruleFactory.getDataPermissionRule(mappedStatementId); + if (CollUtil.isEmpty(rules)) { + return null; + } + + // 生成条件 + Expression allExpression = null; + for (DataPermissionRule rule : rules) { + // 判断表名是否匹配 + String tableName = MyBatisUtils.getTableName(table); + if (!rule.getTableNames().contains(tableName)) { + continue; + } + + // 单条规则的条件 + Expression oneExpress = rule.getExpression(tableName, table.getAlias()); + if (oneExpress == null) { + continue; + } + // 拼接到 allExpression 中 + allExpression = allExpression == null ? oneExpress + : new AndExpression(allExpression, oneExpress); + } + return allExpression; + } + +} diff --git a/tashow-framework/tashow-data-permission/src/main/java/com/tashow/cloud/permission/core/rpc/DataPermissionRequestInterceptor.java b/tashow-framework/tashow-data-permission/src/main/java/com/tashow/cloud/permission/core/rpc/DataPermissionRequestInterceptor.java new file mode 100644 index 0000000..3da4535 --- /dev/null +++ b/tashow-framework/tashow-data-permission/src/main/java/com/tashow/cloud/permission/core/rpc/DataPermissionRequestInterceptor.java @@ -0,0 +1,27 @@ +package com.tashow.cloud.permission.core.rpc; + +import com.tashow.cloud.permission.core.annotation.DataPermission; +import com.tashow.cloud.permission.core.aop.DataPermissionContextHolder; +import feign.RequestInterceptor; +import feign.RequestTemplate; + +/** + * DataPermission 的 RequestInterceptor 实现类:Feign 请求时,将 {@link DataPermission} 设置到 header 中,继续透传给被调用的服务 + * + * 注意:由于 {@link DataPermission} 不支持序列化和反序列化,所以暂时只能传递它的 enable 属性 + * + * @author 芋道源码 + */ +public class DataPermissionRequestInterceptor implements RequestInterceptor { + + public static final String ENABLE_HEADER_NAME = "data-permission-enable"; + + @Override + public void apply(RequestTemplate requestTemplate) { + DataPermission dataPermission = DataPermissionContextHolder.get(); + if (dataPermission != null && Boolean.FALSE.equals(dataPermission.enable())) { + requestTemplate.header(ENABLE_HEADER_NAME, "false"); + } + } + +} diff --git a/tashow-framework/tashow-data-permission/src/main/java/com/tashow/cloud/permission/core/rpc/DataPermissionRpcWebFilter.java b/tashow-framework/tashow-data-permission/src/main/java/com/tashow/cloud/permission/core/rpc/DataPermissionRpcWebFilter.java new file mode 100644 index 0000000..cdba72b --- /dev/null +++ b/tashow-framework/tashow-data-permission/src/main/java/com/tashow/cloud/permission/core/rpc/DataPermissionRpcWebFilter.java @@ -0,0 +1,38 @@ +package com.tashow.cloud.permission.core.rpc; + +import com.tashow.cloud.permission.core.util.DataPermissionUtils; +import com.tashow.cloud.permission.core.aop.DataPermissionContextHolder; +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.springframework.web.filter.OncePerRequestFilter; + +import java.io.IOException; +import java.util.Objects; + +/** + * 针对 {@link DataPermissionRequestInterceptor} 的 RPC 调用,设置 {@link DataPermissionContextHolder} 的上下文 + * + * @author 芋道源码 + */ +public class DataPermissionRpcWebFilter extends OncePerRequestFilter { + + @Override + protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) + throws ServletException, IOException { + String enable = request.getHeader(DataPermissionRequestInterceptor.ENABLE_HEADER_NAME); + if (Objects.equals(enable, Boolean.FALSE.toString())) { + DataPermissionUtils.executeIgnore(() -> { + try { + chain.doFilter(request, response); + } catch (IOException | ServletException e) { + throw new RuntimeException(e); + } + }); + } else { + chain.doFilter(request, response); + } + } + +} diff --git a/tashow-framework/tashow-data-permission/src/main/java/com/tashow/cloud/permission/core/rule/DataPermissionRule.java b/tashow-framework/tashow-data-permission/src/main/java/com/tashow/cloud/permission/core/rule/DataPermissionRule.java new file mode 100644 index 0000000..ffc4afb --- /dev/null +++ b/tashow-framework/tashow-data-permission/src/main/java/com/tashow/cloud/permission/core/rule/DataPermissionRule.java @@ -0,0 +1,36 @@ +package com.tashow.cloud.permission.core.rule; + +import com.baomidou.mybatisplus.core.metadata.TableInfoHelper; +import net.sf.jsqlparser.expression.Alias; +import net.sf.jsqlparser.expression.Expression; + +import java.util.Set; + +/** + * 数据权限规则接口 + * 通过实现接口,自定义数据规则。例如说, + * + * @author 芋道源码 + */ +public interface DataPermissionRule { + + /** + * 返回需要生效的表名数组 + * 为什么需要该方法?Data Permission 数组基于 SQL 重写,通过 Where 返回只有权限的数据 + * + * 如果需要基于实体名获得表名,可调用 {@link TableInfoHelper#getTableInfo(Class)} 获得 + * + * @return 表名数组 + */ + Set getTableNames(); + + /** + * 根据表名和别名,生成对应的 WHERE / OR 过滤条件 + * + * @param tableName 表名 + * @param tableAlias 别名,可能为空 + * @return 过滤条件 Expression 表达式 + */ + Expression getExpression(String tableName, Alias tableAlias); + +} diff --git a/tashow-framework/tashow-data-permission/src/main/java/com/tashow/cloud/permission/core/rule/DataPermissionRuleFactory.java b/tashow-framework/tashow-data-permission/src/main/java/com/tashow/cloud/permission/core/rule/DataPermissionRuleFactory.java new file mode 100644 index 0000000..65c3dae --- /dev/null +++ b/tashow-framework/tashow-data-permission/src/main/java/com/tashow/cloud/permission/core/rule/DataPermissionRuleFactory.java @@ -0,0 +1,28 @@ +package com.tashow.cloud.permission.core.rule; + +import java.util.List; + +/** + * {@link DataPermissionRule} 工厂接口 + * 作为 {@link DataPermissionRule} 的容器,提供管理能力 + * + * @author 芋道源码 + */ +public interface DataPermissionRuleFactory { + + /** + * 获得所有数据权限规则数组 + * + * @return 数据权限规则数组 + */ + List getDataPermissionRules(); + + /** + * 获得指定 Mapper 的数据权限规则数组 + * + * @param mappedStatementId 指定 Mapper 的编号 + * @return 数据权限规则数组 + */ + List getDataPermissionRule(String mappedStatementId); + +} diff --git a/tashow-framework/tashow-data-permission/src/main/java/com/tashow/cloud/permission/core/rule/DataPermissionRuleFactoryImpl.java b/tashow-framework/tashow-data-permission/src/main/java/com/tashow/cloud/permission/core/rule/DataPermissionRuleFactoryImpl.java new file mode 100644 index 0000000..3a81cf4 --- /dev/null +++ b/tashow-framework/tashow-data-permission/src/main/java/com/tashow/cloud/permission/core/rule/DataPermissionRuleFactoryImpl.java @@ -0,0 +1,62 @@ +package com.tashow.cloud.permission.core.rule; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.util.ArrayUtil; +import com.tashow.cloud.permission.core.annotation.DataPermission; +import com.tashow.cloud.permission.core.aop.DataPermissionContextHolder; +import lombok.RequiredArgsConstructor; + +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; + +/** + * 默认的 DataPermissionRuleFactoryImpl 实现类 + * 支持通过 {@link DataPermissionContextHolder} 过滤数据权限 + * + * @author 芋道源码 + */ +@RequiredArgsConstructor +public class DataPermissionRuleFactoryImpl implements DataPermissionRuleFactory { + + /** + * 数据权限规则数组 + */ + private final List rules; + + @Override + public List getDataPermissionRules() { + return rules; + } + + @Override // mappedStatementId 参数,暂时没有用。以后,可以基于 mappedStatementId + DataPermission 进行缓存 + public List getDataPermissionRule(String mappedStatementId) { + // 1. 无数据权限 + if (CollUtil.isEmpty(rules)) { + return Collections.emptyList(); + } + // 2. 未配置,则默认开启 + DataPermission dataPermission = DataPermissionContextHolder.get(); + if (dataPermission == null) { + return rules; + } + // 3. 已配置,但禁用 + if (!dataPermission.enable()) { + return Collections.emptyList(); + } + + // 4. 已配置,只选择部分规则 + if (ArrayUtil.isNotEmpty(dataPermission.includeRules())) { + return rules.stream().filter(rule -> ArrayUtil.contains(dataPermission.includeRules(), rule.getClass())) + .collect(Collectors.toList()); // 一般规则不会太多,所以不采用 HashSet 查询 + } + // 5. 已配置,只排除部分规则 + if (ArrayUtil.isNotEmpty(dataPermission.excludeRules())) { + return rules.stream().filter(rule -> !ArrayUtil.contains(dataPermission.excludeRules(), rule.getClass())) + .collect(Collectors.toList()); // 一般规则不会太多,所以不采用 HashSet 查询 + } + // 6. 已配置,全部规则 + return rules; + } + +} diff --git a/tashow-framework/tashow-data-permission/src/main/java/com/tashow/cloud/permission/core/rule/dept/DeptDataPermissionRule.java b/tashow-framework/tashow-data-permission/src/main/java/com/tashow/cloud/permission/core/rule/dept/DeptDataPermissionRule.java new file mode 100644 index 0000000..65ea201 --- /dev/null +++ b/tashow-framework/tashow-data-permission/src/main/java/com/tashow/cloud/permission/core/rule/dept/DeptDataPermissionRule.java @@ -0,0 +1,207 @@ +package com.tashow.cloud.permission.core.rule.dept; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.StrUtil; +import com.tashow.cloud.common.enums.UserTypeEnum; +import com.tashow.cloud.common.util.collection.CollectionUtils; +import com.tashow.cloud.common.util.json.JsonUtils; +import com.tashow.cloud.mybatis.mybatis.core.dataobject.BaseDO; +import com.tashow.cloud.mybatis.mybatis.core.util.MyBatisUtils; +import com.tashow.cloud.permission.core.rule.DataPermissionRule; +import com.tashow.cloud.security.security.core.LoginUser; +import com.tashow.cloud.security.security.core.util.SecurityFrameworkUtils; +import com.tashow.cloud.systemapi.api.permission.PermissionApi; +import com.tashow.cloud.systemapi.api.permission.dto.DeptDataPermissionRespDTO; +import com.baomidou.mybatisplus.core.metadata.TableInfoHelper; +import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import net.sf.jsqlparser.expression.*; +import net.sf.jsqlparser.expression.operators.conditional.OrExpression; +import net.sf.jsqlparser.expression.operators.relational.EqualsTo; +import net.sf.jsqlparser.expression.operators.relational.ExpressionList; +import net.sf.jsqlparser.expression.operators.relational.InExpression; +import net.sf.jsqlparser.expression.operators.relational.ParenthesedExpressionList; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +/** + * 基于部门的 {@link DataPermissionRule} 数据权限规则实现 + * + * 注意,使用 DeptDataPermissionRule 时,需要保证表中有 dept_id 部门编号的字段,可自定义。 + * + * 实际业务场景下,会存在一个经典的问题?当用户修改部门时,冗余的 dept_id 是否需要修改? + * 1. 一般情况下,dept_id 不进行修改,则会导致用户看不到之前的数据。【yudao-server 采用该方案】 + * 2. 部分情况下,希望该用户还是能看到之前的数据,则有两种方式解决:【需要你改造该 DeptDataPermissionRule 的实现代码】 + * 1)编写洗数据的脚本,将 dept_id 修改成新部门的编号;【建议】 + * 最终过滤条件是 WHERE dept_id = ? + * 2)洗数据的话,可能涉及的数据量较大,也可以采用 user_id 进行过滤的方式,此时需要获取到 dept_id 对应的所有 user_id 用户编号; + * 最终过滤条件是 WHERE user_id IN (?, ?, ? ...) + * 3)想要保证原 dept_id 和 user_id 都可以看的到,此时使用 dept_id 和 user_id 一起过滤; + * 最终过滤条件是 WHERE dept_id = ? OR user_id IN (?, ?, ? ...) + * + * @author 芋道源码 + */ +@AllArgsConstructor +@Slf4j +public class DeptDataPermissionRule implements DataPermissionRule { + + /** + * LoginUser 的 Context 缓存 Key + */ + protected static final String CONTEXT_KEY = DeptDataPermissionRule.class.getSimpleName(); + + private static final String DEPT_COLUMN_NAME = "dept_id"; + private static final String USER_COLUMN_NAME = "user_id"; + + static final Expression EXPRESSION_NULL = new NullValue(); + + private final PermissionApi permissionApi; + + /** + * 基于部门的表字段配置 + * 一般情况下,每个表的部门编号字段是 dept_id,通过该配置自定义。 + * + * key:表名 + * value:字段名 + */ + private final Map deptColumns = new HashMap<>(); + /** + * 基于用户的表字段配置 + * 一般情况下,每个表的部门编号字段是 dept_id,通过该配置自定义。 + * + * key:表名 + * value:字段名 + */ + private final Map userColumns = new HashMap<>(); + /** + * 所有表名,是 {@link #deptColumns} 和 {@link #userColumns} 的合集 + */ + private final Set TABLE_NAMES = new HashSet<>(); + + @Override + public Set getTableNames() { + return TABLE_NAMES; + } + + @Override + public Expression getExpression(String tableName, Alias tableAlias) { + // 只有有登陆用户的情况下,才进行数据权限的处理 + LoginUser loginUser = SecurityFrameworkUtils.getLoginUser(); + if (loginUser == null) { + return null; + } + // 只有管理员类型的用户,才进行数据权限的处理 + if (ObjectUtil.notEqual(loginUser.getUserType(), UserTypeEnum.ADMIN.getValue())) { + return null; + } + + // 获得数据权限 + DeptDataPermissionRespDTO deptDataPermission = loginUser.getContext(CONTEXT_KEY, DeptDataPermissionRespDTO.class); + // 从上下文中拿不到,则调用逻辑进行获取 + if (deptDataPermission == null) { + deptDataPermission = permissionApi.getDeptDataPermission(loginUser.getId()).getCheckedData(); + if (deptDataPermission == null) { + log.error("[getExpression][LoginUser({}) 获取数据权限为 null]", JsonUtils.toJsonString(loginUser)); + throw new NullPointerException(String.format("LoginUser(%d) Table(%s/%s) 未返回数据权限", + loginUser.getId(), tableName, tableAlias.getName())); + } + // 添加到上下文中,避免重复计算 + loginUser.setContext(CONTEXT_KEY, deptDataPermission); + } + + // 情况一,如果是 ALL 可查看全部,则无需拼接条件 + if (deptDataPermission.getAll()) { + return null; + } + + // 情况二,即不能查看部门,又不能查看自己,则说明 100% 无权限 + if (CollUtil.isEmpty(deptDataPermission.getDeptIds()) + && Boolean.FALSE.equals(deptDataPermission.getSelf())) { + return new EqualsTo(null, null); // WHERE null = null,可以保证返回的数据为空 + } + + // 情况三,拼接 Dept 和 User 的条件,最后组合 + Expression deptExpression = buildDeptExpression(tableName,tableAlias, deptDataPermission.getDeptIds()); + Expression userExpression = buildUserExpression(tableName, tableAlias, deptDataPermission.getSelf(), loginUser.getId()); + if (deptExpression == null && userExpression == null) { + // TODO 芋艿:获得不到条件的时候,暂时不抛出异常,而是不返回数据 + log.warn("[getExpression][LoginUser({}) Table({}/{}) DeptDataPermission({}) 构建的条件为空]", + JsonUtils.toJsonString(loginUser), tableName, tableAlias, JsonUtils.toJsonString(deptDataPermission)); +// throw new NullPointerException(String.format("LoginUser(%d) Table(%s/%s) 构建的条件为空", +// loginUser.getId(), tableName, tableAlias.getName())); + return EXPRESSION_NULL; + } + if (deptExpression == null) { + return userExpression; + } + if (userExpression == null) { + return deptExpression; + } + // 目前,如果有指定部门 + 可查看自己,采用 OR 条件。即,WHERE (dept_id IN ? OR user_id = ?) + return new ParenthesedExpressionList(new OrExpression(deptExpression, userExpression)); + } + + private Expression buildDeptExpression(String tableName, Alias tableAlias, Set deptIds) { + // 如果不存在配置,则无需作为条件 + String columnName = deptColumns.get(tableName); + if (StrUtil.isEmpty(columnName)) { + return null; + } + // 如果为空,则无条件 + if (CollUtil.isEmpty(deptIds)) { + return null; + } + // 拼接条件 + return new InExpression(MyBatisUtils.buildColumn(tableName, tableAlias, columnName), + // Parenthesis 的目的,是提供 (1,2,3) 的 () 左右括号 + new ParenthesedExpressionList(new ExpressionList(CollectionUtils.convertList(deptIds, LongValue::new)))); + } + + private Expression buildUserExpression(String tableName, Alias tableAlias, Boolean self, Long userId) { + // 如果不查看自己,则无需作为条件 + if (Boolean.FALSE.equals(self)) { + return null; + } + String columnName = userColumns.get(tableName); + if (StrUtil.isEmpty(columnName)) { + return null; + } + // 拼接条件 + return new EqualsTo(MyBatisUtils.buildColumn(tableName, tableAlias, columnName), new LongValue(userId)); + } + + // ==================== 添加配置 ==================== + + public void addDeptColumn(Class entityClass) { + addDeptColumn(entityClass, DEPT_COLUMN_NAME); + } + + public void addDeptColumn(Class entityClass, String columnName) { + String tableName = TableInfoHelper.getTableInfo(entityClass).getTableName(); + addDeptColumn(tableName, columnName); + } + + public void addDeptColumn(String tableName, String columnName) { + deptColumns.put(tableName, columnName); + TABLE_NAMES.add(tableName); + } + + public void addUserColumn(Class entityClass) { + addUserColumn(entityClass, USER_COLUMN_NAME); + } + + public void addUserColumn(Class entityClass, String columnName) { + String tableName = TableInfoHelper.getTableInfo(entityClass).getTableName(); + addUserColumn(tableName, columnName); + } + + public void addUserColumn(String tableName, String columnName) { + userColumns.put(tableName, columnName); + TABLE_NAMES.add(tableName); + } + +} diff --git a/tashow-framework/tashow-data-permission/src/main/java/com/tashow/cloud/permission/core/rule/dept/DeptDataPermissionRuleCustomizer.java b/tashow-framework/tashow-data-permission/src/main/java/com/tashow/cloud/permission/core/rule/dept/DeptDataPermissionRuleCustomizer.java new file mode 100644 index 0000000..c26f74e --- /dev/null +++ b/tashow-framework/tashow-data-permission/src/main/java/com/tashow/cloud/permission/core/rule/dept/DeptDataPermissionRuleCustomizer.java @@ -0,0 +1,20 @@ +package com.tashow.cloud.permission.core.rule.dept; + +/** + * {@link DeptDataPermissionRule} 的自定义配置接口 + * + * @author 芋道源码 + */ +@FunctionalInterface +public interface DeptDataPermissionRuleCustomizer { + + /** + * 自定义该权限规则 + * 1. 调用 {@link DeptDataPermissionRule#addDeptColumn(Class, String)} 方法,配置基于 dept_id 的过滤规则 + * 2. 调用 {@link DeptDataPermissionRule#addUserColumn(Class, String)} 方法,配置基于 user_id 的过滤规则 + * + * @param rule 权限规则 + */ + void customize(DeptDataPermissionRule rule); + +} diff --git a/tashow-framework/tashow-data-permission/src/main/java/com/tashow/cloud/permission/core/rule/dept/package-info.java b/tashow-framework/tashow-data-permission/src/main/java/com/tashow/cloud/permission/core/rule/dept/package-info.java new file mode 100644 index 0000000..d6579cb --- /dev/null +++ b/tashow-framework/tashow-data-permission/src/main/java/com/tashow/cloud/permission/core/rule/dept/package-info.java @@ -0,0 +1,6 @@ +/** + * 基于部门的数据权限规则 + * + * @author 芋道源码 + */ +package com.tashow.cloud.permission.core.rule.dept; diff --git a/tashow-framework/tashow-data-permission/src/main/java/com/tashow/cloud/permission/core/util/DataPermissionUtils.java b/tashow-framework/tashow-data-permission/src/main/java/com/tashow/cloud/permission/core/util/DataPermissionUtils.java new file mode 100644 index 0000000..9318a71 --- /dev/null +++ b/tashow-framework/tashow-data-permission/src/main/java/com/tashow/cloud/permission/core/util/DataPermissionUtils.java @@ -0,0 +1,63 @@ +package com.tashow.cloud.permission.core.util; + +import com.tashow.cloud.permission.core.annotation.DataPermission; +import com.tashow.cloud.permission.core.aop.DataPermissionContextHolder; +import lombok.SneakyThrows; + +import java.util.concurrent.Callable; + +/** + * 数据权限 Util + * + * @author 芋道源码 + */ +public class DataPermissionUtils { + + private static DataPermission DATA_PERMISSION_DISABLE; + + @DataPermission(enable = false) + @SneakyThrows + private static DataPermission getDisableDataPermissionDisable() { + if (DATA_PERMISSION_DISABLE == null) { + DATA_PERMISSION_DISABLE = DataPermissionUtils.class + .getDeclaredMethod("getDisableDataPermissionDisable") + .getAnnotation(DataPermission.class); + } + return DATA_PERMISSION_DISABLE; + } + + /** + * 忽略数据权限,执行对应的逻辑 + * + * @param runnable 逻辑 + */ + public static void executeIgnore(Runnable runnable) { + DataPermission dataPermission = getDisableDataPermissionDisable(); + DataPermissionContextHolder.add(dataPermission); + try { + // 执行 runnable + runnable.run(); + } finally { + DataPermissionContextHolder.remove(); + } + } + + /** + * 忽略数据权限,执行对应的逻辑 + * + * @param callable 逻辑 + * @return 执行结果 + */ + @SneakyThrows + public static T executeIgnore(Callable callable) { + DataPermission dataPermission = getDisableDataPermissionDisable(); + DataPermissionContextHolder.add(dataPermission); + try { + // 执行 callable + return callable.call(); + } finally { + DataPermissionContextHolder.remove(); + } + } + +} diff --git a/tashow-framework/tashow-data-permission/src/main/java/com/tashow/cloud/permission/package-info.java b/tashow-framework/tashow-data-permission/src/main/java/com/tashow/cloud/permission/package-info.java new file mode 100644 index 0000000..3ce447d --- /dev/null +++ b/tashow-framework/tashow-data-permission/src/main/java/com/tashow/cloud/permission/package-info.java @@ -0,0 +1,4 @@ +/** + * 基于 JSqlParser 解析 SQL,增加数据权限的 WHERE 条件 + */ +package com.tashow.cloud.permission; diff --git a/tashow-framework/tashow-data-permission/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/tashow-framework/tashow-data-permission/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports new file mode 100644 index 0000000..95e116a --- /dev/null +++ b/tashow-framework/tashow-data-permission/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -0,0 +1,3 @@ +com.tashow.cloud.permission.config.DataPermissionAutoConfiguration +com.tashow.cloud.permission.config.DeptDataPermissionAutoConfiguration +com.tashow.cloud.permission.config.DataPermissionRpcAutoConfiguration diff --git a/tashow-framework/tashow-data-redis/pom.xml b/tashow-framework/tashow-data-redis/pom.xml new file mode 100644 index 0000000..9eab3df --- /dev/null +++ b/tashow-framework/tashow-data-redis/pom.xml @@ -0,0 +1,40 @@ + + + 4.0.0 + + com.tashow.cloud + tashow-framework + ${revision} + + tashow-data-redis + jar + + ${project.artifactId} + Redis 封装拓展 + + + + com.tashow.cloud + tashow-common + + + + + org.redisson + redisson-spring-boot-starter + + + + org.springframework.boot + spring-boot-starter-cache + + + + com.fasterxml.jackson.datatype + jackson-datatype-jsr310 + + + + diff --git a/tashow-framework/tashow-data-redis/src/main/java/com/tashow/cloud/redis/config/TashowCacheAutoConfiguration.java b/tashow-framework/tashow-data-redis/src/main/java/com/tashow/cloud/redis/config/TashowCacheAutoConfiguration.java new file mode 100644 index 0000000..37c80f6 --- /dev/null +++ b/tashow-framework/tashow-data-redis/src/main/java/com/tashow/cloud/redis/config/TashowCacheAutoConfiguration.java @@ -0,0 +1,83 @@ +package com.tashow.cloud.redis.config; + +import cn.hutool.core.util.StrUtil; +import com.tashow.cloud.redis.core.TimeoutRedisCacheManager; +import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.boot.autoconfigure.cache.CacheProperties; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.cache.annotation.EnableCaching; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Primary; +import org.springframework.data.redis.cache.BatchStrategies; +import org.springframework.data.redis.cache.RedisCacheConfiguration; +import org.springframework.data.redis.cache.RedisCacheManager; +import org.springframework.data.redis.cache.RedisCacheWriter; +import org.springframework.data.redis.connection.RedisConnectionFactory; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.serializer.RedisSerializationContext; +import org.springframework.util.StringUtils; + +import java.util.Objects; + +import static com.tashow.cloud.redis.config.TashowRedisAutoConfiguration.buildRedisSerializer; + + +/** + * Cache 配置类,基于 Redis 实现 + */ +@AutoConfiguration +@EnableConfigurationProperties({CacheProperties.class, TashowCacheProperties.class}) +@EnableCaching +public class TashowCacheAutoConfiguration { + + /** + * RedisCacheConfiguration Bean + *

+ * 参考 org.springframework.boot.autoconfigure.cache.RedisCacheConfiguration 的 createConfiguration 方法 + */ + @Bean + @Primary + public RedisCacheConfiguration redisCacheConfiguration(org.springframework.boot.autoconfigure.cache.CacheProperties cacheProperties) { + RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig(); + // 设置使用 : 单冒号,而不是双 :: 冒号,避免 Redis Desktop Manager 多余空格 + // 详细可见 https://blog.csdn.net/chuixue24/article/details/103928965 博客 + // 再次修复单冒号,而不是双 :: 冒号问题,Issues 详情:https://gitee.com/zhijiantianya/yudao-cloud/issues/I86VY2 + config = config.computePrefixWith(cacheName -> { + String keyPrefix = cacheProperties.getRedis().getKeyPrefix(); + if (StringUtils.hasText(keyPrefix)) { + keyPrefix = keyPrefix.lastIndexOf(StrUtil.COLON) == -1 ? keyPrefix + StrUtil.COLON : keyPrefix; + return keyPrefix + cacheName + StrUtil.COLON; + } + return cacheName + StrUtil.COLON; + }); + // 设置使用 JSON 序列化方式 + config = config.serializeValuesWith( + RedisSerializationContext.SerializationPair.fromSerializer(buildRedisSerializer())); + + // 设置 CacheProperties.Redis 的属性 + org.springframework.boot.autoconfigure.cache.CacheProperties.Redis redisProperties = cacheProperties.getRedis(); + if (redisProperties.getTimeToLive() != null) { + config = config.entryTtl(redisProperties.getTimeToLive()); + } + if (!redisProperties.isCacheNullValues()) { + config = config.disableCachingNullValues(); + } + if (!redisProperties.isUseKeyPrefix()) { + config = config.disableKeyPrefix(); + } + return config; + } + + @Bean + public RedisCacheManager redisCacheManager(RedisTemplate redisTemplate, + RedisCacheConfiguration redisCacheConfiguration, + TashowCacheProperties tashowCacheProperties) { + // 创建 RedisCacheWriter 对象 + RedisConnectionFactory connectionFactory = Objects.requireNonNull(redisTemplate.getConnectionFactory()); + RedisCacheWriter cacheWriter = RedisCacheWriter.nonLockingRedisCacheWriter(connectionFactory, + BatchStrategies.scan(tashowCacheProperties.getRedisScanBatchSize())); + // 创建 TenantRedisCacheManager 对象 + return new TimeoutRedisCacheManager(cacheWriter, redisCacheConfiguration); + } + +} diff --git a/tashow-framework/tashow-data-redis/src/main/java/com/tashow/cloud/redis/config/TashowCacheProperties.java b/tashow-framework/tashow-data-redis/src/main/java/com/tashow/cloud/redis/config/TashowCacheProperties.java new file mode 100644 index 0000000..edfbf49 --- /dev/null +++ b/tashow-framework/tashow-data-redis/src/main/java/com/tashow/cloud/redis/config/TashowCacheProperties.java @@ -0,0 +1,27 @@ +package com.tashow.cloud.redis.config; + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.validation.annotation.Validated; + +/** + * Cache 配置项 + * + * @author Wanwan + */ +@ConfigurationProperties("tashow.cache") +@Data +@Validated +public class TashowCacheProperties { + + /** + * {@link #redisScanBatchSize} 默认值 + */ + private static final Integer REDIS_SCAN_BATCH_SIZE_DEFAULT = 30; + + /** + * redis scan 一次返回数量 + */ + private Integer redisScanBatchSize = REDIS_SCAN_BATCH_SIZE_DEFAULT; + +} diff --git a/tashow-framework/tashow-data-redis/src/main/java/com/tashow/cloud/redis/config/TashowRedisAutoConfiguration.java b/tashow-framework/tashow-data-redis/src/main/java/com/tashow/cloud/redis/config/TashowRedisAutoConfiguration.java new file mode 100644 index 0000000..116c615 --- /dev/null +++ b/tashow-framework/tashow-data-redis/src/main/java/com/tashow/cloud/redis/config/TashowRedisAutoConfiguration.java @@ -0,0 +1,45 @@ +package com.tashow.cloud.redis.config; + +import cn.hutool.core.util.ReflectUtil; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; +import org.redisson.spring.starter.RedissonAutoConfigurationV2; +import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.context.annotation.Bean; +import org.springframework.data.redis.connection.RedisConnectionFactory; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.serializer.RedisSerializer; + +/** + * Redis 配置类 + */ +@AutoConfiguration(before = RedissonAutoConfigurationV2.class) // 目的:使用自己定义的 RedisTemplate Bean +public class TashowRedisAutoConfiguration { + + /** + * 创建 RedisTemplate Bean,使用 JSON 序列化方式 + */ + @Bean + public RedisTemplate redisTemplate(RedisConnectionFactory factory) { + // 创建 RedisTemplate 对象 + RedisTemplate template = new RedisTemplate<>(); + // 设置 RedisConnection 工厂。😈 它就是实现多种 Java Redis 客户端接入的秘密工厂。感兴趣的胖友,可以自己去撸下。 + template.setConnectionFactory(factory); + // 使用 String 序列化方式,序列化 KEY 。 + template.setKeySerializer(RedisSerializer.string()); + template.setHashKeySerializer(RedisSerializer.string()); + // 使用 JSON 序列化方式(库是 Jackson ),序列化 VALUE 。 + template.setValueSerializer(buildRedisSerializer()); + template.setHashValueSerializer(buildRedisSerializer()); + return template; + } + + public static RedisSerializer buildRedisSerializer() { + RedisSerializer json = RedisSerializer.json(); + // 解决 LocalDateTime 的序列化 + ObjectMapper objectMapper = (ObjectMapper) ReflectUtil.getFieldValue(json, "mapper"); + objectMapper.registerModules(new JavaTimeModule()); + return json; + } + +} diff --git a/tashow-framework/tashow-data-redis/src/main/java/com/tashow/cloud/redis/core/TimeoutRedisCacheManager.java b/tashow-framework/tashow-data-redis/src/main/java/com/tashow/cloud/redis/core/TimeoutRedisCacheManager.java new file mode 100644 index 0000000..57280de --- /dev/null +++ b/tashow-framework/tashow-data-redis/src/main/java/com/tashow/cloud/redis/core/TimeoutRedisCacheManager.java @@ -0,0 +1,86 @@ +package com.tashow.cloud.redis.core; + +import cn.hutool.core.util.NumberUtil; +import cn.hutool.core.util.StrUtil; +import org.springframework.cache.annotation.Cacheable; +import org.springframework.data.redis.cache.RedisCache; +import org.springframework.data.redis.cache.RedisCacheConfiguration; +import org.springframework.data.redis.cache.RedisCacheManager; +import org.springframework.data.redis.cache.RedisCacheWriter; + +import java.time.Duration; + +/** + * 支持自定义过期时间的 {@link RedisCacheManager} 实现类 + * + * 在 {@link Cacheable#cacheNames()} 格式为 "key#ttl" 时,# 后面的 ttl 为过期时间。 + * 单位为最后一个字母(支持的单位有:d 天,h 小时,m 分钟,s 秒),默认单位为 s 秒 + * + * @author 芋道源码 + */ +public class TimeoutRedisCacheManager extends RedisCacheManager { + + private static final String SPLIT = "#"; + + public TimeoutRedisCacheManager(RedisCacheWriter cacheWriter, RedisCacheConfiguration defaultCacheConfiguration) { + super(cacheWriter, defaultCacheConfiguration); + } + + @Override + protected RedisCache createRedisCache(String name, RedisCacheConfiguration cacheConfig) { + if (StrUtil.isEmpty(name)) { + return super.createRedisCache(name, cacheConfig); + } + // 如果使用 # 分隔,大小不为 2,则说明不使用自定义过期时间 + String[] names = StrUtil.splitToArray(name, SPLIT); + if (names.length != 2) { + return super.createRedisCache(name, cacheConfig); + } + + // 核心:通过修改 cacheConfig 的过期时间,实现自定义过期时间 + if (cacheConfig != null) { + // 移除 # 后面的 : 以及后面的内容,避免影响解析 + String ttlStr = StrUtil.subBefore(names[1], StrUtil.COLON, false); // 获得 ttlStr 时间部分 + names[1] = StrUtil.subAfter(names[1], ttlStr, false); // 移除掉 ttlStr 时间部分 + // 解析时间 + Duration duration = parseDuration(ttlStr); + cacheConfig = cacheConfig.entryTtl(duration); + } + + // 创建 RedisCache 对象,需要忽略掉 ttlStr + return super.createRedisCache(names[0] + names[1], cacheConfig); + } + + /** + * 解析过期时间 Duration + * + * @param ttlStr 过期时间字符串 + * @return 过期时间 Duration + */ + private Duration parseDuration(String ttlStr) { + String timeUnit = StrUtil.subSuf(ttlStr, -1); + switch (timeUnit) { + case "d": + return Duration.ofDays(removeDurationSuffix(ttlStr)); + case "h": + return Duration.ofHours(removeDurationSuffix(ttlStr)); + case "m": + return Duration.ofMinutes(removeDurationSuffix(ttlStr)); + case "s": + return Duration.ofSeconds(removeDurationSuffix(ttlStr)); + default: + return Duration.ofSeconds(Long.parseLong(ttlStr)); + } + } + + /** + * 移除多余的后缀,返回具体的时间 + * + * @param ttlStr 过期时间字符串 + * @return 时间 + */ + private Long removeDurationSuffix(String ttlStr) { + return NumberUtil.parseLong(StrUtil.sub(ttlStr, 0, ttlStr.length() - 1)); + } + +} diff --git a/tashow-framework/tashow-data-redis/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/tashow-framework/tashow-data-redis/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports new file mode 100644 index 0000000..3b32e89 --- /dev/null +++ b/tashow-framework/tashow-data-redis/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -0,0 +1,2 @@ +com.tashow.cloud.redis.config.TashowRedisAutoConfiguration +com.tashow.cloud.redis.config.TashowCacheAutoConfiguration diff --git a/tashow-framework/tashow-framework-env/pom.xml b/tashow-framework/tashow-framework-env/pom.xml new file mode 100644 index 0000000..dde8f16 --- /dev/null +++ b/tashow-framework/tashow-framework-env/pom.xml @@ -0,0 +1,65 @@ + + + + com.tashow.cloud + tashow-framework + ${revision} + + 4.0.0 + tashow-framework-env + jar + + ${project.artifactId} + + 开发环境拓展,实现类似阿里的特性环境的能力 + 1. https://segmentfault.com/a/1190000018022987 + + + + 8 + 8 + + + + + com.tashow.cloud + tashow-common + + + + + org.springframework.boot + spring-boot-starter + + + + + org.springframework + spring-web + + + + jakarta.servlet + jakarta.servlet-api + + + + + org.springframework.cloud + spring-cloud-loadbalancer + + + io.github.openfeign + feign-core + + + + + com.alibaba.cloud + spring-cloud-starter-alibaba-nacos-discovery + + + + diff --git a/tashow-framework/tashow-framework-env/src/main/java/com/tashow/cloud/env/config/EnvEnvironmentPostProcessor.java b/tashow-framework/tashow-framework-env/src/main/java/com/tashow/cloud/env/config/EnvEnvironmentPostProcessor.java new file mode 100644 index 0000000..d9d1180 --- /dev/null +++ b/tashow-framework/tashow-framework-env/src/main/java/com/tashow/cloud/env/config/EnvEnvironmentPostProcessor.java @@ -0,0 +1,51 @@ +package com.tashow.cloud.env.config; + +import cn.hutool.core.util.StrUtil; +import com.tashow.cloud.common.util.collection.SetUtils; +import com.tashow.cloud.env.core.util.EnvUtils; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.env.EnvironmentPostProcessor; +import org.springframework.core.env.ConfigurableEnvironment; + +import java.util.Set; + +import static com.tashow.cloud.env.core.util.EnvUtils.HOST_NAME_VALUE; + + +/** + * 多环境的 {@link EnvEnvironmentPostProcessor} 实现类 + * 将 yudao.env.tag 设置到 nacos 等组件对应的 tag 配置项,当且仅当它们不存在时 + * + * @author 芋道源码 + */ +public class EnvEnvironmentPostProcessor implements EnvironmentPostProcessor { + + private static final Set TARGET_TAG_KEYS = SetUtils.asSet( + "spring.cloud.nacos.discovery.metadata.tag" // Nacos 注册中心 + // MQ TODO + ); + + @Override + public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) { + // 0. 设置 ${HOST_NAME} 兜底的环境变量 + String hostNameKey = StrUtil.subBetween(HOST_NAME_VALUE, "{", "}"); + if (!environment.containsProperty(hostNameKey)) { + environment.getSystemProperties().put(hostNameKey, EnvUtils.getHostName()); + } + + // 1.1 如果没有 yudao.env.tag 配置项,则不进行配置项的修改 + String tag = EnvUtils.getTag(environment); + if (StrUtil.isEmpty(tag)) { + return; + } + // 1.2 需要修改的配置项 + for (String targetTagKey : TARGET_TAG_KEYS) { + String targetTagValue = environment.getProperty(targetTagKey); + if (StrUtil.isNotEmpty(targetTagValue)) { + continue; + } + environment.getSystemProperties().put(targetTagKey, tag); + } + } + +} diff --git a/tashow-framework/tashow-framework-env/src/main/java/com/tashow/cloud/env/config/EnvProperties.java b/tashow-framework/tashow-framework-env/src/main/java/com/tashow/cloud/env/config/EnvProperties.java new file mode 100644 index 0000000..a49755f --- /dev/null +++ b/tashow-framework/tashow-framework-env/src/main/java/com/tashow/cloud/env/config/EnvProperties.java @@ -0,0 +1,22 @@ +package com.tashow.cloud.env.config; + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; + +/** + * 环境配置 + * + * @author 芋道源码 + */ +@ConfigurationProperties(prefix = "tashow.env") +@Data +public class EnvProperties { + + public static final String TAG_KEY = "tashow.env.tag"; + + /** + * 环境标签 + */ + private String tag; + +} diff --git a/tashow-framework/tashow-framework-env/src/main/java/com/tashow/cloud/env/config/EnvRpcAutoConfiguration.java b/tashow-framework/tashow-framework-env/src/main/java/com/tashow/cloud/env/config/EnvRpcAutoConfiguration.java new file mode 100644 index 0000000..dd9ed4f --- /dev/null +++ b/tashow-framework/tashow-framework-env/src/main/java/com/tashow/cloud/env/config/EnvRpcAutoConfiguration.java @@ -0,0 +1,46 @@ +package com.tashow.cloud.env.config; + +import com.tashow.cloud.env.core.fegin.EnvLoadBalancerClientFactory; +import com.tashow.cloud.env.core.fegin.EnvRequestInterceptor; +import org.springframework.beans.factory.ObjectProvider; +import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.cloud.client.loadbalancer.LoadBalancerClientsProperties; +import org.springframework.cloud.loadbalancer.annotation.LoadBalancerClientSpecification; +import org.springframework.cloud.loadbalancer.config.LoadBalancerAutoConfiguration; +import org.springframework.cloud.loadbalancer.support.LoadBalancerClientFactory; +import org.springframework.context.annotation.Bean; + +import java.util.Collections; +import java.util.List; + +/** + * 多环境的 RPC 组件的自动配置 + * + * @author 芋道源码 + */ +@AutoConfiguration +@EnableConfigurationProperties(EnvProperties.class) +public class EnvRpcAutoConfiguration { + + // ========== Feign 相关 ========== + + /** + * 创建 {@link EnvLoadBalancerClientFactory} Bean + * + * 参考 {@link LoadBalancerAutoConfiguration#loadBalancerClientFactory(LoadBalancerClientsProperties)} 方法 + */ + @Bean + public LoadBalancerClientFactory loadBalancerClientFactory(LoadBalancerClientsProperties properties, + ObjectProvider> configurations) { + EnvLoadBalancerClientFactory clientFactory = new EnvLoadBalancerClientFactory(properties); + clientFactory.setConfigurations(configurations.getIfAvailable(Collections::emptyList)); + return clientFactory; + } + + @Bean + public EnvRequestInterceptor envRequestInterceptor() { + return new EnvRequestInterceptor(); + } + +} diff --git a/tashow-framework/tashow-framework-env/src/main/java/com/tashow/cloud/env/config/EnvWebAutoConfiguration.java b/tashow-framework/tashow-framework-env/src/main/java/com/tashow/cloud/env/config/EnvWebAutoConfiguration.java new file mode 100644 index 0000000..53560e3 --- /dev/null +++ b/tashow-framework/tashow-framework-env/src/main/java/com/tashow/cloud/env/config/EnvWebAutoConfiguration.java @@ -0,0 +1,32 @@ +package com.tashow.cloud.env.config; + +import com.tashow.cloud.common.enums.WebFilterOrderEnum; +import com.tashow.cloud.env.core.web.EnvWebFilter; +import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.boot.web.servlet.FilterRegistrationBean; +import org.springframework.context.annotation.Bean; + +/** + * 多环境的 Web 组件的自动配置 + * + * @author 芋道源码 + */ +@AutoConfiguration +@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET) +@EnableConfigurationProperties(EnvProperties.class) +public class EnvWebAutoConfiguration { + + /** + * 创建 {@link EnvWebFilter} Bean + */ + @Bean + public FilterRegistrationBean envWebFilterFilter() { + EnvWebFilter filter = new EnvWebFilter(); + FilterRegistrationBean bean = new FilterRegistrationBean<>(filter); + bean.setOrder(WebFilterOrderEnum.ENV_TAG_FILTER); + return bean; + } + +} diff --git a/tashow-framework/tashow-framework-env/src/main/java/com/tashow/cloud/env/core/context/EnvContextHolder.java b/tashow-framework/tashow-framework-env/src/main/java/com/tashow/cloud/env/core/context/EnvContextHolder.java new file mode 100644 index 0000000..40f1565 --- /dev/null +++ b/tashow-framework/tashow-framework-env/src/main/java/com/tashow/cloud/env/core/context/EnvContextHolder.java @@ -0,0 +1,39 @@ +package com.tashow.cloud.env.core.context; + +import cn.hutool.core.collection.CollUtil; +import com.alibaba.ttl.TransmittableThreadLocal; + +import java.util.ArrayList; +import java.util.List; + +/** + * 开发环境上下文 + * + * @author 芋道源码 + */ +public class EnvContextHolder { + + /** + * 标签的上下文 + * + * 使用 {@link List} 的原因,可能存在多层设置或者清理 + */ + private static final ThreadLocal> TAG_CONTEXT = TransmittableThreadLocal.withInitial(ArrayList::new); + + public static void setTag(String tag) { + TAG_CONTEXT.get().add(tag); + } + + public static String getTag() { + return CollUtil.getLast(TAG_CONTEXT.get()); + } + + public static void removeTag() { + List tags = TAG_CONTEXT.get(); + if (CollUtil.isEmpty(tags)) { + return; + } + tags.remove(tags.size() - 1); + } + +} diff --git a/tashow-framework/tashow-framework-env/src/main/java/com/tashow/cloud/env/core/fegin/EnvLoadBalancerClient.java b/tashow-framework/tashow-framework-env/src/main/java/com/tashow/cloud/env/core/fegin/EnvLoadBalancerClient.java new file mode 100644 index 0000000..82d9552 --- /dev/null +++ b/tashow-framework/tashow-framework-env/src/main/java/com/tashow/cloud/env/core/fegin/EnvLoadBalancerClient.java @@ -0,0 +1,83 @@ +package com.tashow.cloud.env.core.fegin; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.util.StrUtil; +import com.alibaba.cloud.nacos.balancer.NacosBalancer; +import com.tashow.cloud.common.util.collection.CollectionUtils; +import com.tashow.cloud.env.core.context.EnvContextHolder; +import com.tashow.cloud.env.core.util.EnvUtils; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.ObjectProvider; +import org.springframework.cloud.client.ServiceInstance; +import org.springframework.cloud.client.loadbalancer.DefaultResponse; +import org.springframework.cloud.client.loadbalancer.EmptyResponse; +import org.springframework.cloud.client.loadbalancer.Request; +import org.springframework.cloud.client.loadbalancer.Response; +import org.springframework.cloud.client.loadbalancer.reactive.ReactiveLoadBalancer; +import org.springframework.cloud.loadbalancer.core.NoopServiceInstanceListSupplier; +import org.springframework.cloud.loadbalancer.core.ReactorServiceInstanceLoadBalancer; +import org.springframework.cloud.loadbalancer.core.ServiceInstanceListSupplier; +import reactor.core.publisher.Mono; + +import java.util.List; + +/** + * 多环境的 {@link org.springframework.cloud.client.loadbalancer.LoadBalancerClient} 实现类 + * 在从服务实例列表选择时,优先选择 tag 匹配的服务实例 + * + * @author 芋道源码 + */ +@RequiredArgsConstructor +@Slf4j +public class EnvLoadBalancerClient implements ReactorServiceInstanceLoadBalancer { + + /** + * 用于获取 serviceId 对应的服务实例的列表 + */ + private final ObjectProvider serviceInstanceListSupplierProvider; + /** + * 需要获取的服务实例名 + * + * 暂时用于打印 logger 日志 + */ + private final String serviceId; + /** + * 被代理的 ReactiveLoadBalancer 对象 + */ + private final ReactiveLoadBalancer reactiveLoadBalancer; + + @Override + public Mono> choose(Request request) { + // 情况一,没有 tag 时,使用默认的 reactiveLoadBalancer 实现负载均衡 + String tag = EnvContextHolder.getTag(); + if (StrUtil.isEmpty(tag)) { + return Mono.from(reactiveLoadBalancer.choose(request)); + } + + // 情况二,有 tag 时,使用 tag 匹配服务实例 + ServiceInstanceListSupplier supplier = serviceInstanceListSupplierProvider.getIfAvailable(NoopServiceInstanceListSupplier::new); + return supplier.get(request).next().map(list -> getInstanceResponse(list, tag)); + } + + private Response getInstanceResponse(List instances, String tag) { + // 如果服务实例为空,则直接返回 + if (CollUtil.isEmpty(instances)) { + log.warn("[getInstanceResponse][serviceId({}) 服务实例列表为空]", serviceId); + return new EmptyResponse(); + } + + // 筛选满足条件的实例列表 + List chooseInstances = CollectionUtils.filterList(instances, instance -> tag.equals(EnvUtils.getTag(instance))); + if (CollUtil.isEmpty(chooseInstances)) { + log.warn("[getInstanceResponse][serviceId({}) 没有满足 tag({}) 的服务实例列表,直接使用所有服务实例列表]", serviceId, tag); + chooseInstances = instances; + } + + // TODO 芋艿:https://juejin.cn/post/7056770721858469896 想通网段 + + // 随机 + 权重获取实例列表 TODO 芋艿:目前直接使用 Nacos 提供的方法,如果替换注册中心,需要重新失败该方法 + return new DefaultResponse(NacosBalancer.getHostByRandomWeight3(chooseInstances)); + } + +} diff --git a/tashow-framework/tashow-framework-env/src/main/java/com/tashow/cloud/env/core/fegin/EnvLoadBalancerClientFactory.java b/tashow-framework/tashow-framework-env/src/main/java/com/tashow/cloud/env/core/fegin/EnvLoadBalancerClientFactory.java new file mode 100644 index 0000000..cb879ce --- /dev/null +++ b/tashow-framework/tashow-framework-env/src/main/java/com/tashow/cloud/env/core/fegin/EnvLoadBalancerClientFactory.java @@ -0,0 +1,30 @@ +package com.tashow.cloud.env.core.fegin; + + +import org.springframework.cloud.client.ServiceInstance; +import org.springframework.cloud.client.loadbalancer.LoadBalancerClientsProperties; +import org.springframework.cloud.client.loadbalancer.reactive.ReactiveLoadBalancer; +import org.springframework.cloud.loadbalancer.core.ServiceInstanceListSupplier; +import org.springframework.cloud.loadbalancer.support.LoadBalancerClientFactory; + +/** + * 多环境的 {@link LoadBalancerClientFactory} 实现类 + * 目的:在创建 {@link ReactiveLoadBalancer} 时,会额外增加 {@link EnvLoadBalancerClient} 代理,用于 tag 过滤服务实例 + * + * @author 芋道源码 + */ +public class EnvLoadBalancerClientFactory extends LoadBalancerClientFactory { + + public EnvLoadBalancerClientFactory(LoadBalancerClientsProperties properties) { + super(properties); + } + + @Override + public ReactiveLoadBalancer getInstance(String serviceId) { + ReactiveLoadBalancer reactiveLoadBalancer = super.getInstance(serviceId); + // 参考 {@link com.alibaba.cloud.nacos.loadbalancer.NacosLoadBalancerClientConfiguration#nacosLoadBalancer(Environment, LoadBalancerClientFactory, NacosDiscoveryProperties)} 方法 + return new EnvLoadBalancerClient(super.getLazyProvider(serviceId, ServiceInstanceListSupplier.class), + serviceId, reactiveLoadBalancer); + } + +} diff --git a/tashow-framework/tashow-framework-env/src/main/java/com/tashow/cloud/env/core/fegin/EnvRequestInterceptor.java b/tashow-framework/tashow-framework-env/src/main/java/com/tashow/cloud/env/core/fegin/EnvRequestInterceptor.java new file mode 100644 index 0000000..4cd461d --- /dev/null +++ b/tashow-framework/tashow-framework-env/src/main/java/com/tashow/cloud/env/core/fegin/EnvRequestInterceptor.java @@ -0,0 +1,24 @@ +package com.tashow.cloud.env.core.fegin; + +import cn.hutool.core.util.StrUtil; +import com.tashow.cloud.env.core.context.EnvContextHolder; +import com.tashow.cloud.env.core.util.EnvUtils; +import feign.RequestInterceptor; +import feign.RequestTemplate; + +/** + * 多环境的 {@link RequestInterceptor} 实现类:Feign 请求时,将 tag 设置到 header 中,继续透传给被调用的服务 + * + * @author 芋道源码 + */ +public class EnvRequestInterceptor implements RequestInterceptor { + + @Override + public void apply(RequestTemplate requestTemplate) { + String tag = EnvContextHolder.getTag(); + if (StrUtil.isNotEmpty(tag)) { + EnvUtils.setTag(requestTemplate, tag); + } + } + +} diff --git a/tashow-framework/tashow-framework-env/src/main/java/com/tashow/cloud/env/core/package-info.java b/tashow-framework/tashow-framework-env/src/main/java/com/tashow/cloud/env/core/package-info.java new file mode 100644 index 0000000..ee6717d --- /dev/null +++ b/tashow-framework/tashow-framework-env/src/main/java/com/tashow/cloud/env/core/package-info.java @@ -0,0 +1 @@ +package com.tashow.cloud.env.core; diff --git a/tashow-framework/tashow-framework-env/src/main/java/com/tashow/cloud/env/core/util/EnvUtils.java b/tashow-framework/tashow-framework-env/src/main/java/com/tashow/cloud/env/core/util/EnvUtils.java new file mode 100644 index 0000000..8aebcec --- /dev/null +++ b/tashow-framework/tashow-framework-env/src/main/java/com/tashow/cloud/env/core/util/EnvUtils.java @@ -0,0 +1,56 @@ +package com.tashow.cloud.env.core.util; + +import com.tashow.cloud.env.config.EnvProperties; +import feign.RequestTemplate; +import jakarta.servlet.http.HttpServletRequest; +import lombok.SneakyThrows; +import org.springframework.cloud.client.ServiceInstance; +import org.springframework.core.env.Environment; + +import java.net.InetAddress; +import java.util.Objects; + +/** + * 环境 Utils + * + * @author 芋道源码 + */ +public class EnvUtils { + + private static final String HEADER_TAG = "tag"; + + public static final String HOST_NAME_VALUE = "${HOSTNAME}"; + + public static String getTag(HttpServletRequest request) { + String tag = request.getHeader(HEADER_TAG); + // 如果请求的是 "${HOSTNAME}",则解析成对应的本地主机名 + // 目的:特殊逻辑,解决 IDEA Rest Client 不支持环境变量的读取,所以就服务器来做 + return Objects.equals(tag, HOST_NAME_VALUE) ? getHostName() : tag; + } + + public static String getTag(ServiceInstance instance) { + return instance.getMetadata().get(HEADER_TAG); + } + + public static String getTag(Environment environment) { + String tag = environment.getProperty(EnvProperties.TAG_KEY); + // 如果请求的是 "${HOSTNAME}",则解析成对应的本地主机名 + // 目的:特殊逻辑,解决 IDEA Rest Client 不支持环境变量的读取,所以就服务器来做 + return Objects.equals(tag, HOST_NAME_VALUE) ? getHostName() : tag; + } + + public static void setTag(RequestTemplate requestTemplate, String tag) { + requestTemplate.header(HEADER_TAG, tag); + } + + /** + * 获得 hostname 主机名 + * + * @return 主机名 + */ + @SneakyThrows + public static String getHostName() { + return InetAddress.getLocalHost().getHostName(); + } + +} diff --git a/tashow-framework/tashow-framework-env/src/main/java/com/tashow/cloud/env/core/web/EnvWebFilter.java b/tashow-framework/tashow-framework-env/src/main/java/com/tashow/cloud/env/core/web/EnvWebFilter.java new file mode 100644 index 0000000..31fa82e --- /dev/null +++ b/tashow-framework/tashow-framework-env/src/main/java/com/tashow/cloud/env/core/web/EnvWebFilter.java @@ -0,0 +1,41 @@ +package com.tashow.cloud.env.core.web; + +import cn.hutool.core.util.StrUtil; +import com.tashow.cloud.env.core.context.EnvContextHolder; +import com.tashow.cloud.env.core.util.EnvUtils; +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.springframework.web.filter.OncePerRequestFilter; + +import java.io.IOException; + +/** + * 环境的 {@link jakarta.servlet.Filter} 实现类 + * 当有 tag 请求头时,设置到 {@link EnvContextHolder} 的标签上下文 + * + * @author 芋道源码 + */ +public class EnvWebFilter extends OncePerRequestFilter { + + @Override + protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) + throws ServletException, IOException { + // 如果没有 tag,则走默认的流程 + String tag = EnvUtils.getTag(request); + if (StrUtil.isEmpty(tag)) { + chain.doFilter(request, response); + return; + } + + // 如果有 tag,则设置到上下文 + EnvContextHolder.setTag(tag); + try { + chain.doFilter(request, response); + } finally { + EnvContextHolder.removeTag(); + } + } + +} diff --git a/tashow-framework/tashow-framework-env/src/main/resources/META-INF/spring.factories b/tashow-framework/tashow-framework-env/src/main/resources/META-INF/spring.factories new file mode 100644 index 0000000..7178c49 --- /dev/null +++ b/tashow-framework/tashow-framework-env/src/main/resources/META-INF/spring.factories @@ -0,0 +1,2 @@ +org.springframework.boot.env.EnvironmentPostProcessor=\ + com.tashow.cloud.env.config.EnvEnvironmentPostProcessor diff --git a/tashow-framework/tashow-framework-env/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/tashow-framework/tashow-framework-env/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports new file mode 100644 index 0000000..8858e5b --- /dev/null +++ b/tashow-framework/tashow-framework-env/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -0,0 +1,2 @@ +com.tashow.cloud.env.config.EnvWebAutoConfiguration +com.tashow.cloud.env.config.EnvRpcAutoConfiguration diff --git a/tashow-framework/tashow-framework-job/pom.xml b/tashow-framework/tashow-framework-job/pom.xml new file mode 100644 index 0000000..8e2663b --- /dev/null +++ b/tashow-framework/tashow-framework-job/pom.xml @@ -0,0 +1,49 @@ + + + 4.0.0 + + com.tashow.cloud + tashow-framework + ${revision} + + tashow-framework-job + jar + + ${project.artifactId} + 任务拓展,基于 XXL-Job 实现 + + + + com.tashow.cloud + tashow-common + + + + + org.springframework.boot + spring-boot-configuration-processor + true + + + org.springframework.boot + spring-boot-starter + true + + + + + com.xuxueli + xxl-job-core + + + + + jakarta.validation + jakarta.validation-api + + + + + diff --git a/tashow-framework/tashow-framework-job/src/main/java/com/tashow/cloud/quartz/config/AsyncAutoConfiguration.java b/tashow-framework/tashow-framework-job/src/main/java/com/tashow/cloud/quartz/config/AsyncAutoConfiguration.java new file mode 100644 index 0000000..5aea1a8 --- /dev/null +++ b/tashow-framework/tashow-framework-job/src/main/java/com/tashow/cloud/quartz/config/AsyncAutoConfiguration.java @@ -0,0 +1,36 @@ +package com.tashow.cloud.quartz.config; + +import com.alibaba.ttl.TtlRunnable; +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.config.BeanPostProcessor; +import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.context.annotation.Bean; +import org.springframework.scheduling.annotation.EnableAsync; +import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; + +/** + * 异步任务 Configuration + */ +@AutoConfiguration +@EnableAsync +public class AsyncAutoConfiguration { + + @Bean + public BeanPostProcessor threadPoolTaskExecutorBeanPostProcessor() { + return new BeanPostProcessor() { + + @Override + public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { + if (!(bean instanceof ThreadPoolTaskExecutor)) { + return bean; + } + // 修改提交的任务,接入 TransmittableThreadLocal + ThreadPoolTaskExecutor executor = (ThreadPoolTaskExecutor) bean; + executor.setTaskDecorator(TtlRunnable::get); + return executor; + } + + }; + } + +} diff --git a/tashow-framework/tashow-framework-job/src/main/java/com/tashow/cloud/quartz/config/XxlJobAutoConfiguration.java b/tashow-framework/tashow-framework-job/src/main/java/com/tashow/cloud/quartz/config/XxlJobAutoConfiguration.java new file mode 100644 index 0000000..8c6b9b6 --- /dev/null +++ b/tashow-framework/tashow-framework-job/src/main/java/com/tashow/cloud/quartz/config/XxlJobAutoConfiguration.java @@ -0,0 +1,46 @@ +package com.tashow.cloud.quartz.config; + +import com.xxl.job.core.executor.XxlJobExecutor; +import com.xxl.job.core.executor.impl.XxlJobSpringExecutor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.scheduling.annotation.EnableScheduling; + +/** + * XXL-Job 自动配置类 + * + * @author 芋道源码 + */ +@AutoConfiguration +@ConditionalOnClass(XxlJobSpringExecutor.class) +@ConditionalOnProperty(prefix = "xxl.job", name = "enabled", havingValue = "true", matchIfMissing = true) +@EnableConfigurationProperties({XxlJobProperties.class}) +@EnableScheduling // 开启 Spring 自带的定时任务 +@Slf4j +public class XxlJobAutoConfiguration { + + @Bean + @ConditionalOnMissingBean + public XxlJobExecutor xxlJobExecutor(XxlJobProperties properties) { + log.info("[xxlJobExecutor][初始化 XXL-Job 执行器的配置]"); + XxlJobProperties.AdminProperties admin = properties.getAdmin(); + XxlJobProperties.ExecutorProperties executor = properties.getExecutor(); + + // 初始化执行器 + XxlJobExecutor xxlJobExecutor = new XxlJobSpringExecutor(); + xxlJobExecutor.setIp(executor.getIp()); + xxlJobExecutor.setPort(executor.getPort()); + xxlJobExecutor.setAppname(executor.getAppName()); + xxlJobExecutor.setLogPath(executor.getLogPath()); + xxlJobExecutor.setLogRetentionDays(executor.getLogRetentionDays()); + xxlJobExecutor.setAdminAddresses(admin.getAddresses()); + xxlJobExecutor.setAccessToken(properties.getAccessToken()); + return xxlJobExecutor; + } + +} diff --git a/tashow-framework/tashow-framework-job/src/main/java/com/tashow/cloud/quartz/config/XxlJobProperties.java b/tashow-framework/tashow-framework-job/src/main/java/com/tashow/cloud/quartz/config/XxlJobProperties.java new file mode 100644 index 0000000..1fc7157 --- /dev/null +++ b/tashow-framework/tashow-framework-job/src/main/java/com/tashow/cloud/quartz/config/XxlJobProperties.java @@ -0,0 +1,98 @@ +package com.tashow.cloud.quartz.config; + +import jakarta.validation.Valid; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.validation.annotation.Validated; + +/** + * XXL-Job 配置类 + */ +@ConfigurationProperties("xxl.job") +@Validated +@Data +public class XxlJobProperties { + + /** + * 是否开启,默认为 true 关闭 + */ + private Boolean enabled = true; + /** + * 访问令牌 + */ + private String accessToken; + /** + * 控制器配置 + */ + @NotNull(message = "控制器配置不能为空") + private AdminProperties admin; + /** + * 执行器配置 + */ + @NotNull(message = "执行器配置不能为空") + private ExecutorProperties executor; + + /** + * XXL-Job 调度器配置类 + */ + @Data + @Valid + public static class AdminProperties { + + /** + * 调度器地址 + */ + @NotEmpty(message = "调度器地址不能为空") + private String addresses; + + } + + /** + * XXL-Job 执行器配置类 + */ + @Data + @Valid + public static class ExecutorProperties { + + /** + * 默认端口 + * + * 这里使用 -1 表示随机 + */ + private static final Integer PORT_DEFAULT = -1; + + /** + * 默认日志保留天数 + * + * 如果想永久保留,则设置为 -1 + */ + private static final Integer LOG_RETENTION_DAYS_DEFAULT = 30; + + /** + * 应用名 + */ + @NotEmpty(message = "应用名不能为空") + private String appName; + /** + * 执行器的 IP + */ + private String ip; + /** + * 执行器的 Port + */ + private Integer port = PORT_DEFAULT; + /** + * 日志地址 + */ + @NotEmpty(message = "日志地址不能为空") + private String logPath; + /** + * 日志保留天数 + */ + private Integer logRetentionDays = LOG_RETENTION_DAYS_DEFAULT; + + } + +} diff --git a/tashow-framework/tashow-framework-job/src/main/java/com/tashow/cloud/quartz/package-info.java b/tashow-framework/tashow-framework-job/src/main/java/com/tashow/cloud/quartz/package-info.java new file mode 100644 index 0000000..60b8974 --- /dev/null +++ b/tashow-framework/tashow-framework-job/src/main/java/com/tashow/cloud/quartz/package-info.java @@ -0,0 +1,5 @@ +/** + * 1. 定时任务,基于 XXL-Job 实现。 + * 2. 异步任务,采用 Spring Async 异步执行。 + */ +package com.tashow.cloud.quartz; diff --git a/tashow-framework/tashow-framework-job/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/tashow-framework/tashow-framework-job/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports new file mode 100644 index 0000000..d4b951c --- /dev/null +++ b/tashow-framework/tashow-framework-job/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -0,0 +1,2 @@ +com.tashow.cloud.quartz.config.XxlJobAutoConfiguration +com.tashow.cloud.quartz.config.AsyncAutoConfiguration diff --git a/tashow-framework/tashow-framework-monitor/pom.xml b/tashow-framework/tashow-framework-monitor/pom.xml new file mode 100644 index 0000000..4e6f0ec --- /dev/null +++ b/tashow-framework/tashow-framework-monitor/pom.xml @@ -0,0 +1,72 @@ + + + 4.0.0 + + com.tashow.cloud + tashow-framework + ${revision} + + tashow-framework-monitor + jar + + ${project.artifactId} + 服务监控,提供链路追踪、日志服务、指标收集等等功能 + + + + com.tashow.cloud + tashow-common + + + + + org.springframework.boot + spring-boot-starter-aop + + + + + org.springframework + spring-web + provided + + + + jakarta.servlet + jakarta.servlet-api + provided + + + + + io.opentracing + opentracing-util + + + org.apache.skywalking + apm-toolkit-trace + + + org.apache.skywalking + apm-toolkit-logback-1.x + + + org.apache.skywalking + apm-toolkit-opentracing + + + + + io.micrometer + micrometer-registry-prometheus + + + + de.codecentric + spring-boot-admin-starter-client + + + + diff --git a/tashow-framework/tashow-framework-monitor/src/main/java/com/tashow/cloud/monitor/config/MetricsAutoConfiguration.java b/tashow-framework/tashow-framework-monitor/src/main/java/com/tashow/cloud/monitor/config/MetricsAutoConfiguration.java new file mode 100644 index 0000000..d2fde41 --- /dev/null +++ b/tashow-framework/tashow-framework-monitor/src/main/java/com/tashow/cloud/monitor/config/MetricsAutoConfiguration.java @@ -0,0 +1,27 @@ +package com.tashow.cloud.monitor.config; + +import io.micrometer.core.instrument.MeterRegistry; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.actuate.autoconfigure.metrics.MeterRegistryCustomizer; +import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.context.annotation.Bean; + +/** + * Metrics 配置类 + * + * @author 芋道源码 + */ +@AutoConfiguration +@ConditionalOnClass({MeterRegistryCustomizer.class}) +@ConditionalOnProperty(prefix = "tashow.metrics", value = "enable", matchIfMissing = true) // 允许使用 yudao.metrics.enable=false 禁用 Metrics +public class MetricsAutoConfiguration { + + @Bean + public MeterRegistryCustomizer metricsCommonTags( + @Value("${spring.application.name}") String applicationName) { + return registry -> registry.config().commonTags("application", applicationName); + } + +} diff --git a/tashow-framework/tashow-framework-monitor/src/main/java/com/tashow/cloud/monitor/config/TracerAutoConfiguration.java b/tashow-framework/tashow-framework-monitor/src/main/java/com/tashow/cloud/monitor/config/TracerAutoConfiguration.java new file mode 100644 index 0000000..f013466 --- /dev/null +++ b/tashow-framework/tashow-framework-monitor/src/main/java/com/tashow/cloud/monitor/config/TracerAutoConfiguration.java @@ -0,0 +1,55 @@ +package com.tashow.cloud.monitor.config; + +import com.tashow.cloud.common.enums.WebFilterOrderEnum; +import com.tashow.cloud.monitor.core.aop.BizTraceAspect; +import com.tashow.cloud.monitor.core.filter.TraceFilter; +import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.boot.web.servlet.FilterRegistrationBean; +import org.springframework.context.annotation.Bean; + +/** + * Tracer 配置类 + * + * @author mashu + */ +@AutoConfiguration +@ConditionalOnClass(value = {BizTraceAspect.class}, name = "jakarta.servlet.Filter") +@EnableConfigurationProperties(TracerProperties.class) +@ConditionalOnProperty(prefix = "tashow.tracer", value = "enable", matchIfMissing = true) +public class TracerAutoConfiguration { + + // TODO @芋艿:重要。目前 opentracing 版本存在冲突,要么保证 skywalking,要么保证阿里云短信 sdk +// @Bean +// public TracerProperties bizTracerProperties() { +// return new TracerProperties(); +// } +// +// @Bean +// public BizTraceAspect bizTracingAop() { +// return new BizTraceAspect(tracer()); +// } +// +// @Bean +// public Tracer tracer() { +// // 创建 SkywalkingTracer 对象 +// SkywalkingTracer tracer = new SkywalkingTracer(); +// // 设置为 GlobalTracer 的追踪器 +// GlobalTracer.register(tracer); +// return tracer; +// } + + /** + * 创建 TraceFilter 过滤器,响应 header 设置 traceId + */ + @Bean + public FilterRegistrationBean traceFilter() { + FilterRegistrationBean registrationBean = new FilterRegistrationBean<>(); + registrationBean.setFilter(new TraceFilter()); + registrationBean.setOrder(WebFilterOrderEnum.TRACE_FILTER); + return registrationBean; + } + +} diff --git a/tashow-framework/tashow-framework-monitor/src/main/java/com/tashow/cloud/monitor/config/TracerProperties.java b/tashow-framework/tashow-framework-monitor/src/main/java/com/tashow/cloud/monitor/config/TracerProperties.java new file mode 100644 index 0000000..ebdbe3d --- /dev/null +++ b/tashow-framework/tashow-framework-monitor/src/main/java/com/tashow/cloud/monitor/config/TracerProperties.java @@ -0,0 +1,14 @@ +package com.tashow.cloud.monitor.config; + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; + +/** + * BizTracer配置类 + * + * @author 麻薯 + */ +@ConfigurationProperties("tashow.tracer") +@Data +public class TracerProperties { +} diff --git a/tashow-framework/tashow-framework-monitor/src/main/java/com/tashow/cloud/monitor/core/annotation/BizTrace.java b/tashow-framework/tashow-framework-monitor/src/main/java/com/tashow/cloud/monitor/core/annotation/BizTrace.java new file mode 100644 index 0000000..97662ba --- /dev/null +++ b/tashow-framework/tashow-framework-monitor/src/main/java/com/tashow/cloud/monitor/core/annotation/BizTrace.java @@ -0,0 +1,42 @@ +package com.tashow.cloud.monitor.core.annotation; + +import java.lang.annotation.*; + +/** + * 打印业务编号 / 业务类型注解 + * + * 使用时,需要设置 SkyWalking OAP Server 的 application.yaml 配置文件,修改 SW_SEARCHABLE_TAG_KEYS 配置项, + * 增加 biz.type 和 biz.id 两值,然后重启 SkyWalking OAP Server 服务器。 + * + * @author 麻薯 + */ +@Target({ElementType.METHOD}) +@Retention(RetentionPolicy.RUNTIME) +@Inherited +public @interface BizTrace { + + /** + * 业务编号 tag 名 + */ + String ID_TAG = "biz.id"; + /** + * 业务类型 tag 名 + */ + String TYPE_TAG = "biz.type"; + + /** + * @return 操作名 + */ + String operationName() default ""; + + /** + * @return 业务编号 + */ + String id(); + + /** + * @return 业务类型 + */ + String type(); + +} diff --git a/tashow-framework/tashow-framework-monitor/src/main/java/com/tashow/cloud/monitor/core/aop/BizTraceAspect.java b/tashow-framework/tashow-framework-monitor/src/main/java/com/tashow/cloud/monitor/core/aop/BizTraceAspect.java new file mode 100644 index 0000000..825832e --- /dev/null +++ b/tashow-framework/tashow-framework-monitor/src/main/java/com/tashow/cloud/monitor/core/aop/BizTraceAspect.java @@ -0,0 +1,77 @@ +package com.tashow.cloud.monitor.core.aop; + +import cn.hutool.core.map.MapUtil; +import cn.hutool.core.util.StrUtil; +import com.tashow.cloud.common.util.spring.SpringExpressionUtils; +import com.tashow.cloud.monitor.core.annotation.BizTrace; +import com.tashow.cloud.monitor.core.util.TracerFrameworkUtils; +import io.opentracing.Span; +import io.opentracing.Tracer; +import io.opentracing.tag.Tags; +import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.aspectj.lang.ProceedingJoinPoint; +import org.aspectj.lang.annotation.Around; +import org.aspectj.lang.annotation.Aspect; + +import java.util.Map; + +import static java.util.Arrays.asList; + +/** + * {@link BizTrace} 切面,记录业务链路 + * + * @author mashu + */ +@Aspect +@AllArgsConstructor +@Slf4j +public class BizTraceAspect { + + private static final String BIZ_OPERATION_NAME_PREFIX = "Biz/"; + + private final Tracer tracer; + + @Around(value = "@annotation(trace)") + public Object around(ProceedingJoinPoint joinPoint, BizTrace trace) throws Throwable { + // 创建 span + String operationName = getOperationName(joinPoint, trace); + Span span = tracer.buildSpan(operationName) + .withTag(Tags.COMPONENT.getKey(), "biz") + .start(); + try { + // 执行原有方法 + return joinPoint.proceed(); + } catch (Throwable throwable) { + TracerFrameworkUtils.onError(throwable, span); + throw throwable; + } finally { + // 设置 Span 的 biz 属性 + setBizTag(span, joinPoint, trace); + // 完成 Span + span.finish(); + } + } + + private String getOperationName(ProceedingJoinPoint joinPoint, BizTrace trace) { + // 自定义操作名 + if (StrUtil.isNotEmpty(trace.operationName())) { + return BIZ_OPERATION_NAME_PREFIX + trace.operationName(); + } + // 默认操作名,使用方法名 + return BIZ_OPERATION_NAME_PREFIX + + joinPoint.getSignature().getDeclaringType().getSimpleName() + + "/" + joinPoint.getSignature().getName(); + } + + private void setBizTag(Span span, ProceedingJoinPoint joinPoint, BizTrace trace) { + try { + Map result = SpringExpressionUtils.parseExpressions(joinPoint, asList(trace.type(), trace.id())); + span.setTag(BizTrace.TYPE_TAG, MapUtil.getStr(result, trace.type())); + span.setTag(BizTrace.ID_TAG, MapUtil.getStr(result, trace.id())); + } catch (Exception ex) { + log.error("[setBizTag][解析 bizType 与 bizId 发生异常]", ex); + } + } + +} diff --git a/tashow-framework/tashow-framework-monitor/src/main/java/com/tashow/cloud/monitor/core/filter/TraceFilter.java b/tashow-framework/tashow-framework-monitor/src/main/java/com/tashow/cloud/monitor/core/filter/TraceFilter.java new file mode 100644 index 0000000..0925c98 --- /dev/null +++ b/tashow-framework/tashow-framework-monitor/src/main/java/com/tashow/cloud/monitor/core/filter/TraceFilter.java @@ -0,0 +1,33 @@ +package com.tashow.cloud.monitor.core.filter; + +import com.tashow.cloud.common.util.monitor.TracerUtils; +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.springframework.web.filter.OncePerRequestFilter; + +import java.io.IOException; + +/** + * Trace 过滤器,打印 traceId 到 header 中返回 + * + * @author 芋道源码 + */ +public class TraceFilter extends OncePerRequestFilter { + + /** + * Header 名 - 链路追踪编号 + */ + private static final String HEADER_NAME_TRACE_ID = "trace-id"; + + @Override + protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) + throws IOException, ServletException { + // 设置响应 traceId + response.addHeader(HEADER_NAME_TRACE_ID, TracerUtils.getTraceId()); + // 继续过滤 + chain.doFilter(request, response); + } + +} diff --git a/tashow-framework/tashow-framework-monitor/src/main/java/com/tashow/cloud/monitor/core/util/TracerFrameworkUtils.java b/tashow-framework/tashow-framework-monitor/src/main/java/com/tashow/cloud/monitor/core/util/TracerFrameworkUtils.java new file mode 100644 index 0000000..9ed6cbd --- /dev/null +++ b/tashow-framework/tashow-framework-monitor/src/main/java/com/tashow/cloud/monitor/core/util/TracerFrameworkUtils.java @@ -0,0 +1,46 @@ +package com.tashow.cloud.monitor.core.util; + +import io.opentracing.Span; +import io.opentracing.tag.Tags; + +import java.io.PrintWriter; +import java.io.StringWriter; +import java.util.HashMap; +import java.util.Map; + +/** + * 链路追踪 Util + * + * @author 芋道源码 + */ +public class TracerFrameworkUtils { + + /** + * 将异常记录到 Span 中,参考自 com.aliyuncs.utils.TraceUtils + * + * @param throwable 异常 + * @param span Span + */ + public static void onError(Throwable throwable, Span span) { + Tags.ERROR.set(span, Boolean.TRUE); + if (throwable != null) { + span.log(errorLogs(throwable)); + } + } + + private static Map errorLogs(Throwable throwable) { + Map errorLogs = new HashMap(10); + errorLogs.put("event", Tags.ERROR.getKey()); + errorLogs.put("error.object", throwable); + errorLogs.put("error.kind", throwable.getClass().getName()); + String message = throwable.getCause() != null ? throwable.getCause().getMessage() : throwable.getMessage(); + if (message != null) { + errorLogs.put("message", message); + } + StringWriter sw = new StringWriter(); + throwable.printStackTrace(new PrintWriter(sw)); + errorLogs.put("stack", sw.toString()); + return errorLogs; + } + +} diff --git a/tashow-framework/tashow-framework-monitor/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/tashow-framework/tashow-framework-monitor/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports new file mode 100644 index 0000000..0a9b037 --- /dev/null +++ b/tashow-framework/tashow-framework-monitor/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -0,0 +1,2 @@ +com.tashow.cloud.monitor.config.TracerAutoConfiguration +com.tashow.cloud.monitor.config.MetricsAutoConfiguration diff --git a/tashow-framework/tashow-framework-mq/pom.xml b/tashow-framework/tashow-framework-mq/pom.xml new file mode 100644 index 0000000..7ed1e03 --- /dev/null +++ b/tashow-framework/tashow-framework-mq/pom.xml @@ -0,0 +1,60 @@ + + + + tashow-framework + com.tashow.cloud + ${revision} + + 4.0.0 + + tashow-framework-mq + jar + + ${project.artifactId} + 消息队列模块,基于RabbitMQ等中间件 + https://github.com/tashow/tashow-platform + + + + + org.springframework.boot + spring-boot-starter-amqp + true + + + + + org.springframework + spring-web + true + + + org.springframework + spring-webmvc + true + + + jakarta.servlet + jakarta.servlet-api + true + + + org.projectlombok + lombok + provided + + + com.tashow.cloud + tashow-data-mybatis + + + org.jodd + jodd-util + 6.3.0 + compile + + + + diff --git a/tashow-framework/tashow-framework-mq/src/main/java/com/tashow/cloud/mq/core/BaseMqMessage.java b/tashow-framework/tashow-framework-mq/src/main/java/com/tashow/cloud/mq/core/BaseMqMessage.java new file mode 100644 index 0000000..11bbad2 --- /dev/null +++ b/tashow-framework/tashow-framework-mq/src/main/java/com/tashow/cloud/mq/core/BaseMqMessage.java @@ -0,0 +1,46 @@ +package com.tashow.cloud.mq.core; +import lombok.Data; +import java.io.Serializable; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; +/** + * MQ消息基类, + * + * + * @author tashow + */ +@Data +public class BaseMqMessage implements Serializable { + + private static final long serialVersionUID = 1L; + /** + * 消息ID,默认为UUID + */ + private Integer id = UUID.randomUUID().hashCode(); + /** + * 消息状态码 + */ + private Integer statusCode; + + /** + * 消息重试次数 + */ + private Integer retryCount = 0; + + /** + * 消息错误信息 + */ + private String errorMessage; + + /** + * 消息创建时间 + */ + private Date createTime = new Date(); + + /** + * 扩展数据 + */ + private Map extraData = new HashMap<>(); +} \ No newline at end of file diff --git a/tashow-framework/tashow-framework-mq/src/main/java/com/tashow/cloud/mq/handler/FailRecordHandler.java b/tashow-framework/tashow-framework-mq/src/main/java/com/tashow/cloud/mq/handler/FailRecordHandler.java new file mode 100644 index 0000000..1035e3f --- /dev/null +++ b/tashow-framework/tashow-framework-mq/src/main/java/com/tashow/cloud/mq/handler/FailRecordHandler.java @@ -0,0 +1,31 @@ +package com.tashow.cloud.mq.handler; +/** + * 消息记录处理接口 + * + * @author tashow + */ +public interface FailRecordHandler { + /** + * 保存消息记录 + * + * @param exchange 交换机 + * @param routingKey 路由键 + * @param cause 失败原因,可为null + * @param messageContent 消息内容 + * @param status 状态:0-未处理,1-处理成功,2-处理失败 + */ + void saveMessageRecord(Integer id, String exchange, String routingKey, String cause, String messageContent, int status); + /** + * 更新消息状态 + * + * @param id 关联ID + */ + void updateMessageStatus(Integer id); + + /** + * 更新消息状态并设置失败原因 + + */ + void updateMessageStatusWithCause(Integer id, String causes); + +} \ No newline at end of file diff --git a/tashow-framework/tashow-framework-mq/src/main/java/com/tashow/cloud/mq/rabbitmq/config/RabbitMQAutoConfiguration.java b/tashow-framework/tashow-framework-mq/src/main/java/com/tashow/cloud/mq/rabbitmq/config/RabbitMQAutoConfiguration.java new file mode 100644 index 0000000..0b1ab33 --- /dev/null +++ b/tashow-framework/tashow-framework-mq/src/main/java/com/tashow/cloud/mq/rabbitmq/config/RabbitMQAutoConfiguration.java @@ -0,0 +1,16 @@ +package com.tashow.cloud.mq.rabbitmq.config; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; + +/** + * RabbitMQ 消息队列自动配置类 + * + * @author tashow + */ +@AutoConfiguration +@ConditionalOnClass(name = "org.springframework.amqp.rabbit.core.RabbitTemplate") +public class RabbitMQAutoConfiguration extends RabbitMQConfiguration { + private static final Logger log = LoggerFactory.getLogger(RabbitMQAutoConfiguration.class); +} \ No newline at end of file diff --git a/tashow-framework/tashow-framework-mq/src/main/java/com/tashow/cloud/mq/rabbitmq/config/RabbitMQConfiguration.java b/tashow-framework/tashow-framework-mq/src/main/java/com/tashow/cloud/mq/rabbitmq/config/RabbitMQConfiguration.java new file mode 100644 index 0000000..47370c2 --- /dev/null +++ b/tashow-framework/tashow-framework-mq/src/main/java/com/tashow/cloud/mq/rabbitmq/config/RabbitMQConfiguration.java @@ -0,0 +1,29 @@ +package com.tashow.cloud.mq.rabbitmq.config; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.amqp.rabbit.core.RabbitTemplate; +import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter; +import org.springframework.amqp.support.converter.MessageConverter; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * RabbitMQ 配置类 + * + * @author tashow + */ +@Configuration +@ConditionalOnClass(name = "org.springframework.amqp.rabbit.core.RabbitTemplate") +public class RabbitMQConfiguration { + /** + * 创建消息转换器 + * + * @return MessageConverter + */ + @Bean + public MessageConverter messageConverter() { + return new Jackson2JsonMessageConverter(); + } +} \ No newline at end of file diff --git a/tashow-framework/tashow-framework-mq/src/main/java/com/tashow/cloud/mq/rabbitmq/consumer/AbstractRabbitMQConsumer.java b/tashow-framework/tashow-framework-mq/src/main/java/com/tashow/cloud/mq/rabbitmq/consumer/AbstractRabbitMQConsumer.java new file mode 100644 index 0000000..f4e0535 --- /dev/null +++ b/tashow-framework/tashow-framework-mq/src/main/java/com/tashow/cloud/mq/rabbitmq/consumer/AbstractRabbitMQConsumer.java @@ -0,0 +1,89 @@ +package com.tashow.cloud.mq.rabbitmq.consumer; +import com.rabbitmq.client.Channel; +import com.tashow.cloud.mq.core.BaseMqMessage; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.amqp.support.AmqpHeaders; +import org.springframework.messaging.handler.annotation.Header; + +/** + * RabbitMQ消息消费者抽象类 + * + * @param 消息类型 + * @author tashow + */ +public abstract class AbstractRabbitMQConsumer { + + private static final Logger log = LoggerFactory.getLogger(AbstractRabbitMQConsumer.class); + /** + * 消息状态:成功 + */ + public static final int STATUS_SUCCESS = 20; + /** + * 消息状态:消费异常 + */ + public static final int STATUS_SEND_EXCEPTION = 30; + + + /** + * 埋点处理消息 + * + * @param message 消息对象 + * @return 处理结果,true表示处理成功,false表示处理失败 + */ + public abstract boolean processMessage(T message); + + + /** + * 消息处理入口 + * + * @param message 消息对象 + * @param channel 通道 + * @param deliveryTag 投递标签 + */ + public void onMessage(T message, Channel channel, @Header(AmqpHeaders.DELIVERY_TAG) long deliveryTag) { + message.setStatusCode(STATUS_SUCCESS); + try { + if(true){ + throw new RuntimeException("测试异常"); + } + processMessage(message); + safeChannelAck(channel, deliveryTag); + } catch (Exception e) { + message.setStatusCode(STATUS_SEND_EXCEPTION); + processMessage( message); + safeChannelAck(channel, deliveryTag); + + } + } + + /** + * 安全确认消息 + * + * @param channel 通道 + * @param deliveryTag 投递标签 + */ + protected void safeChannelAck(Channel channel, long deliveryTag) { + try { + channel.basicAck(deliveryTag, false); + } catch (Exception e) { + log.error("[MQ消费者] 确认消息失败: {}", e.getMessage()); + } + } + + /** + * 安全拒绝消息 + * + * @param channel 通道 + * @param deliveryTag 投递标签 + * @param multiple 是否批量 + * @param requeue 是否重新入队 + */ + protected void safeChannelNack(Channel channel, long deliveryTag, boolean multiple, boolean requeue) { + try { + channel.basicNack(deliveryTag, multiple, requeue); + } catch (Exception e) { + log.error("[MQ消费者] 拒绝消息失败: {}", e.getMessage()); + } + } +} \ No newline at end of file diff --git a/tashow-framework/tashow-framework-mq/src/main/java/com/tashow/cloud/mq/rabbitmq/producer/AbstractRabbitMQProducer.java b/tashow-framework/tashow-framework-mq/src/main/java/com/tashow/cloud/mq/rabbitmq/producer/AbstractRabbitMQProducer.java new file mode 100644 index 0000000..072a657 --- /dev/null +++ b/tashow-framework/tashow-framework-mq/src/main/java/com/tashow/cloud/mq/rabbitmq/producer/AbstractRabbitMQProducer.java @@ -0,0 +1,97 @@ +package com.tashow.cloud.mq.rabbitmq.producer; +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.JSONObject; +import com.tashow.cloud.common.util.json.JsonUtils; +import com.tashow.cloud.mq.core.BaseMqMessage; +import com.tashow.cloud.mq.handler.FailRecordHandler; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.amqp.rabbit.connection.CorrelationData; +import org.springframework.amqp.rabbit.core.RabbitTemplate; +import org.springframework.beans.factory.annotation.Autowired; +import jakarta.annotation.PostConstruct; +import java.util.UUID; + +/** + * RabbitMQ消息生产者抽象类 + * + * @param 消息类型 + * @author tashow + */ +public abstract class AbstractRabbitMQProducer + implements RabbitTemplate.ConfirmCallback, RabbitTemplate.ReturnsCallback { + + private static final Logger log = LoggerFactory.getLogger(AbstractRabbitMQProducer.class); + + @Autowired + protected RabbitTemplate rabbitTemplate; + + @Autowired(required = false) + protected FailRecordHandler failRecordHandler; + + /** + * 初始化RabbitTemplate + */ + @PostConstruct + public void initRabbitTemplate() { + rabbitTemplate.setMandatory(true); + rabbitTemplate.setReturnsCallback(this); + rabbitTemplate.setConfirmCallback(this); + if (rabbitTemplate.isConfirmListener()) { + log.info("[MQ生产者] 确认回调已正确配置"); + } else { + log.error("[MQ生产者] 确认回调配置失败"); + } + } + + + + + + /** + * 异步发送消息,使用指定的correlationId + * + * @param message 消息对象 + */ + public void asyncSendMessage(T message) { + try { + String messageJson = JsonUtils.toJsonString(message); + CorrelationData correlationData = new CorrelationData(messageJson); + failRecordHandler.saveMessageRecord( + message.getId(), + getExchange(), + getRoutingKey(), + null, + messageJson, + 0 + ); + rabbitTemplate.convertAndSend(getExchange(), getRoutingKey(), message, correlationData); + } catch (Exception e) { + throw e; + } + } + /** + * 获取交换机名称 + * + * @return 交换机名称 + */ + public abstract String getExchange(); + /** + * 获取路由键 + * + * @return 路由键 + */ + public abstract String getRoutingKey(); + + @Override + public void confirm(CorrelationData correlationData, boolean ack, String cause) { + JSONObject jsonObject = JSON.parseObject(correlationData.getId()); + Integer id = jsonObject.getInteger("id"); + if (ack) { + failRecordHandler.updateMessageStatus(id); + } else { + failRecordHandler.updateMessageStatusWithCause(id,cause); + } + } + +} \ No newline at end of file diff --git a/tashow-framework/tashow-framework-mq/src/main/java/com/tashow/cloud/mq/retry/AbstractMessageRetryTask.java b/tashow-framework/tashow-framework-mq/src/main/java/com/tashow/cloud/mq/retry/AbstractMessageRetryTask.java new file mode 100644 index 0000000..fa4aa49 --- /dev/null +++ b/tashow-framework/tashow-framework-mq/src/main/java/com/tashow/cloud/mq/retry/AbstractMessageRetryTask.java @@ -0,0 +1,49 @@ +package com.tashow.cloud.mq.retry; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.List; + +/** + * 消息重试任务抽象实现 + * + * @param 失败记录类型 + * @author tashow + */ +public abstract class AbstractMessageRetryTask { + + private static final Logger log = LoggerFactory.getLogger(AbstractMessageRetryTask.class); + + + + /** + * 获取消息重试服务 + * + * @return 消息重试服务 + */ + protected abstract MessageRetryService getMessageRetryService(); + + /** + * 获取记录ID + * + * @param record 记录对象 + * @return 记录ID + */ + protected abstract Integer getRecordId(T record); + + /** + * 执行重试 + */ + public void retryFailedMessages() { + try { + List unprocessedRecords = getMessageRetryService().getUnprocessedRecords(); + for (T record : unprocessedRecords) { + Integer recordId = getRecordId(record); + getMessageRetryService().retryFailedMessage(recordId); + } + } catch (Exception e) { + log.error("[MQ重试] 执行消息重试任务异常", e); + } + } +} \ No newline at end of file diff --git a/tashow-framework/tashow-framework-mq/src/main/java/com/tashow/cloud/mq/retry/MessageRetryService.java b/tashow-framework/tashow-framework-mq/src/main/java/com/tashow/cloud/mq/retry/MessageRetryService.java new file mode 100644 index 0000000..1272dc8 --- /dev/null +++ b/tashow-framework/tashow-framework-mq/src/main/java/com/tashow/cloud/mq/retry/MessageRetryService.java @@ -0,0 +1,28 @@ +package com.tashow.cloud.mq.retry; + +import java.util.List; + +/** + * 消息重试服务接口 + * + * @param 失败记录类型 + * @author tashow + */ +public interface MessageRetryService { + + /** + * 获取未处理的失败记录 + * + * @return 失败记录列表 + */ + List getUnprocessedRecords(); + + /** + * 重试失败消息 + * + * @param recordId 记录ID + * @return 重试结果 + */ + void retryFailedMessage(Integer recordId); + +} \ No newline at end of file diff --git a/tashow-framework/tashow-framework-mq/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/tashow-framework/tashow-framework-mq/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports new file mode 100644 index 0000000..7d713e1 --- /dev/null +++ b/tashow-framework/tashow-framework-mq/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -0,0 +1 @@ +com.tashow.cloud.mq.rabbitmq.config.RabbitMQAutoConfiguration diff --git a/tashow-framework/tashow-framework-protection/pom.xml b/tashow-framework/tashow-framework-protection/pom.xml new file mode 100644 index 0000000..f025548 --- /dev/null +++ b/tashow-framework/tashow-framework-protection/pom.xml @@ -0,0 +1,39 @@ + + + 4.0.0 + + com.tashow.cloud + tashow-framework + ${revision} + + tashow-framework-protection + jar + + ${project.artifactId} + 服务保证,提供分布式锁、幂等、限流、熔断等等功能 + + + + + com.tashow.cloud + tashow-framework-web + provided + + + + + com.tashow.cloud + tashow-data-redis + + + + + com.baomidou + lock4j-redisson-spring-boot-starter + true + + + + diff --git a/tashow-framework/tashow-framework-protection/src/main/java/com/tashow/cloud/protection/idempotent/config/IdempotentConfiguration.java b/tashow-framework/tashow-framework-protection/src/main/java/com/tashow/cloud/protection/idempotent/config/IdempotentConfiguration.java new file mode 100644 index 0000000..67effc5 --- /dev/null +++ b/tashow-framework/tashow-framework-protection/src/main/java/com/tashow/cloud/protection/idempotent/config/IdempotentConfiguration.java @@ -0,0 +1,46 @@ +package com.tashow.cloud.protection.idempotent.config; + +import com.tashow.cloud.protection.idempotent.core.keyresolver.impl.ExpressionIdempotentKeyResolver; +import com.tashow.cloud.protection.idempotent.core.keyresolver.impl.UserIdempotentKeyResolver; +import com.tashow.cloud.protection.idempotent.core.aop.IdempotentAspect; +import com.tashow.cloud.protection.idempotent.core.keyresolver.IdempotentKeyResolver; +import com.tashow.cloud.protection.idempotent.core.keyresolver.impl.DefaultIdempotentKeyResolver; +import com.tashow.cloud.protection.idempotent.core.redis.IdempotentRedisDAO; +import com.tashow.cloud.redis.config.TashowRedisAutoConfiguration; +import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.context.annotation.Bean; +import org.springframework.data.redis.core.StringRedisTemplate; + +import java.util.List; + +@AutoConfiguration(after = TashowRedisAutoConfiguration.class) +public class IdempotentConfiguration { + + @Bean + public IdempotentAspect idempotentAspect(List keyResolvers, IdempotentRedisDAO idempotentRedisDAO) { + return new IdempotentAspect(keyResolvers, idempotentRedisDAO); + } + + @Bean + public IdempotentRedisDAO idempotentRedisDAO(StringRedisTemplate stringRedisTemplate) { + return new IdempotentRedisDAO(stringRedisTemplate); + } + + // ========== 各种 IdempotentKeyResolver Bean ========== + + @Bean + public DefaultIdempotentKeyResolver defaultIdempotentKeyResolver() { + return new DefaultIdempotentKeyResolver(); + } + + @Bean + public UserIdempotentKeyResolver userIdempotentKeyResolver() { + return new UserIdempotentKeyResolver(); + } + + @Bean + public ExpressionIdempotentKeyResolver expressionIdempotentKeyResolver() { + return new ExpressionIdempotentKeyResolver(); + } + +} diff --git a/tashow-framework/tashow-framework-protection/src/main/java/com/tashow/cloud/protection/idempotent/core/annotation/Idempotent.java b/tashow-framework/tashow-framework-protection/src/main/java/com/tashow/cloud/protection/idempotent/core/annotation/Idempotent.java new file mode 100644 index 0000000..2598d72 --- /dev/null +++ b/tashow-framework/tashow-framework-protection/src/main/java/com/tashow/cloud/protection/idempotent/core/annotation/Idempotent.java @@ -0,0 +1,64 @@ +package com.tashow.cloud.protection.idempotent.core.annotation; + +import com.tashow.cloud.protection.idempotent.core.keyresolver.impl.UserIdempotentKeyResolver; +import com.tashow.cloud.protection.idempotent.core.keyresolver.IdempotentKeyResolver; +import com.tashow.cloud.protection.idempotent.core.keyresolver.impl.DefaultIdempotentKeyResolver; +import com.tashow.cloud.protection.idempotent.core.keyresolver.impl.ExpressionIdempotentKeyResolver; +import com.tashow.cloud.protection.idempotent.core.keyresolver.impl.UserIdempotentKeyResolver; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import java.util.concurrent.TimeUnit; + +/** + * 幂等注解 + * + * @author 芋道源码 + */ +@Target({ElementType.METHOD}) +@Retention(RetentionPolicy.RUNTIME) +public @interface Idempotent { + + /** + * 幂等的超时时间,默认为 1 秒 + * + * 注意,如果执行时间超过它,请求还是会进来 + */ + int timeout() default 1; + /** + * 时间单位,默认为 SECONDS 秒 + */ + TimeUnit timeUnit() default TimeUnit.SECONDS; + + /** + * 提示信息,正在执行中的提示 + */ + String message() default "重复请求,请稍后重试"; + + /** + * 使用的 Key 解析器 + * + * @see DefaultIdempotentKeyResolver 全局级别 + * @see UserIdempotentKeyResolver 用户级别 + * @see ExpressionIdempotentKeyResolver 自定义表达式,通过 {@link #keyArg()} 计算 + */ + Class keyResolver() default DefaultIdempotentKeyResolver.class; + /** + * 使用的 Key 参数 + */ + String keyArg() default ""; + + /** + * 删除 Key,当发生异常时候 + * + * 问题:为什么发生异常时,需要删除 Key 呢? + * 回答:发生异常时,说明业务发生错误,此时需要删除 Key,避免下次请求无法正常执行。 + * + * 问题:为什么不搞 deleteWhenSuccess 执行成功时,需要删除 Key 呢? + * 回答:这种情况下,本质上是分布式锁,推荐使用 @Lock4j 注解 + */ + boolean deleteKeyWhenException() default true; + +} diff --git a/tashow-framework/tashow-framework-protection/src/main/java/com/tashow/cloud/protection/idempotent/core/aop/IdempotentAspect.java b/tashow-framework/tashow-framework-protection/src/main/java/com/tashow/cloud/protection/idempotent/core/aop/IdempotentAspect.java new file mode 100644 index 0000000..3fbcc60 --- /dev/null +++ b/tashow-framework/tashow-framework-protection/src/main/java/com/tashow/cloud/protection/idempotent/core/aop/IdempotentAspect.java @@ -0,0 +1,70 @@ +package com.tashow.cloud.protection.idempotent.core.aop; + +import cn.hutool.core.lang.Assert; +import com.tashow.cloud.common.exception.ServiceException; +import com.tashow.cloud.common.exception.enums.GlobalErrorCodeConstants; +import com.tashow.cloud.common.util.collection.CollectionUtils; +import com.tashow.cloud.protection.idempotent.core.annotation.Idempotent; +import com.tashow.cloud.protection.idempotent.core.keyresolver.IdempotentKeyResolver; +import com.tashow.cloud.protection.idempotent.core.annotation.Idempotent; +import com.tashow.cloud.protection.idempotent.core.keyresolver.IdempotentKeyResolver; +import com.tashow.cloud.protection.idempotent.core.redis.IdempotentRedisDAO; +import lombok.extern.slf4j.Slf4j; +import org.aspectj.lang.ProceedingJoinPoint; +import org.aspectj.lang.annotation.Around; +import org.aspectj.lang.annotation.Aspect; + +import java.util.List; +import java.util.Map; + +/** + * 拦截声明了 {@link Idempotent} 注解的方法,实现幂等操作 + * + * @author 芋道源码 + */ +@Aspect +@Slf4j +public class IdempotentAspect { + + /** + * IdempotentKeyResolver 集合 + */ + private final Map, IdempotentKeyResolver> keyResolvers; + + private final IdempotentRedisDAO idempotentRedisDAO; + + public IdempotentAspect(List keyResolvers, IdempotentRedisDAO idempotentRedisDAO) { + this.keyResolvers = CollectionUtils.convertMap(keyResolvers, IdempotentKeyResolver::getClass); + this.idempotentRedisDAO = idempotentRedisDAO; + } + + @Around(value = "@annotation(idempotent)") + public Object aroundPointCut(ProceedingJoinPoint joinPoint, Idempotent idempotent) throws Throwable { + // 获得 IdempotentKeyResolver + IdempotentKeyResolver keyResolver = keyResolvers.get(idempotent.keyResolver()); + Assert.notNull(keyResolver, "找不到对应的 IdempotentKeyResolver"); + // 解析 Key + String key = keyResolver.resolver(joinPoint, idempotent); + + // 1. 锁定 Key + boolean success = idempotentRedisDAO.setIfAbsent(key, idempotent.timeout(), idempotent.timeUnit()); + // 锁定失败,抛出异常 + if (!success) { + log.info("[aroundPointCut][方法({}) 参数({}) 存在重复请求]", joinPoint.getSignature().toString(), joinPoint.getArgs()); + throw new ServiceException(GlobalErrorCodeConstants.REPEATED_REQUESTS.getCode(), idempotent.message()); + } + + // 2. 执行逻辑 + try { + return joinPoint.proceed(); + } catch (Throwable throwable) { + // 3. 异常时,删除 Key + // 参考美团 GTIS 思路:https://tech.meituan.com/2016/09/29/distributed-system-mutually-exclusive-idempotence-cerberus-gtis.html + if (idempotent.deleteKeyWhenException()) { + idempotentRedisDAO.delete(key); + } + throw throwable; + } + } + +} diff --git a/tashow-framework/tashow-framework-protection/src/main/java/com/tashow/cloud/protection/idempotent/core/keyresolver/IdempotentKeyResolver.java b/tashow-framework/tashow-framework-protection/src/main/java/com/tashow/cloud/protection/idempotent/core/keyresolver/IdempotentKeyResolver.java new file mode 100644 index 0000000..2d90fb2 --- /dev/null +++ b/tashow-framework/tashow-framework-protection/src/main/java/com/tashow/cloud/protection/idempotent/core/keyresolver/IdempotentKeyResolver.java @@ -0,0 +1,23 @@ +package com.tashow.cloud.protection.idempotent.core.keyresolver; + +import com.tashow.cloud.protection.idempotent.core.annotation.Idempotent; +import com.tashow.cloud.protection.idempotent.core.annotation.Idempotent; +import org.aspectj.lang.JoinPoint; + +/** + * 幂等 Key 解析器接口 + * + * @author 芋道源码 + */ +public interface IdempotentKeyResolver { + + /** + * 解析一个 Key + * + * @param idempotent 幂等注解 + * @param joinPoint AOP 切面 + * @return Key + */ + String resolver(JoinPoint joinPoint, Idempotent idempotent); + +} diff --git a/tashow-framework/tashow-framework-protection/src/main/java/com/tashow/cloud/protection/idempotent/core/keyresolver/impl/DefaultIdempotentKeyResolver.java b/tashow-framework/tashow-framework-protection/src/main/java/com/tashow/cloud/protection/idempotent/core/keyresolver/impl/DefaultIdempotentKeyResolver.java new file mode 100644 index 0000000..9dfb3e8 --- /dev/null +++ b/tashow-framework/tashow-framework-protection/src/main/java/com/tashow/cloud/protection/idempotent/core/keyresolver/impl/DefaultIdempotentKeyResolver.java @@ -0,0 +1,26 @@ +package com.tashow.cloud.protection.idempotent.core.keyresolver.impl; + +import cn.hutool.core.util.StrUtil; +import cn.hutool.crypto.SecureUtil; +import com.tashow.cloud.protection.idempotent.core.annotation.Idempotent; +import com.tashow.cloud.protection.idempotent.core.annotation.Idempotent; +import com.tashow.cloud.protection.idempotent.core.keyresolver.IdempotentKeyResolver; +import org.aspectj.lang.JoinPoint; + +/** + * 默认(全局级别)幂等 Key 解析器,使用方法名 + 方法参数,组装成一个 Key + * + * 为了避免 Key 过长,使用 MD5 进行“压缩” + * + * @author 芋道源码 + */ +public class DefaultIdempotentKeyResolver implements IdempotentKeyResolver { + + @Override + public String resolver(JoinPoint joinPoint, Idempotent idempotent) { + String methodName = joinPoint.getSignature().toString(); + String argsStr = StrUtil.join(",", joinPoint.getArgs()); + return SecureUtil.md5(methodName + argsStr); + } + +} diff --git a/tashow-framework/tashow-framework-protection/src/main/java/com/tashow/cloud/protection/idempotent/core/keyresolver/impl/ExpressionIdempotentKeyResolver.java b/tashow-framework/tashow-framework-protection/src/main/java/com/tashow/cloud/protection/idempotent/core/keyresolver/impl/ExpressionIdempotentKeyResolver.java new file mode 100644 index 0000000..195361d --- /dev/null +++ b/tashow-framework/tashow-framework-protection/src/main/java/com/tashow/cloud/protection/idempotent/core/keyresolver/impl/ExpressionIdempotentKeyResolver.java @@ -0,0 +1,65 @@ +package com.tashow.cloud.protection.idempotent.core.keyresolver.impl; + +import cn.hutool.core.util.ArrayUtil; +import com.tashow.cloud.protection.idempotent.core.annotation.Idempotent; +import com.tashow.cloud.protection.idempotent.core.annotation.Idempotent; +import com.tashow.cloud.protection.idempotent.core.keyresolver.IdempotentKeyResolver; +import org.aspectj.lang.JoinPoint; +import org.aspectj.lang.reflect.MethodSignature; +import org.springframework.core.DefaultParameterNameDiscoverer; +import org.springframework.core.ParameterNameDiscoverer; +import org.springframework.expression.Expression; +import org.springframework.expression.ExpressionParser; +import org.springframework.expression.spel.standard.SpelExpressionParser; +import org.springframework.expression.spel.support.StandardEvaluationContext; + +import java.lang.reflect.Method; + +/** + * 基于 Spring EL 表达式, + * + * @author 芋道源码 + */ +public class ExpressionIdempotentKeyResolver implements IdempotentKeyResolver { + + private final ParameterNameDiscoverer parameterNameDiscoverer = new DefaultParameterNameDiscoverer(); + + private final ExpressionParser expressionParser = new SpelExpressionParser(); + + @Override + public String resolver(JoinPoint joinPoint, Idempotent idempotent) { + // 获得被拦截方法参数名列表 + Method method = getMethod(joinPoint); + Object[] args = joinPoint.getArgs(); + String[] parameterNames = this.parameterNameDiscoverer.getParameterNames(method); + // 准备 Spring EL 表达式解析的上下文 + StandardEvaluationContext evaluationContext = new StandardEvaluationContext(); + if (ArrayUtil.isNotEmpty(parameterNames)) { + for (int i = 0; i < parameterNames.length; i++) { + evaluationContext.setVariable(parameterNames[i], args[i]); + } + } + + // 解析参数 + Expression expression = expressionParser.parseExpression(idempotent.keyArg()); + return expression.getValue(evaluationContext, String.class); + } + + private static Method getMethod(JoinPoint point) { + // 处理,声明在类上的情况 + MethodSignature signature = (MethodSignature) point.getSignature(); + Method method = signature.getMethod(); + if (!method.getDeclaringClass().isInterface()) { + return method; + } + + // 处理,声明在接口上的情况 + try { + return point.getTarget().getClass().getDeclaredMethod( + point.getSignature().getName(), method.getParameterTypes()); + } catch (NoSuchMethodException e) { + throw new RuntimeException(e); + } + } + +} diff --git a/tashow-framework/tashow-framework-protection/src/main/java/com/tashow/cloud/protection/idempotent/core/keyresolver/impl/UserIdempotentKeyResolver.java b/tashow-framework/tashow-framework-protection/src/main/java/com/tashow/cloud/protection/idempotent/core/keyresolver/impl/UserIdempotentKeyResolver.java new file mode 100644 index 0000000..0a58eef --- /dev/null +++ b/tashow-framework/tashow-framework-protection/src/main/java/com/tashow/cloud/protection/idempotent/core/keyresolver/impl/UserIdempotentKeyResolver.java @@ -0,0 +1,28 @@ +package com.tashow.cloud.protection.idempotent.core.keyresolver.impl; + +import cn.hutool.core.util.StrUtil; +import cn.hutool.crypto.SecureUtil; +import com.tashow.cloud.protection.idempotent.core.annotation.Idempotent; +import com.tashow.cloud.protection.idempotent.core.keyresolver.IdempotentKeyResolver; +import com.tashow.cloud.web.web.core.util.WebFrameworkUtils; +import org.aspectj.lang.JoinPoint; + +/** + * 用户级别的幂等 Key 解析器,使用方法名 + 方法参数 + userId + userType,组装成一个 Key + * + * 为了避免 Key 过长,使用 MD5 进行“压缩” + * + * @author 芋道源码 + */ +public class UserIdempotentKeyResolver implements IdempotentKeyResolver { + + @Override + public String resolver(JoinPoint joinPoint, Idempotent idempotent) { + String methodName = joinPoint.getSignature().toString(); + String argsStr = StrUtil.join(",", joinPoint.getArgs()); + Long userId = WebFrameworkUtils.getLoginUserId(); + Integer userType = WebFrameworkUtils.getLoginUserType(); + return SecureUtil.md5(methodName + argsStr + userId + userType); + } + +} diff --git a/tashow-framework/tashow-framework-protection/src/main/java/com/tashow/cloud/protection/idempotent/core/redis/IdempotentRedisDAO.java b/tashow-framework/tashow-framework-protection/src/main/java/com/tashow/cloud/protection/idempotent/core/redis/IdempotentRedisDAO.java new file mode 100644 index 0000000..2c4293b --- /dev/null +++ b/tashow-framework/tashow-framework-protection/src/main/java/com/tashow/cloud/protection/idempotent/core/redis/IdempotentRedisDAO.java @@ -0,0 +1,41 @@ +package com.tashow.cloud.protection.idempotent.core.redis; + +import lombok.AllArgsConstructor; +import org.springframework.data.redis.core.StringRedisTemplate; + +import java.util.concurrent.TimeUnit; + +/** + * 幂等 Redis DAO + * + * @author 芋道源码 + */ +@AllArgsConstructor +public class IdempotentRedisDAO { + + /** + * 幂等操作 + * + * KEY 格式:idempotent:%s // 参数为 uuid + * VALUE 格式:String + * 过期时间:不固定 + */ + private static final String IDEMPOTENT = "idempotent:%s"; + + private final StringRedisTemplate redisTemplate; + + public Boolean setIfAbsent(String key, long timeout, TimeUnit timeUnit) { + String redisKey = formatKey(key); + return redisTemplate.opsForValue().setIfAbsent(redisKey, "", timeout, timeUnit); + } + + public void delete(String key) { + String redisKey = formatKey(key); + redisTemplate.delete(redisKey); + } + + private static String formatKey(String key) { + return String.format(IDEMPOTENT, key); + } + +} diff --git a/tashow-framework/tashow-framework-protection/src/main/java/com/tashow/cloud/protection/idempotent/package-info.java b/tashow-framework/tashow-framework-protection/src/main/java/com/tashow/cloud/protection/idempotent/package-info.java new file mode 100644 index 0000000..87ae469 --- /dev/null +++ b/tashow-framework/tashow-framework-protection/src/main/java/com/tashow/cloud/protection/idempotent/package-info.java @@ -0,0 +1,12 @@ +/** + * 幂等组件,参考 https://github.com/it4alla/idempotent 项目实现 + * 实现原理是,相同参数的方法,一段时间内,有且仅能执行一次。通过这样的方式,保证幂等性。 + * + * 使用场景:例如说,用户快速的双击了某个按钮,前端没有禁用该按钮,导致发送了两次重复的请求。 + * + * 和 it4alla/idempotent 组件的差异点,主要体现在两点: + * 1. 我们去掉了 @Idempotent 注解的 delKey 属性。原因是,本质上 delKey 为 true 时,实现的是分布式锁的能力 + * 此时,我们偏向使用 Lock4j 组件。原则上,一个组件只提供一种单一的能力。 + * 2. 考虑到组件的通用性,我们并未像 it4alla/idempotent 组件一样使用 Redisson RMap 结构,而是直接使用 Redis 的 String 数据格式。 + */ +package com.tashow.cloud.protection.idempotent; diff --git a/tashow-framework/tashow-framework-protection/src/main/java/com/tashow/cloud/protection/lock4j/config/Lock4jConfiguration.java b/tashow-framework/tashow-framework-protection/src/main/java/com/tashow/cloud/protection/lock4j/config/Lock4jConfiguration.java new file mode 100644 index 0000000..02cc9cd --- /dev/null +++ b/tashow-framework/tashow-framework-protection/src/main/java/com/tashow/cloud/protection/lock4j/config/Lock4jConfiguration.java @@ -0,0 +1,19 @@ +package com.tashow.cloud.protection.lock4j.config; + +import com.baomidou.lock.spring.boot.autoconfigure.LockAutoConfiguration; +import com.tashow.cloud.protection.lock4j.core.DefaultLockFailureStrategy; +import com.tashow.cloud.protection.lock4j.core.DefaultLockFailureStrategy; +import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.context.annotation.Bean; + +@AutoConfiguration(before = LockAutoConfiguration.class) +@ConditionalOnClass(name = "com.baomidou.lock.annotation.Lock4j") +public class Lock4jConfiguration { + + @Bean + public DefaultLockFailureStrategy lockFailureStrategy() { + return new DefaultLockFailureStrategy(); + } + +} diff --git a/tashow-framework/tashow-framework-protection/src/main/java/com/tashow/cloud/protection/lock4j/core/DefaultLockFailureStrategy.java b/tashow-framework/tashow-framework-protection/src/main/java/com/tashow/cloud/protection/lock4j/core/DefaultLockFailureStrategy.java new file mode 100644 index 0000000..17b742d --- /dev/null +++ b/tashow-framework/tashow-framework-protection/src/main/java/com/tashow/cloud/protection/lock4j/core/DefaultLockFailureStrategy.java @@ -0,0 +1,21 @@ +package com.tashow.cloud.protection.lock4j.core; + +import com.tashow.cloud.common.exception.ServiceException; +import com.baomidou.lock.LockFailureStrategy; +import com.tashow.cloud.common.exception.enums.GlobalErrorCodeConstants; +import lombok.extern.slf4j.Slf4j; + +import java.lang.reflect.Method; + +/** + * 自定义获取锁失败策略,抛出 {@link ServiceException} 异常 + */ +@Slf4j +public class DefaultLockFailureStrategy implements LockFailureStrategy { + + @Override + public void onLockFailure(String key, Method method, Object[] arguments) { + log.debug("[onLockFailure][线程:{} 获取锁失败,key:{} 获取失败:{} ]", Thread.currentThread().getName(), key, arguments); + throw new ServiceException(GlobalErrorCodeConstants.LOCKED); + } +} diff --git a/tashow-framework/tashow-framework-protection/src/main/java/com/tashow/cloud/protection/lock4j/core/Lock4jRedisKeyConstants.java b/tashow-framework/tashow-framework-protection/src/main/java/com/tashow/cloud/protection/lock4j/core/Lock4jRedisKeyConstants.java new file mode 100644 index 0000000..2dc9695 --- /dev/null +++ b/tashow-framework/tashow-framework-protection/src/main/java/com/tashow/cloud/protection/lock4j/core/Lock4jRedisKeyConstants.java @@ -0,0 +1,19 @@ +package com.tashow.cloud.protection.lock4j.core; + +/** + * Lock4j Redis Key 枚举类 + * + * @author 芋道源码 + */ +public interface Lock4jRedisKeyConstants { + + /** + * 分布式锁 + * + * KEY 格式:lock4j:%s // 参数来自 DefaultLockKeyBuilder 类 + * VALUE 数据格式:HASH // RLock.class:Redisson 的 Lock 锁,使用 Hash 数据结构 + * 过期时间:不固定 + */ + String LOCK4J = "lock4j:%s"; + +} diff --git a/tashow-framework/tashow-framework-protection/src/main/java/com/tashow/cloud/protection/lock4j/package-info.java b/tashow-framework/tashow-framework-protection/src/main/java/com/tashow/cloud/protection/lock4j/package-info.java new file mode 100644 index 0000000..627b698 --- /dev/null +++ b/tashow-framework/tashow-framework-protection/src/main/java/com/tashow/cloud/protection/lock4j/package-info.java @@ -0,0 +1,4 @@ +/** + * 分布式锁组件,使用 https://gitee.com/baomidou/lock4j 开源项目 + */ +package com.tashow.cloud.protection.lock4j; diff --git a/tashow-framework/tashow-framework-protection/src/main/java/com/tashow/cloud/protection/ratelimiter/config/RateLimiterConfiguration.java b/tashow-framework/tashow-framework-protection/src/main/java/com/tashow/cloud/protection/ratelimiter/config/RateLimiterConfiguration.java new file mode 100644 index 0000000..b982bf4 --- /dev/null +++ b/tashow-framework/tashow-framework-protection/src/main/java/com/tashow/cloud/protection/ratelimiter/config/RateLimiterConfiguration.java @@ -0,0 +1,55 @@ +package com.tashow.cloud.protection.ratelimiter.config; + +import com.tashow.cloud.protection.ratelimiter.core.aop.RateLimiterAspect; +import com.tashow.cloud.protection.ratelimiter.core.keyresolver.RateLimiterKeyResolver; +import com.tashow.cloud.protection.ratelimiter.core.keyresolver.impl.*; +import com.tashow.cloud.redis.config.TashowRedisAutoConfiguration; +import com.tashow.cloud.protection.ratelimiter.core.redis.RateLimiterRedisDAO; +import org.redisson.api.RedissonClient; +import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.context.annotation.Bean; + +import java.util.List; + +@AutoConfiguration(after = TashowRedisAutoConfiguration.class) +public class RateLimiterConfiguration { + + @Bean + public RateLimiterAspect rateLimiterAspect(List keyResolvers, RateLimiterRedisDAO rateLimiterRedisDAO) { + return new RateLimiterAspect(keyResolvers, rateLimiterRedisDAO); + } + + @Bean + @SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection") + public RateLimiterRedisDAO rateLimiterRedisDAO(RedissonClient redissonClient) { + return new RateLimiterRedisDAO(redissonClient); + } + + // ========== 各种 RateLimiterRedisDAO Bean ========== + + @Bean + public DefaultRateLimiterKeyResolver defaultRateLimiterKeyResolver() { + return new DefaultRateLimiterKeyResolver(); + } + + @Bean + public UserRateLimiterKeyResolver userRateLimiterKeyResolver() { + return new UserRateLimiterKeyResolver(); + } + + @Bean + public ClientIpRateLimiterKeyResolver clientIpRateLimiterKeyResolver() { + return new ClientIpRateLimiterKeyResolver(); + } + + @Bean + public ServerNodeRateLimiterKeyResolver serverNodeRateLimiterKeyResolver() { + return new ServerNodeRateLimiterKeyResolver(); + } + + @Bean + public ExpressionRateLimiterKeyResolver expressionRateLimiterKeyResolver() { + return new ExpressionRateLimiterKeyResolver(); + } + +} diff --git a/tashow-framework/tashow-framework-protection/src/main/java/com/tashow/cloud/protection/ratelimiter/core/annotation/RateLimiter.java b/tashow-framework/tashow-framework-protection/src/main/java/com/tashow/cloud/protection/ratelimiter/core/annotation/RateLimiter.java new file mode 100644 index 0000000..f38237d --- /dev/null +++ b/tashow-framework/tashow-framework-protection/src/main/java/com/tashow/cloud/protection/ratelimiter/core/annotation/RateLimiter.java @@ -0,0 +1,62 @@ +package com.tashow.cloud.protection.ratelimiter.core.annotation; + +import com.tashow.cloud.common.exception.enums.GlobalErrorCodeConstants; +import com.tashow.cloud.protection.idempotent.core.keyresolver.impl.ExpressionIdempotentKeyResolver; +import com.tashow.cloud.protection.ratelimiter.core.keyresolver.RateLimiterKeyResolver; +import com.tashow.cloud.protection.ratelimiter.core.keyresolver.impl.DefaultRateLimiterKeyResolver; +import com.tashow.cloud.protection.ratelimiter.core.keyresolver.impl.UserRateLimiterKeyResolver; +import com.tashow.cloud.protection.ratelimiter.core.keyresolver.impl.ClientIpRateLimiterKeyResolver; +import com.tashow.cloud.protection.ratelimiter.core.keyresolver.impl.ServerNodeRateLimiterKeyResolver; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import java.util.concurrent.TimeUnit; + +/** + * 限流注解 + * + * @author 芋道源码 + */ +@Target({ElementType.METHOD}) +@Retention(RetentionPolicy.RUNTIME) +public @interface RateLimiter { + + /** + * 限流的时间,默认为 1 秒 + */ + int time() default 1; + /** + * 时间单位,默认为 SECONDS 秒 + */ + TimeUnit timeUnit() default TimeUnit.SECONDS; + + /** + * 限流次数 + */ + int count() default 100; + + /** + * 提示信息,请求过快的提示 + * + * @see GlobalErrorCodeConstants#TOO_MANY_REQUESTS + */ + String message() default ""; // 为空时,使用 TOO_MANY_REQUESTS 错误提示 + + /** + * 使用的 Key 解析器 + * + * @see DefaultRateLimiterKeyResolver 全局级别 + * @see UserRateLimiterKeyResolver 用户 ID 级别 + * @see ClientIpRateLimiterKeyResolver 用户 IP 级别 + * @see ServerNodeRateLimiterKeyResolver 服务器 Node 级别 + * @see ExpressionIdempotentKeyResolver 自定义表达式,通过 {@link #keyArg()} 计算 + */ + Class keyResolver() default DefaultRateLimiterKeyResolver.class; + /** + * 使用的 Key 参数 + */ + String keyArg() default ""; + +} diff --git a/tashow-framework/tashow-framework-protection/src/main/java/com/tashow/cloud/protection/ratelimiter/core/aop/RateLimiterAspect.java b/tashow-framework/tashow-framework-protection/src/main/java/com/tashow/cloud/protection/ratelimiter/core/aop/RateLimiterAspect.java new file mode 100644 index 0000000..44f18e6 --- /dev/null +++ b/tashow-framework/tashow-framework-protection/src/main/java/com/tashow/cloud/protection/ratelimiter/core/aop/RateLimiterAspect.java @@ -0,0 +1,60 @@ +package com.tashow.cloud.protection.ratelimiter.core.aop; + +import cn.hutool.core.util.StrUtil; +import com.tashow.cloud.common.exception.ServiceException; +import com.tashow.cloud.common.exception.enums.GlobalErrorCodeConstants; +import com.tashow.cloud.common.util.collection.CollectionUtils; +import com.tashow.cloud.protection.ratelimiter.core.keyresolver.RateLimiterKeyResolver; +import com.tashow.cloud.protection.ratelimiter.core.annotation.RateLimiter; +import com.tashow.cloud.protection.ratelimiter.core.redis.RateLimiterRedisDAO; +import lombok.extern.slf4j.Slf4j; +import org.aspectj.lang.JoinPoint; +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.annotation.Before; +import org.springframework.util.Assert; + +import java.util.List; +import java.util.Map; + +/** + * 拦截声明了 {@link RateLimiter} 注解的方法,实现限流操作 + * + * @author 芋道源码 + */ +@Aspect +@Slf4j +public class RateLimiterAspect { + + /** + * RateLimiterKeyResolver 集合 + */ + private final Map, RateLimiterKeyResolver> keyResolvers; + + private final RateLimiterRedisDAO rateLimiterRedisDAO; + + public RateLimiterAspect(List keyResolvers, RateLimiterRedisDAO rateLimiterRedisDAO) { + this.keyResolvers = CollectionUtils.convertMap(keyResolvers, RateLimiterKeyResolver::getClass); + this.rateLimiterRedisDAO = rateLimiterRedisDAO; + } + + @Before("@annotation(rateLimiter)") + public void beforePointCut(JoinPoint joinPoint, RateLimiter rateLimiter) { + // 获得 IdempotentKeyResolver 对象 + RateLimiterKeyResolver keyResolver = keyResolvers.get(rateLimiter.keyResolver()); + Assert.notNull(keyResolver, "找不到对应的 RateLimiterKeyResolver"); + // 解析 Key + String key = keyResolver.resolver(joinPoint, rateLimiter); + + // 获取 1 次限流 + boolean success = rateLimiterRedisDAO.tryAcquire(key, + rateLimiter.count(), rateLimiter.time(), rateLimiter.timeUnit()); + if (!success) { + log.info("[beforePointCut][方法({}) 参数({}) 请求过于频繁]", joinPoint.getSignature().toString(), joinPoint.getArgs()); + String message = StrUtil.blankToDefault(rateLimiter.message(), + GlobalErrorCodeConstants.TOO_MANY_REQUESTS.getMsg()); + throw new ServiceException(GlobalErrorCodeConstants.TOO_MANY_REQUESTS.getCode(), message); + } + } + +} + diff --git a/tashow-framework/tashow-framework-protection/src/main/java/com/tashow/cloud/protection/ratelimiter/core/keyresolver/RateLimiterKeyResolver.java b/tashow-framework/tashow-framework-protection/src/main/java/com/tashow/cloud/protection/ratelimiter/core/keyresolver/RateLimiterKeyResolver.java new file mode 100644 index 0000000..fc698b0 --- /dev/null +++ b/tashow-framework/tashow-framework-protection/src/main/java/com/tashow/cloud/protection/ratelimiter/core/keyresolver/RateLimiterKeyResolver.java @@ -0,0 +1,22 @@ +package com.tashow.cloud.protection.ratelimiter.core.keyresolver; + +import com.tashow.cloud.protection.ratelimiter.core.annotation.RateLimiter; +import org.aspectj.lang.JoinPoint; + +/** + * 限流 Key 解析器接口 + * + * @author 芋道源码 + */ +public interface RateLimiterKeyResolver { + + /** + * 解析一个 Key + * + * @param rateLimiter 限流注解 + * @param joinPoint AOP 切面 + * @return Key + */ + String resolver(JoinPoint joinPoint, RateLimiter rateLimiter); + +} diff --git a/tashow-framework/tashow-framework-protection/src/main/java/com/tashow/cloud/protection/ratelimiter/core/keyresolver/impl/ClientIpRateLimiterKeyResolver.java b/tashow-framework/tashow-framework-protection/src/main/java/com/tashow/cloud/protection/ratelimiter/core/keyresolver/impl/ClientIpRateLimiterKeyResolver.java new file mode 100644 index 0000000..bb7fdbc --- /dev/null +++ b/tashow-framework/tashow-framework-protection/src/main/java/com/tashow/cloud/protection/ratelimiter/core/keyresolver/impl/ClientIpRateLimiterKeyResolver.java @@ -0,0 +1,27 @@ +package com.tashow.cloud.protection.ratelimiter.core.keyresolver.impl; + +import cn.hutool.core.util.StrUtil; +import cn.hutool.crypto.SecureUtil; +import com.tashow.cloud.common.util.servlet.ServletUtils; +import com.tashow.cloud.protection.ratelimiter.core.annotation.RateLimiter; +import com.tashow.cloud.protection.ratelimiter.core.keyresolver.RateLimiterKeyResolver; +import org.aspectj.lang.JoinPoint; + +/** + * IP 级别的限流 Key 解析器,使用方法名 + 方法参数 + IP,组装成一个 Key + * + * 为了避免 Key 过长,使用 MD5 进行“压缩” + * + * @author 芋道源码 + */ +public class ClientIpRateLimiterKeyResolver implements RateLimiterKeyResolver { + + @Override + public String resolver(JoinPoint joinPoint, RateLimiter rateLimiter) { + String methodName = joinPoint.getSignature().toString(); + String argsStr = StrUtil.join(",", joinPoint.getArgs()); + String clientIp = ServletUtils.getClientIP(); + return SecureUtil.md5(methodName + argsStr + clientIp); + } + +} diff --git a/tashow-framework/tashow-framework-protection/src/main/java/com/tashow/cloud/protection/ratelimiter/core/keyresolver/impl/DefaultRateLimiterKeyResolver.java b/tashow-framework/tashow-framework-protection/src/main/java/com/tashow/cloud/protection/ratelimiter/core/keyresolver/impl/DefaultRateLimiterKeyResolver.java new file mode 100644 index 0000000..7f25e44 --- /dev/null +++ b/tashow-framework/tashow-framework-protection/src/main/java/com/tashow/cloud/protection/ratelimiter/core/keyresolver/impl/DefaultRateLimiterKeyResolver.java @@ -0,0 +1,25 @@ +package com.tashow.cloud.protection.ratelimiter.core.keyresolver.impl; + +import cn.hutool.core.util.StrUtil; +import cn.hutool.crypto.SecureUtil; +import com.tashow.cloud.protection.ratelimiter.core.annotation.RateLimiter; +import com.tashow.cloud.protection.ratelimiter.core.keyresolver.RateLimiterKeyResolver; +import org.aspectj.lang.JoinPoint; + +/** + * 默认(全局级别)限流 Key 解析器,使用方法名 + 方法参数,组装成一个 Key + * + * 为了避免 Key 过长,使用 MD5 进行“压缩” + * + * @author 芋道源码 + */ +public class DefaultRateLimiterKeyResolver implements RateLimiterKeyResolver { + + @Override + public String resolver(JoinPoint joinPoint, RateLimiter rateLimiter) { + String methodName = joinPoint.getSignature().toString(); + String argsStr = StrUtil.join(",", joinPoint.getArgs()); + return SecureUtil.md5(methodName + argsStr); + } + +} diff --git a/tashow-framework/tashow-framework-protection/src/main/java/com/tashow/cloud/protection/ratelimiter/core/keyresolver/impl/ExpressionRateLimiterKeyResolver.java b/tashow-framework/tashow-framework-protection/src/main/java/com/tashow/cloud/protection/ratelimiter/core/keyresolver/impl/ExpressionRateLimiterKeyResolver.java new file mode 100644 index 0000000..e676c50 --- /dev/null +++ b/tashow-framework/tashow-framework-protection/src/main/java/com/tashow/cloud/protection/ratelimiter/core/keyresolver/impl/ExpressionRateLimiterKeyResolver.java @@ -0,0 +1,65 @@ +package com.tashow.cloud.protection.ratelimiter.core.keyresolver.impl; + +import cn.hutool.core.util.ArrayUtil; +import com.tashow.cloud.protection.ratelimiter.core.keyresolver.RateLimiterKeyResolver; +import com.tashow.cloud.protection.ratelimiter.core.annotation.RateLimiter; +import com.tashow.cloud.protection.ratelimiter.core.keyresolver.RateLimiterKeyResolver; +import org.aspectj.lang.JoinPoint; +import org.aspectj.lang.reflect.MethodSignature; +import org.springframework.core.DefaultParameterNameDiscoverer; +import org.springframework.core.ParameterNameDiscoverer; +import org.springframework.expression.Expression; +import org.springframework.expression.ExpressionParser; +import org.springframework.expression.spel.standard.SpelExpressionParser; +import org.springframework.expression.spel.support.StandardEvaluationContext; + +import java.lang.reflect.Method; + +/** + * 基于 Spring EL 表达式的 {@link RateLimiterKeyResolver} 实现类 + * + * @author 芋道源码 + */ +public class ExpressionRateLimiterKeyResolver implements RateLimiterKeyResolver { + + private final ParameterNameDiscoverer parameterNameDiscoverer = new DefaultParameterNameDiscoverer(); + + private final ExpressionParser expressionParser = new SpelExpressionParser(); + + @Override + public String resolver(JoinPoint joinPoint, RateLimiter rateLimiter) { + // 获得被拦截方法参数名列表 + Method method = getMethod(joinPoint); + Object[] args = joinPoint.getArgs(); + String[] parameterNames = this.parameterNameDiscoverer.getParameterNames(method); + // 准备 Spring EL 表达式解析的上下文 + StandardEvaluationContext evaluationContext = new StandardEvaluationContext(); + if (ArrayUtil.isNotEmpty(parameterNames)) { + for (int i = 0; i < parameterNames.length; i++) { + evaluationContext.setVariable(parameterNames[i], args[i]); + } + } + + // 解析参数 + Expression expression = expressionParser.parseExpression(rateLimiter.keyArg()); + return expression.getValue(evaluationContext, String.class); + } + + private static Method getMethod(JoinPoint point) { + // 处理,声明在类上的情况 + MethodSignature signature = (MethodSignature) point.getSignature(); + Method method = signature.getMethod(); + if (!method.getDeclaringClass().isInterface()) { + return method; + } + + // 处理,声明在接口上的情况 + try { + return point.getTarget().getClass().getDeclaredMethod( + point.getSignature().getName(), method.getParameterTypes()); + } catch (NoSuchMethodException e) { + throw new RuntimeException(e); + } + } + +} diff --git a/tashow-framework/tashow-framework-protection/src/main/java/com/tashow/cloud/protection/ratelimiter/core/keyresolver/impl/ServerNodeRateLimiterKeyResolver.java b/tashow-framework/tashow-framework-protection/src/main/java/com/tashow/cloud/protection/ratelimiter/core/keyresolver/impl/ServerNodeRateLimiterKeyResolver.java new file mode 100644 index 0000000..114e512 --- /dev/null +++ b/tashow-framework/tashow-framework-protection/src/main/java/com/tashow/cloud/protection/ratelimiter/core/keyresolver/impl/ServerNodeRateLimiterKeyResolver.java @@ -0,0 +1,29 @@ +package com.tashow.cloud.protection.ratelimiter.core.keyresolver.impl; + +import cn.hutool.core.util.StrUtil; +import cn.hutool.crypto.SecureUtil; +import cn.hutool.system.SystemUtil; +import com.tashow.cloud.protection.ratelimiter.core.annotation.RateLimiter; +import com.tashow.cloud.protection.ratelimiter.core.keyresolver.RateLimiterKeyResolver; +import com.tashow.cloud.protection.ratelimiter.core.annotation.RateLimiter; +import com.tashow.cloud.protection.ratelimiter.core.keyresolver.RateLimiterKeyResolver; +import org.aspectj.lang.JoinPoint; + +/** + * Server 节点级别的限流 Key 解析器,使用方法名 + 方法参数 + IP,组装成一个 Key + * + * 为了避免 Key 过长,使用 MD5 进行“压缩” + * + * @author 芋道源码 + */ +public class ServerNodeRateLimiterKeyResolver implements RateLimiterKeyResolver { + + @Override + public String resolver(JoinPoint joinPoint, RateLimiter rateLimiter) { + String methodName = joinPoint.getSignature().toString(); + String argsStr = StrUtil.join(",", joinPoint.getArgs()); + String serverNode = String.format("%s@%d", SystemUtil.getHostInfo().getAddress(), SystemUtil.getCurrentPID()); + return SecureUtil.md5(methodName + argsStr + serverNode); + } + +} diff --git a/tashow-framework/tashow-framework-protection/src/main/java/com/tashow/cloud/protection/ratelimiter/core/keyresolver/impl/UserRateLimiterKeyResolver.java b/tashow-framework/tashow-framework-protection/src/main/java/com/tashow/cloud/protection/ratelimiter/core/keyresolver/impl/UserRateLimiterKeyResolver.java new file mode 100644 index 0000000..cea96c4 --- /dev/null +++ b/tashow-framework/tashow-framework-protection/src/main/java/com/tashow/cloud/protection/ratelimiter/core/keyresolver/impl/UserRateLimiterKeyResolver.java @@ -0,0 +1,28 @@ +package com.tashow.cloud.protection.ratelimiter.core.keyresolver.impl; + +import cn.hutool.core.util.StrUtil; +import cn.hutool.crypto.SecureUtil; +import com.tashow.cloud.protection.ratelimiter.core.annotation.RateLimiter; +import com.tashow.cloud.protection.ratelimiter.core.keyresolver.RateLimiterKeyResolver; +import com.tashow.cloud.web.web.core.util.WebFrameworkUtils; +import org.aspectj.lang.JoinPoint; + +/** + * 用户级别的限流 Key 解析器,使用方法名 + 方法参数 + userId + userType,组装成一个 Key + * + * 为了避免 Key 过长,使用 MD5 进行“压缩” + * + * @author 芋道源码 + */ +public class UserRateLimiterKeyResolver implements RateLimiterKeyResolver { + + @Override + public String resolver(JoinPoint joinPoint, RateLimiter rateLimiter) { + String methodName = joinPoint.getSignature().toString(); + String argsStr = StrUtil.join(",", joinPoint.getArgs()); + Long userId = WebFrameworkUtils.getLoginUserId(); + Integer userType = WebFrameworkUtils.getLoginUserType(); + return SecureUtil.md5(methodName + argsStr + userId + userType); + } + +} diff --git a/tashow-framework/tashow-framework-protection/src/main/java/com/tashow/cloud/protection/ratelimiter/core/redis/RateLimiterRedisDAO.java b/tashow-framework/tashow-framework-protection/src/main/java/com/tashow/cloud/protection/ratelimiter/core/redis/RateLimiterRedisDAO.java new file mode 100644 index 0000000..4592189 --- /dev/null +++ b/tashow-framework/tashow-framework-protection/src/main/java/com/tashow/cloud/protection/ratelimiter/core/redis/RateLimiterRedisDAO.java @@ -0,0 +1,60 @@ +package com.tashow.cloud.protection.ratelimiter.core.redis; + +import lombok.AllArgsConstructor; +import org.redisson.api.*; + +import java.util.Objects; +import java.util.concurrent.TimeUnit; + +/** + * 限流 Redis DAO + * + * @author 芋道源码 + */ +@AllArgsConstructor +public class RateLimiterRedisDAO { + + /** + * 限流操作 + * + * KEY 格式:rate_limiter:%s // 参数为 uuid + * VALUE 格式:String + * 过期时间:不固定 + */ + private static final String RATE_LIMITER = "rate_limiter:%s"; + + private final RedissonClient redissonClient; + + public Boolean tryAcquire(String key, int count, int time, TimeUnit timeUnit) { + // 1. 获得 RRateLimiter,并设置 rate 速率 + RRateLimiter rateLimiter = getRRateLimiter(key, count, time, timeUnit); + // 2. 尝试获取 1 个 + return rateLimiter.tryAcquire(); + } + + private static String formatKey(String key) { + return String.format(RATE_LIMITER, key); + } + + private RRateLimiter getRRateLimiter(String key, long count, int time, TimeUnit timeUnit) { + String redisKey = formatKey(key); + RRateLimiter rateLimiter = redissonClient.getRateLimiter(redisKey); + long rateInterval = timeUnit.toSeconds(time); + // 1. 如果不存在,设置 rate 速率 + RateLimiterConfig config = rateLimiter.getConfig(); + if (config == null) { + rateLimiter.trySetRate(RateType.OVERALL, count, rateInterval, RateIntervalUnit.SECONDS); + return rateLimiter; + } + // 2. 如果存在,并且配置相同,则直接返回 + if (config.getRateType() == RateType.OVERALL + && Objects.equals(config.getRate(), count) + && Objects.equals(config.getRateInterval(), TimeUnit.SECONDS.toMillis(rateInterval))) { + return rateLimiter; + } + // 3. 如果存在,并且配置不同,则进行新建 + rateLimiter.setRate(RateType.OVERALL, count, rateInterval, RateIntervalUnit.SECONDS); + return rateLimiter; + } + +} diff --git a/tashow-framework/tashow-framework-protection/src/main/java/com/tashow/cloud/protection/ratelimiter/package-info.java b/tashow-framework/tashow-framework-protection/src/main/java/com/tashow/cloud/protection/ratelimiter/package-info.java new file mode 100644 index 0000000..0dd7fbc --- /dev/null +++ b/tashow-framework/tashow-framework-protection/src/main/java/com/tashow/cloud/protection/ratelimiter/package-info.java @@ -0,0 +1,4 @@ +/** + * 限流组件,基于 Redisson {@link org.redisson.api.RRateLimiter} 限流实现 + */ +package com.tashow.cloud.protection.ratelimiter; diff --git a/tashow-framework/tashow-framework-protection/src/main/java/com/tashow/cloud/protection/signature/config/ApiSignatureAutoConfiguration.java b/tashow-framework/tashow-framework-protection/src/main/java/com/tashow/cloud/protection/signature/config/ApiSignatureAutoConfiguration.java new file mode 100644 index 0000000..dbb4268 --- /dev/null +++ b/tashow-framework/tashow-framework-protection/src/main/java/com/tashow/cloud/protection/signature/config/ApiSignatureAutoConfiguration.java @@ -0,0 +1,28 @@ +package com.tashow.cloud.protection.signature.config; + +import com.tashow.cloud.protection.signature.core.redis.ApiSignatureRedisDAO; +import com.tashow.cloud.protection.signature.core.aop.ApiSignatureAspect; +import com.tashow.cloud.redis.config.TashowRedisAutoConfiguration; +import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.context.annotation.Bean; +import org.springframework.data.redis.core.StringRedisTemplate; + +/** + * HTTP API 签名的自动配置类 + * + * @author Zhougang + */ +@AutoConfiguration(after = TashowRedisAutoConfiguration.class) +public class ApiSignatureAutoConfiguration { + + @Bean + public ApiSignatureAspect signatureAspect(ApiSignatureRedisDAO signatureRedisDAO) { + return new ApiSignatureAspect(signatureRedisDAO); + } + + @Bean + public ApiSignatureRedisDAO signatureRedisDAO(StringRedisTemplate stringRedisTemplate) { + return new ApiSignatureRedisDAO(stringRedisTemplate); + } + +} diff --git a/tashow-framework/tashow-framework-protection/src/main/java/com/tashow/cloud/protection/signature/core/annotation/ApiSignature.java b/tashow-framework/tashow-framework-protection/src/main/java/com/tashow/cloud/protection/signature/core/annotation/ApiSignature.java new file mode 100644 index 0000000..3a715db --- /dev/null +++ b/tashow-framework/tashow-framework-protection/src/main/java/com/tashow/cloud/protection/signature/core/annotation/ApiSignature.java @@ -0,0 +1,60 @@ +package com.tashow.cloud.protection.signature.core.annotation; + + +import com.tashow.cloud.common.exception.enums.GlobalErrorCodeConstants; + +import java.lang.annotation.*; +import java.util.concurrent.TimeUnit; + + +/** + * HTTP API 签名注解 + * + * @author Zhougang + */ +@Inherited +@Documented +@Target({ElementType.METHOD, ElementType.TYPE}) +@Retention(RetentionPolicy.RUNTIME) +public @interface ApiSignature { + + /** + * 同一个请求多长时间内有效 默认 60 秒 + */ + int timeout() default 60; + + /** + * 时间单位,默认为 SECONDS 秒 + */ + TimeUnit timeUnit() default TimeUnit.SECONDS; + + // ========================== 签名参数 ========================== + + /** + * 提示信息,签名失败的提示 + * + * @see GlobalErrorCodeConstants#BAD_REQUEST + */ + String message() default "签名不正确"; // 为空时,使用 BAD_REQUEST 错误提示 + + /** + * 签名字段:appId 应用ID + */ + String appId() default "appId"; + + /** + * 签名字段:timestamp 时间戳 + */ + String timestamp() default "timestamp"; + + /** + * 签名字段:nonce 随机数,10 位以上 + */ + String nonce() default "nonce"; + + /** + * sign 客户端签名 + */ + String sign() default "sign"; + +} diff --git a/tashow-framework/tashow-framework-protection/src/main/java/com/tashow/cloud/protection/signature/core/aop/ApiSignatureAspect.java b/tashow-framework/tashow-framework-protection/src/main/java/com/tashow/cloud/protection/signature/core/aop/ApiSignatureAspect.java new file mode 100644 index 0000000..f204dab --- /dev/null +++ b/tashow-framework/tashow-framework-protection/src/main/java/com/tashow/cloud/protection/signature/core/aop/ApiSignatureAspect.java @@ -0,0 +1,169 @@ +package com.tashow.cloud.protection.signature.core.aop; + +import cn.hutool.core.lang.Assert; +import cn.hutool.core.map.MapUtil; +import cn.hutool.core.util.ObjUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.crypto.digest.DigestUtil; +import com.tashow.cloud.common.exception.ServiceException; +import com.tashow.cloud.common.util.servlet.ServletUtils; +import com.tashow.cloud.protection.signature.core.annotation.ApiSignature; +import com.tashow.cloud.protection.signature.core.redis.ApiSignatureRedisDAO; +import jakarta.servlet.http.HttpServletRequest; +import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.aspectj.lang.JoinPoint; +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.annotation.Before; + +import java.util.Map; +import java.util.Objects; +import java.util.SortedMap; +import java.util.TreeMap; + +import static com.tashow.cloud.common.exception.enums.GlobalErrorCodeConstants.BAD_REQUEST; + + +/** + * 拦截声明了 {@link ApiSignature} 注解的方法,实现签名 + * + * @author Zhougang + */ +@Aspect +@Slf4j +@AllArgsConstructor +public class ApiSignatureAspect { + + private final ApiSignatureRedisDAO signatureRedisDAO; + + @Before("@annotation(signature)") + public void beforePointCut(JoinPoint joinPoint, ApiSignature signature) { + // 1. 验证通过,直接结束 + if (verifySignature(signature, Objects.requireNonNull(ServletUtils.getRequest()))) { + return; + } + + // 2. 验证不通过,抛出异常 + log.error("[beforePointCut][方法{} 参数({}) 签名失败]", joinPoint.getSignature().toString(), + joinPoint.getArgs()); + throw new ServiceException(BAD_REQUEST.getCode(), + StrUtil.blankToDefault(signature.message(), BAD_REQUEST.getMsg())); + } + + public boolean verifySignature(ApiSignature signature, HttpServletRequest request) { + // 1.1 校验 Header + if (!verifyHeaders(signature, request)) { + return false; + } + // 1.2 校验 appId 是否能获取到对应的 appSecret + String appId = request.getHeader(signature.appId()); + String appSecret = signatureRedisDAO.getAppSecret(appId); + Assert.notNull(appSecret, "[appId({})] 找不到对应的 appSecret", appId); + + // 2. 校验签名【重要!】 + String clientSignature = request.getHeader(signature.sign()); // 客户端签名 + String serverSignatureString = buildSignatureString(signature, request, appSecret); // 服务端签名字符串 + String serverSignature = DigestUtil.sha256Hex(serverSignatureString); // 服务端签名 + if (ObjUtil.notEqual(clientSignature, serverSignature)) { + return false; + } + + // 3. 将 nonce 记入缓存,防止重复使用(重点二:此处需要将 ttl 设定为允许 timestamp 时间差的值 x 2 ) + String nonce = request.getHeader(signature.nonce()); + signatureRedisDAO.setNonce(appId, nonce, signature.timeout() * 2, signature.timeUnit()); + return true; + } + + /** + * 校验请求头加签参数 + * + * 1. appId 是否为空 + * 2. timestamp 是否为空,请求是否已经超时,默认 10 分钟 + * 3. nonce 是否为空,随机数是否 10 位以上,是否在规定时间内已经访问过了 + * 4. sign 是否为空 + * + * @param signature signature + * @param request request + * @return 是否校验 Header 通过 + */ + private boolean verifyHeaders(ApiSignature signature, HttpServletRequest request) { + // 1. 非空校验 + String appId = request.getHeader(signature.appId()); + if (StrUtil.isBlank(appId)) { + return false; + } + String timestamp = request.getHeader(signature.timestamp()); + if (StrUtil.isBlank(timestamp)) { + return false; + } + String nonce = request.getHeader(signature.nonce()); + if (StrUtil.length(nonce) < 10) { + return false; + } + String sign = request.getHeader(signature.sign()); + if (StrUtil.isBlank(sign)) { + return false; + } + + // 2. 检查 timestamp 是否超出允许的范围 (重点一:此处需要取绝对值) + long expireTime = signature.timeUnit().toMillis(signature.timeout()); + long requestTimestamp = Long.parseLong(timestamp); + long timestampDisparity = Math.abs(System.currentTimeMillis() - requestTimestamp); + if (timestampDisparity > expireTime) { + return false; + } + + // 3. 检查 nonce 是否存在,有且仅能使用一次 + return signatureRedisDAO.getNonce(appId, nonce) == null; + } + + /** + * 构建签名字符串 + * + * 格式为 = 请求参数 + 请求体 + 请求头 + 密钥 + * + * @param signature signature + * @param request request + * @param appSecret appSecret + * @return 签名字符串 + */ + private String buildSignatureString(ApiSignature signature, HttpServletRequest request, String appSecret) { + SortedMap parameterMap = getRequestParameterMap(request); // 请求头 + SortedMap headerMap = getRequestHeaderMap(signature, request); // 请求参数 + String requestBody = StrUtil.nullToDefault(ServletUtils.getBody(request), ""); // 请求体 + return MapUtil.join(parameterMap, "&", "=") + + requestBody + + MapUtil.join(headerMap, "&", "=") + + appSecret; + } + + /** + * 获取请求头加签参数 Map + * + * @param request 请求 + * @param signature 签名注解 + * @return signature params + */ + private static SortedMap getRequestHeaderMap(ApiSignature signature, HttpServletRequest request) { + SortedMap sortedMap = new TreeMap<>(); + sortedMap.put(signature.appId(), request.getHeader(signature.appId())); + sortedMap.put(signature.timestamp(), request.getHeader(signature.timestamp())); + sortedMap.put(signature.nonce(), request.getHeader(signature.nonce())); + return sortedMap; + } + + /** + * 获取请求参数 Map + * + * @param request 请求 + * @return queryParams + */ + private static SortedMap getRequestParameterMap(HttpServletRequest request) { + SortedMap sortedMap = new TreeMap<>(); + for (Map.Entry entry : request.getParameterMap().entrySet()) { + sortedMap.put(entry.getKey(), entry.getValue()[0]); + } + return sortedMap; + } + +} diff --git a/tashow-framework/tashow-framework-protection/src/main/java/com/tashow/cloud/protection/signature/core/redis/ApiSignatureRedisDAO.java b/tashow-framework/tashow-framework-protection/src/main/java/com/tashow/cloud/protection/signature/core/redis/ApiSignatureRedisDAO.java new file mode 100644 index 0000000..c3c41c1 --- /dev/null +++ b/tashow-framework/tashow-framework-protection/src/main/java/com/tashow/cloud/protection/signature/core/redis/ApiSignatureRedisDAO.java @@ -0,0 +1,57 @@ +package com.tashow.cloud.protection.signature.core.redis; + +import lombok.AllArgsConstructor; +import org.springframework.data.redis.core.StringRedisTemplate; + +import java.util.concurrent.TimeUnit; + +/** + * HTTP API 签名 Redis DAO + * + * @author Zhougang + */ +@AllArgsConstructor +public class ApiSignatureRedisDAO { + + private final StringRedisTemplate stringRedisTemplate; + + /** + * 验签随机数 + * + * KEY 格式:signature_nonce:%s // 参数为 随机数 + * VALUE 格式:String + * 过期时间:不固定 + */ + private static final String SIGNATURE_NONCE = "api_signature_nonce:%s:%s"; + + /** + * 签名密钥 + * + * HASH 结构 + * KEY 格式:%s // 参数为 appid + * VALUE 格式:String + * 过期时间:永不过期(预加载到 Redis) + */ + private static final String SIGNATURE_APPID = "api_signature_app"; + + // ========== 验签随机数 ========== + + public String getNonce(String appId, String nonce) { + return stringRedisTemplate.opsForValue().get(formatNonceKey(appId, nonce)); + } + + public void setNonce(String appId, String nonce, int time, TimeUnit timeUnit) { + stringRedisTemplate.opsForValue().set(formatNonceKey(appId, nonce), "", time, timeUnit); + } + + private static String formatNonceKey(String appId, String nonce) { + return String.format(SIGNATURE_NONCE, appId, nonce); + } + + // ========== 签名密钥 ========== + + public String getAppSecret(String appId) { + return (String) stringRedisTemplate.opsForHash().get(SIGNATURE_APPID, appId); + } + +} diff --git a/tashow-framework/tashow-framework-protection/src/main/java/com/tashow/cloud/protection/signature/package-info.java b/tashow-framework/tashow-framework-protection/src/main/java/com/tashow/cloud/protection/signature/package-info.java new file mode 100644 index 0000000..689d9c8 --- /dev/null +++ b/tashow-framework/tashow-framework-protection/src/main/java/com/tashow/cloud/protection/signature/package-info.java @@ -0,0 +1,6 @@ +/** + * HTTP API 签名,校验安全性 + * + * @see + + + com.tashow.cloud + tashow-framework + ${revision} + + 4.0.0 + tashow-framework-rpc + jar + + ${project.artifactId} + + OpenFeign:提供 RESTful API 的调用 + + + + + com.tashow.cloud + tashow-common + + + + + org.springframework.cloud + spring-cloud-starter-loadbalancer + + + org.springframework.cloud + spring-cloud-starter-openfeign + + + io.github.openfeign + feign-okhttp + + + + + jakarta.validation + jakarta.validation-api + + + diff --git a/tashow-framework/tashow-framework-rpc/src/main/java/com/tashow/cloud/rpc/config/package-info.java b/tashow-framework/tashow-framework-rpc/src/main/java/com/tashow/cloud/rpc/config/package-info.java new file mode 100644 index 0000000..ff499d0 --- /dev/null +++ b/tashow-framework/tashow-framework-rpc/src/main/java/com/tashow/cloud/rpc/config/package-info.java @@ -0,0 +1,4 @@ +/** + * 占坑 TODO + */ +package com.tashow.cloud.rpc.config; diff --git a/tashow-framework/tashow-framework-rpc/src/main/java/com/tashow/cloud/rpc/core/package-info.java b/tashow-framework/tashow-framework-rpc/src/main/java/com/tashow/cloud/rpc/core/package-info.java new file mode 100644 index 0000000..be165b7 --- /dev/null +++ b/tashow-framework/tashow-framework-rpc/src/main/java/com/tashow/cloud/rpc/core/package-info.java @@ -0,0 +1,4 @@ +/** + * 占坑 TODO + */ +package com.tashow.cloud.rpc.core; diff --git a/tashow-framework/tashow-framework-security/pom.xml b/tashow-framework/tashow-framework-security/pom.xml new file mode 100644 index 0000000..f327a7c --- /dev/null +++ b/tashow-framework/tashow-framework-security/pom.xml @@ -0,0 +1,78 @@ + + + + com.tashow.cloud + tashow-framework + ${revision} + + 4.0.0 + tashow-framework-security + jar + + ${project.artifactId} + + 1. security:用户的认证、权限的校验,实现「谁」可以做「什么事」 + 2. operatelog:操作日志,实现「谁」在「什么时间」对「什么」做了「什么事」 + + + + + com.tashow.cloud + tashow-common + + + + + org.springframework.boot + spring-boot-starter-aop + + + + + com.tashow.cloud + tashow-framework-web + + + + org.springframework.boot + spring-boot-configuration-processor + true + + + + org.springframework.boot + spring-boot-starter-security + + + + + com.tashow.cloud + tashow-framework-rpc + true + + + + + com.tashow.cloud + tashow-system-api + ${revision} + + + + + com.google.guava + guava + + + + + + io.github.mouzt + bizlog-sdk + + + + + diff --git a/tashow-framework/tashow-framework-security/src/main/java/com/tashow/cloud/security/operatelog/config/OperateLogConfiguration.java b/tashow-framework/tashow-framework-security/src/main/java/com/tashow/cloud/security/operatelog/config/OperateLogConfiguration.java new file mode 100644 index 0000000..e69a2ba --- /dev/null +++ b/tashow-framework/tashow-framework-security/src/main/java/com/tashow/cloud/security/operatelog/config/OperateLogConfiguration.java @@ -0,0 +1,27 @@ +package com.tashow.cloud.security.operatelog.config; + +import com.mzt.logapi.service.ILogRecordService; +import com.mzt.logapi.starter.annotation.EnableLogRecord; +import com.tashow.cloud.security.operatelog.core.service.LogRecordServiceImpl; +import lombok.extern.slf4j.Slf4j; +import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Primary; + +/** + * 操作日志配置类 + * + * @author HUIHUI + */ +@EnableLogRecord(tenant = "") // 貌似用不上 tenant 这玩意给个空好啦 +@AutoConfiguration +@Slf4j +public class OperateLogConfiguration { + + @Bean + @Primary + public ILogRecordService iLogRecordServiceImpl() { + return new LogRecordServiceImpl(); + } + +} diff --git a/tashow-framework/tashow-framework-security/src/main/java/com/tashow/cloud/security/operatelog/config/OperateLogRpcAutoConfiguration.java b/tashow-framework/tashow-framework-security/src/main/java/com/tashow/cloud/security/operatelog/config/OperateLogRpcAutoConfiguration.java new file mode 100644 index 0000000..bbe48fc --- /dev/null +++ b/tashow-framework/tashow-framework-security/src/main/java/com/tashow/cloud/security/operatelog/config/OperateLogRpcAutoConfiguration.java @@ -0,0 +1,15 @@ +package com.tashow.cloud.security.operatelog.config; + +import com.tashow.cloud.systemapi.api.logger.OperateLogApi; +import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.cloud.openfeign.EnableFeignClients; + +/** + * OperateLog 使用到 Feign 的配置项 + * + * @author 芋道源码 + */ +@AutoConfiguration +@EnableFeignClients(clients = {OperateLogApi.class}) // 主要是引入相关的 API 服务 +public class OperateLogRpcAutoConfiguration { +} diff --git a/tashow-framework/tashow-framework-security/src/main/java/com/tashow/cloud/security/operatelog/core/package-info.java b/tashow-framework/tashow-framework-security/src/main/java/com/tashow/cloud/security/operatelog/core/package-info.java new file mode 100644 index 0000000..92fe6f3 --- /dev/null +++ b/tashow-framework/tashow-framework-security/src/main/java/com/tashow/cloud/security/operatelog/core/package-info.java @@ -0,0 +1,4 @@ +/** + * 占位,无特殊作用 + */ +package com.tashow.cloud.security.operatelog.core; diff --git a/tashow-framework/tashow-framework-security/src/main/java/com/tashow/cloud/security/operatelog/core/service/LogRecordServiceImpl.java b/tashow-framework/tashow-framework-security/src/main/java/com/tashow/cloud/security/operatelog/core/service/LogRecordServiceImpl.java new file mode 100644 index 0000000..026613a --- /dev/null +++ b/tashow-framework/tashow-framework-security/src/main/java/com/tashow/cloud/security/operatelog/core/service/LogRecordServiceImpl.java @@ -0,0 +1,91 @@ +package com.tashow.cloud.security.operatelog.core.service; + +import com.tashow.cloud.systemapi.api.logger.OperateLogApi; +import com.tashow.cloud.systemapi.api.logger.dto.OperateLogCreateReqDTO; +import com.mzt.logapi.beans.LogRecord; +import com.mzt.logapi.service.ILogRecordService; +import com.tashow.cloud.common.util.monitor.TracerUtils; +import com.tashow.cloud.common.util.servlet.ServletUtils; +import com.tashow.cloud.security.security.core.LoginUser; +import com.tashow.cloud.security.security.core.util.SecurityFrameworkUtils; +import jakarta.annotation.Resource; +import jakarta.servlet.http.HttpServletRequest; +import lombok.extern.slf4j.Slf4j; + +import java.util.List; + +/** + * 操作日志 ILogRecordService 实现类 + * + * 基于 {@link OperateLogApi} 实现,记录操作日志 + * + * @author HUIHUI + */ +@Slf4j +public class LogRecordServiceImpl implements ILogRecordService { + + @Resource + private OperateLogApi operateLogApi; + + @Override + public void record(LogRecord logRecord) { + OperateLogCreateReqDTO reqDTO = new OperateLogCreateReqDTO(); + try { + reqDTO.setTraceId(TracerUtils.getTraceId()); + // 补充用户信息 + fillUserFields(reqDTO); + // 补全模块信息 + fillModuleFields(reqDTO, logRecord); + // 补全请求信息 + fillRequestFields(reqDTO); + + // 2. 异步记录日志 + operateLogApi.createOperateLogAsync(reqDTO); + } catch (Throwable ex) { + // 由于 @Async 异步调用,这里打印下日志,更容易跟进 + log.error("[record][url({}) log({}) 发生异常]", reqDTO.getRequestUrl(), reqDTO, ex); + } + } + + private static void fillUserFields(OperateLogCreateReqDTO reqDTO) { + // 使用 SecurityFrameworkUtils。因为要考虑,rpc、mq、job,它其实不是 web; + LoginUser loginUser = SecurityFrameworkUtils.getLoginUser(); + if (loginUser == null) { + return; + } + reqDTO.setUserId(loginUser.getId()); + reqDTO.setUserType(loginUser.getUserType()); + } + + public static void fillModuleFields(OperateLogCreateReqDTO reqDTO, LogRecord logRecord) { + reqDTO.setType(logRecord.getType()); // 大模块类型,例如:CRM 客户 + reqDTO.setSubType(logRecord.getSubType());// 操作名称,例如:转移客户 + reqDTO.setBizId(Long.parseLong(logRecord.getBizNo())); // 业务编号,例如:客户编号 + reqDTO.setAction(logRecord.getAction());// 操作内容,例如:修改编号为 1 的用户信息,将性别从男改成女,将姓名从芋道改成源码。 + reqDTO.setExtra(logRecord.getExtra()); // 拓展字段,有些复杂的业务,需要记录一些字段 ( JSON 格式 ),例如说,记录订单编号,{ orderId: "1"} + } + + private static void fillRequestFields(OperateLogCreateReqDTO reqDTO) { + // 获得 Request 对象 + HttpServletRequest request = ServletUtils.getRequest(); + if (request == null) { + return; + } + // 补全请求信息 + reqDTO.setRequestMethod(request.getMethod()); + reqDTO.setRequestUrl(request.getRequestURI()); + reqDTO.setUserIp(ServletUtils.getClientIP(request)); + reqDTO.setUserAgent(ServletUtils.getUserAgent(request)); + } + + @Override + public List queryLog(String bizNo, String type) { + throw new UnsupportedOperationException("使用 OperateLogApi 进行操作日志的查询"); + } + + @Override + public List queryLogByBizNo(String bizNo, String type, String subType) { + throw new UnsupportedOperationException("使用 OperateLogApi 进行操作日志的查询"); + } + +} diff --git a/tashow-framework/tashow-framework-security/src/main/java/com/tashow/cloud/security/operatelog/package-info.java b/tashow-framework/tashow-framework-security/src/main/java/com/tashow/cloud/security/operatelog/package-info.java new file mode 100644 index 0000000..23bd5b1 --- /dev/null +++ b/tashow-framework/tashow-framework-security/src/main/java/com/tashow/cloud/security/operatelog/package-info.java @@ -0,0 +1,7 @@ +/** + * 基于 mzt-log 框架 + * 实现操作日志功能 + * + * @author HUIHUI + */ +package com.tashow.cloud.security.operatelog; diff --git a/tashow-framework/tashow-framework-security/src/main/java/com/tashow/cloud/security/security/config/AuthorizeRequestsCustomizer.java b/tashow-framework/tashow-framework-security/src/main/java/com/tashow/cloud/security/security/config/AuthorizeRequestsCustomizer.java new file mode 100644 index 0000000..7d1a6f7 --- /dev/null +++ b/tashow-framework/tashow-framework-security/src/main/java/com/tashow/cloud/security/security/config/AuthorizeRequestsCustomizer.java @@ -0,0 +1,35 @@ +package com.tashow.cloud.security.security.config; + +import com.tashow.cloud.web.web.config.WebProperties; +import jakarta.annotation.Resource; +import org.springframework.core.Ordered; +import org.springframework.security.config.Customizer; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configurers.AuthorizeHttpRequestsConfigurer; + +/** + * 自定义的 URL 的安全配置 + * 目的:每个 Maven Module 可以自定义规则! + * + * @author 芋道源码 + */ +public abstract class AuthorizeRequestsCustomizer + implements Customizer.AuthorizationManagerRequestMatcherRegistry>, Ordered { + + @Resource + private WebProperties webProperties; + + protected String buildAdminApi(String url) { + return webProperties.getAdminApi().getPrefix() + url; + } + + protected String buildAppApi(String url) { + return webProperties.getAppApi().getPrefix() + url; + } + + @Override + public int getOrder() { + return 0; + } + +} diff --git a/tashow-framework/tashow-framework-security/src/main/java/com/tashow/cloud/security/security/config/SecurityAutoConfiguration.java b/tashow-framework/tashow-framework-security/src/main/java/com/tashow/cloud/security/security/config/SecurityAutoConfiguration.java new file mode 100644 index 0000000..f782d3f --- /dev/null +++ b/tashow-framework/tashow-framework-security/src/main/java/com/tashow/cloud/security/security/config/SecurityAutoConfiguration.java @@ -0,0 +1,94 @@ +package com.tashow.cloud.security.security.config; + +import com.tashow.cloud.systemapi.api.oauth2.OAuth2TokenApi; +import com.tashow.cloud.systemapi.api.permission.PermissionApi; +import com.tashow.cloud.security.security.core.context.TransmittableThreadLocalSecurityContextHolderStrategy; +import com.tashow.cloud.security.security.core.filter.TokenAuthenticationFilter; +import com.tashow.cloud.security.security.core.handler.AccessDeniedHandlerImpl; +import com.tashow.cloud.security.security.core.handler.AuthenticationEntryPointImpl; +import com.tashow.cloud.security.security.core.service.SecurityFrameworkService; +import com.tashow.cloud.security.security.core.service.SecurityFrameworkServiceImpl; +import com.tashow.cloud.web.web.core.handler.GlobalExceptionHandler; +import jakarta.annotation.Resource; +import org.springframework.beans.factory.config.MethodInvokingFactoryBean; +import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.boot.autoconfigure.AutoConfigureOrder; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.security.web.AuthenticationEntryPoint; +import org.springframework.security.web.access.AccessDeniedHandler; + +/** + * Spring Security 自动配置类,主要用于相关组件的配置 + * + * 注意,不能和 {@link WebSecurityConfigurerAdapter} 用一个,原因是会导致初始化报错。 + * 参见 https://stackoverflow.com/questions/53847050/spring-boot-delegatebuilder-cannot-be-null-on-autowiring-authenticationmanager 文档。 + * + * @author 芋道源码 + */ +@AutoConfiguration +@AutoConfigureOrder(-1) // 目的:先于 Spring Security 自动配置,避免一键改包后,org.* 基础包无法生效 +@EnableConfigurationProperties(SecurityProperties.class) +public class SecurityAutoConfiguration { + + @Resource + private SecurityProperties securityProperties; + + /** + * 认证失败处理类 Bean + */ + @Bean + public AuthenticationEntryPoint authenticationEntryPoint() { + return new AuthenticationEntryPointImpl(); + } + + /** + * 权限不够处理器 Bean + */ + @Bean + public AccessDeniedHandler accessDeniedHandler() { + return new AccessDeniedHandlerImpl(); + } + + /** + * Spring Security 加密器 + * 考虑到安全性,这里采用 BCryptPasswordEncoder 加密器 + * + * @see Password Encoding with Spring Security + */ + @Bean + public PasswordEncoder passwordEncoder() { + return new BCryptPasswordEncoder(securityProperties.getPasswordEncoderLength()); + } + + /** + * Token 认证过滤器 Bean + */ + @Bean + public TokenAuthenticationFilter authenticationTokenFilter(GlobalExceptionHandler globalExceptionHandler, + OAuth2TokenApi oauth2TokenApi) { + return new TokenAuthenticationFilter(securityProperties, globalExceptionHandler, oauth2TokenApi); + } + + @Bean("ss") // 使用 Spring Security 的缩写,方便使用 + public SecurityFrameworkService securityFrameworkService(PermissionApi permissionApi) { + return new SecurityFrameworkServiceImpl(permissionApi); + } + + /** + * 声明调用 {@link SecurityContextHolder#setStrategyName(String)} 方法, + * 设置使用 {@link TransmittableThreadLocalSecurityContextHolderStrategy} 作为 Security 的上下文策略 + */ + @Bean + public MethodInvokingFactoryBean securityContextHolderMethodInvokingFactoryBean() { + MethodInvokingFactoryBean methodInvokingFactoryBean = new MethodInvokingFactoryBean(); + methodInvokingFactoryBean.setTargetClass(SecurityContextHolder.class); + methodInvokingFactoryBean.setTargetMethod("setStrategyName"); + methodInvokingFactoryBean.setArguments(TransmittableThreadLocalSecurityContextHolderStrategy.class.getName()); + return methodInvokingFactoryBean; + } + +} diff --git a/tashow-framework/tashow-framework-security/src/main/java/com/tashow/cloud/security/security/config/SecurityProperties.java b/tashow-framework/tashow-framework-security/src/main/java/com/tashow/cloud/security/security/config/SecurityProperties.java new file mode 100644 index 0000000..407b691 --- /dev/null +++ b/tashow-framework/tashow-framework-security/src/main/java/com/tashow/cloud/security/security/config/SecurityProperties.java @@ -0,0 +1,51 @@ +package com.tashow.cloud.security.security.config; + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.validation.annotation.Validated; + +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import java.util.Collections; +import java.util.List; + +@ConfigurationProperties(prefix = "yudao.security") +@Validated +@Data +public class SecurityProperties { + + /** + * HTTP 请求时,访问令牌的请求 Header + */ + @NotEmpty(message = "Token Header 不能为空") + private String tokenHeader = "Authorization"; + /** + * HTTP 请求时,访问令牌的请求参数 + * + * 初始目的:解决 WebSocket 无法通过 header 传参,只能通过 token 参数拼接 + */ + @NotEmpty(message = "Token Parameter 不能为空") + private String tokenParameter = "token"; + + /** + * mock 模式的开关 + */ + @NotNull(message = "mock 模式的开关不能为空") + private Boolean mockEnable = false; + /** + * mock 模式的密钥 + * 一定要配置密钥,保证安全性 + */ + @NotEmpty(message = "mock 模式的密钥不能为空") // 这里设置了一个默认值,因为实际上只有 mockEnable 为 true 时才需要配置。 + private String mockSecret = "test"; + + /** + * 免登录的 URL 列表 + */ + private List permitAllUrls = Collections.emptyList(); + + /** + * PasswordEncoder 加密复杂度,越高开销越大 + */ + private Integer passwordEncoderLength = 4; +} diff --git a/tashow-framework/tashow-framework-security/src/main/java/com/tashow/cloud/security/security/config/SecurityRpcAutoConfiguration.java b/tashow-framework/tashow-framework-security/src/main/java/com/tashow/cloud/security/security/config/SecurityRpcAutoConfiguration.java new file mode 100644 index 0000000..15b3369 --- /dev/null +++ b/tashow-framework/tashow-framework-security/src/main/java/com/tashow/cloud/security/security/config/SecurityRpcAutoConfiguration.java @@ -0,0 +1,25 @@ +package com.tashow.cloud.security.security.config; + +import com.tashow.cloud.systemapi.api.oauth2.OAuth2TokenApi; +import com.tashow.cloud.systemapi.api.permission.PermissionApi; +import com.tashow.cloud.security.security.core.rpc.LoginUserRequestInterceptor; +import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.cloud.openfeign.EnableFeignClients; +import org.springframework.context.annotation.Bean; + +/** + * Security 使用到 Feign 的配置项 + * + * @author 芋道源码 + */ +@AutoConfiguration +@EnableFeignClients(clients = {OAuth2TokenApi.class, // 主要是引入相关的 API 服务 + PermissionApi.class}) +public class SecurityRpcAutoConfiguration { + + @Bean + public LoginUserRequestInterceptor loginUserRequestInterceptor() { + return new LoginUserRequestInterceptor(); + } + +} diff --git a/tashow-framework/tashow-framework-security/src/main/java/com/tashow/cloud/security/security/config/WebSecurityConfigurerAdapter.java b/tashow-framework/tashow-framework-security/src/main/java/com/tashow/cloud/security/security/config/WebSecurityConfigurerAdapter.java new file mode 100644 index 0000000..cc7a93d --- /dev/null +++ b/tashow-framework/tashow-framework-security/src/main/java/com/tashow/cloud/security/security/config/WebSecurityConfigurerAdapter.java @@ -0,0 +1,213 @@ +package com.tashow.cloud.security.security.config; + +import cn.hutool.core.collection.CollUtil; +import com.google.common.collect.HashMultimap; +import com.google.common.collect.Multimap; +import com.tashow.cloud.security.security.core.filter.TokenAuthenticationFilter; +import com.tashow.cloud.web.web.config.WebProperties; +import jakarta.annotation.Resource; +import jakarta.annotation.security.PermitAll; +import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.boot.autoconfigure.AutoConfigureOrder; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.http.HttpMethod; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.config.Customizer; +import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration; +import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; +import org.springframework.security.config.annotation.web.configurers.HeadersConfigurer; +import org.springframework.security.config.http.SessionCreationPolicy; +import org.springframework.security.web.AuthenticationEntryPoint; +import org.springframework.security.web.SecurityFilterChain; +import org.springframework.security.web.access.AccessDeniedHandler; +import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.method.HandlerMethod; +import org.springframework.web.servlet.mvc.method.RequestMappingInfo; +import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping; +import org.springframework.web.util.pattern.PathPattern; + +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import static com.tashow.cloud.common.util.collection.CollectionUtils.convertList; + +/** + * 自定义的 Spring Security 配置适配器实现 + * + * @author 芋道源码 + */ +@AutoConfiguration +@AutoConfigureOrder(-1) // 目的:先于 Spring Security 自动配置,避免一键改包后,org.* 基础包无法生效 +@EnableMethodSecurity(securedEnabled = true) +public class WebSecurityConfigurerAdapter { + + @Resource + private WebProperties webProperties; + @Resource + private SecurityProperties securityProperties; + + /** + * 认证失败处理类 Bean + */ + @Resource + private AuthenticationEntryPoint authenticationEntryPoint; + /** + * 权限不够处理器 Bean + */ + @Resource + private AccessDeniedHandler accessDeniedHandler; + /** + * Token 认证过滤器 Bean + */ + @Resource + private TokenAuthenticationFilter authenticationTokenFilter; + + /** + * 自定义的权限映射 Bean 们 + * + * @see #filterChain(HttpSecurity) + */ + @Resource + private List authorizeRequestsCustomizers; + + @Resource + private ApplicationContext applicationContext; + + /** + * 由于 Spring Security 创建 AuthenticationManager 对象时,没声明 @Bean 注解,导致无法被注入 + * 通过覆写父类的该方法,添加 @Bean 注解,解决该问题 + */ + @Bean + public AuthenticationManager authenticationManagerBean(AuthenticationConfiguration authenticationConfiguration) throws Exception { + return authenticationConfiguration.getAuthenticationManager(); + } + + /** + * 配置 URL 的安全配置 + * + * anyRequest | 匹配所有请求路径 + * access | SpringEl表达式结果为true时可以访问 + * anonymous | 匿名可以访问 + * denyAll | 用户不能访问 + * fullyAuthenticated | 用户完全认证可以访问(非remember-me下自动登录) + * hasAnyAuthority | 如果有参数,参数表示权限,则其中任何一个权限可以访问 + * hasAnyRole | 如果有参数,参数表示角色,则其中任何一个角色可以访问 + * hasAuthority | 如果有参数,参数表示权限,则其权限可以访问 + * hasIpAddress | 如果有参数,参数表示IP地址,如果用户IP和参数匹配,则可以访问 + * hasRole | 如果有参数,参数表示角色,则其角色可以访问 + * permitAll | 用户可以任意访问 + * rememberMe | 允许通过remember-me登录的用户访问 + * authenticated | 用户登录后可访问 + */ + @Bean + protected SecurityFilterChain filterChain(HttpSecurity httpSecurity) throws Exception { + // 登出 + httpSecurity + // 开启跨域 + .cors(Customizer.withDefaults()) + // CSRF 禁用,因为不使用 Session + .csrf(AbstractHttpConfigurer::disable) + // 基于 token 机制,所以不需要 Session + .sessionManagement(c -> c.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) + .headers(c -> c.frameOptions(HeadersConfigurer.FrameOptionsConfig::disable)) + // 一堆自定义的 Spring Security 处理器 + .exceptionHandling(c -> c.authenticationEntryPoint(authenticationEntryPoint) + .accessDeniedHandler(accessDeniedHandler)); + // 登录、登录暂时不使用 Spring Security 的拓展点,主要考虑一方面拓展多用户、多种登录方式相对复杂,一方面用户的学习成本较高 + + // 获得 @PermitAll 带来的 URL 列表,免登录 + Multimap permitAllUrls = getPermitAllUrlsFromAnnotations(); + // 设置每个请求的权限 + httpSecurity + // ①:全局共享规则 + .authorizeHttpRequests(c -> c + // 1.1 静态资源,可匿名访问 + .requestMatchers(HttpMethod.GET, "/*.html", "/*.css", "/*.js").permitAll() + // 1.2 设置 @PermitAll 无需认证 + .requestMatchers(HttpMethod.GET, permitAllUrls.get(HttpMethod.GET).toArray(new String[0])).permitAll() + .requestMatchers(HttpMethod.POST, permitAllUrls.get(HttpMethod.POST).toArray(new String[0])).permitAll() + .requestMatchers(HttpMethod.PUT, permitAllUrls.get(HttpMethod.PUT).toArray(new String[0])).permitAll() + .requestMatchers(HttpMethod.DELETE, permitAllUrls.get(HttpMethod.DELETE).toArray(new String[0])).permitAll() + .requestMatchers(HttpMethod.HEAD, permitAllUrls.get(HttpMethod.HEAD).toArray(new String[0])).permitAll() + .requestMatchers(HttpMethod.PATCH, permitAllUrls.get(HttpMethod.PATCH).toArray(new String[0])).permitAll() + // 1.3 基于 yudao.security.permit-all-urls 无需认证 + .requestMatchers(securityProperties.getPermitAllUrls().toArray(new String[0])).permitAll() + ) + // ②:每个项目的自定义规则 + .authorizeHttpRequests(c -> authorizeRequestsCustomizers.forEach(customizer -> customizer.customize(c))) + // ③:兜底规则,必须认证 + .authorizeHttpRequests(c -> c.anyRequest().authenticated()); + + // 添加 Token Filter + httpSecurity.addFilterBefore(authenticationTokenFilter, UsernamePasswordAuthenticationFilter.class); + return httpSecurity.build(); + } + + private Multimap getPermitAllUrlsFromAnnotations() { + Multimap result = HashMultimap.create(); + // 获得接口对应的 HandlerMethod 集合 + RequestMappingHandlerMapping requestMappingHandlerMapping = (RequestMappingHandlerMapping) + applicationContext.getBean("requestMappingHandlerMapping"); + Map handlerMethodMap = requestMappingHandlerMapping.getHandlerMethods(); + // 获得有 @PermitAll 注解的接口 + for (Map.Entry entry : handlerMethodMap.entrySet()) { + HandlerMethod handlerMethod = entry.getValue(); + if (!handlerMethod.hasMethodAnnotation(PermitAll.class)) { + continue; + } + Set urls = new HashSet<>(); + if (entry.getKey().getPatternsCondition() != null) { + urls.addAll(entry.getKey().getPatternsCondition().getPatterns()); + } + if (entry.getKey().getPathPatternsCondition() != null) { + urls.addAll(convertList(entry.getKey().getPathPatternsCondition().getPatterns(), PathPattern::getPatternString)); + } + if (urls.isEmpty()) { + continue; + } + + // 特殊:使用 @RequestMapping 注解,并且未写 method 属性,此时认为都需要免登录 + Set methods = entry.getKey().getMethodsCondition().getMethods(); + if (CollUtil.isEmpty(methods)) { + result.putAll(HttpMethod.GET, urls); + result.putAll(HttpMethod.POST, urls); + result.putAll(HttpMethod.PUT, urls); + result.putAll(HttpMethod.DELETE, urls); + result.putAll(HttpMethod.HEAD, urls); + result.putAll(HttpMethod.PATCH, urls); + continue; + } + // 根据请求方法,添加到 result 结果 + entry.getKey().getMethodsCondition().getMethods().forEach(requestMethod -> { + switch (requestMethod) { + case GET: + result.putAll(HttpMethod.GET, urls); + break; + case POST: + result.putAll(HttpMethod.POST, urls); + break; + case PUT: + result.putAll(HttpMethod.PUT, urls); + break; + case DELETE: + result.putAll(HttpMethod.DELETE, urls); + break; + case HEAD: + result.putAll(HttpMethod.HEAD, urls); + break; + case PATCH: + result.putAll(HttpMethod.PATCH, urls); + break; + } + }); + } + return result; + } + +} diff --git a/tashow-framework/tashow-framework-security/src/main/java/com/tashow/cloud/security/security/core/LoginUser.java b/tashow-framework/tashow-framework-security/src/main/java/com/tashow/cloud/security/security/core/LoginUser.java new file mode 100644 index 0000000..dc967b9 --- /dev/null +++ b/tashow-framework/tashow-framework-security/src/main/java/com/tashow/cloud/security/security/core/LoginUser.java @@ -0,0 +1,71 @@ +package com.tashow.cloud.security.security.core; + +import cn.hutool.core.map.MapUtil; +import com.tashow.cloud.common.enums.UserTypeEnum; +import com.fasterxml.jackson.annotation.JsonIgnore; +import lombok.Data; + +import java.time.LocalDateTime; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * 登录用户信息 + * + * @author 芋道源码 + */ +@Data +public class LoginUser { + + public static final String INFO_KEY_NICKNAME = "nickname"; + public static final String INFO_KEY_DEPT_ID = "deptId"; + + /** + * 用户编号 + */ + private Long id; + /** + * 用户类型 + * + * 关联 {@link UserTypeEnum} + */ + private Integer userType; + /** + * 额外的用户信息 + */ + private Map info; + /** + * 租户编号 + */ + private Long tenantId; + /** + * 授权范围 + */ + private List scopes; + /** + * 过期时间 + */ + private LocalDateTime expiresTime; + + // ========== 上下文 ========== + /** + * 上下文字段,不进行持久化 + * + * 1. 用于基于 LoginUser 维度的临时缓存 + */ + @JsonIgnore + private Map context; + + public void setContext(String key, Object value) { + if (context == null) { + context = new HashMap<>(); + } + context.put(key, value); + } + + public T getContext(String key, Class type) { + return MapUtil.get(context, key, type); + } + +} diff --git a/tashow-framework/tashow-framework-security/src/main/java/com/tashow/cloud/security/security/core/context/TransmittableThreadLocalSecurityContextHolderStrategy.java b/tashow-framework/tashow-framework-security/src/main/java/com/tashow/cloud/security/security/core/context/TransmittableThreadLocalSecurityContextHolderStrategy.java new file mode 100644 index 0000000..f8950ef --- /dev/null +++ b/tashow-framework/tashow-framework-security/src/main/java/com/tashow/cloud/security/security/core/context/TransmittableThreadLocalSecurityContextHolderStrategy.java @@ -0,0 +1,48 @@ +package com.tashow.cloud.security.security.core.context; + +import com.alibaba.ttl.TransmittableThreadLocal; +import org.springframework.security.core.context.SecurityContext; +import org.springframework.security.core.context.SecurityContextHolderStrategy; +import org.springframework.security.core.context.SecurityContextImpl; +import org.springframework.util.Assert; + +/** + * 基于 TransmittableThreadLocal 实现的 Security Context 持有者策略 + * 目的是,避免 @Async 等异步执行时,原生 ThreadLocal 的丢失问题 + * + * @author 芋道源码 + */ +public class TransmittableThreadLocalSecurityContextHolderStrategy implements SecurityContextHolderStrategy { + + /** + * 使用 TransmittableThreadLocal 作为上下文 + */ + private static final ThreadLocal CONTEXT_HOLDER = new TransmittableThreadLocal<>(); + + @Override + public void clearContext() { + CONTEXT_HOLDER.remove(); + } + + @Override + public SecurityContext getContext() { + SecurityContext ctx = CONTEXT_HOLDER.get(); + if (ctx == null) { + ctx = createEmptyContext(); + CONTEXT_HOLDER.set(ctx); + } + return ctx; + } + + @Override + public void setContext(SecurityContext context) { + Assert.notNull(context, "Only non-null SecurityContext instances are permitted"); + CONTEXT_HOLDER.set(context); + } + + @Override + public SecurityContext createEmptyContext() { + return new SecurityContextImpl(); + } + +} diff --git a/tashow-framework/tashow-framework-security/src/main/java/com/tashow/cloud/security/security/core/filter/TokenAuthenticationFilter.java b/tashow-framework/tashow-framework-security/src/main/java/com/tashow/cloud/security/security/core/filter/TokenAuthenticationFilter.java new file mode 100644 index 0000000..6c400a8 --- /dev/null +++ b/tashow-framework/tashow-framework-security/src/main/java/com/tashow/cloud/security/security/core/filter/TokenAuthenticationFilter.java @@ -0,0 +1,145 @@ +package com.tashow.cloud.security.security.core.filter; + +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.StrUtil; +import com.tashow.cloud.common.exception.ServiceException; +import com.tashow.cloud.common.pojo.CommonResult; +import com.tashow.cloud.common.util.json.JsonUtils; +import com.tashow.cloud.common.util.servlet.ServletUtils; +import com.tashow.cloud.systemapi.api.oauth2.OAuth2TokenApi; +import com.tashow.cloud.systemapi.api.oauth2.dto.OAuth2AccessTokenCheckRespDTO; +import com.tashow.cloud.security.security.config.SecurityProperties; +import com.tashow.cloud.security.security.core.LoginUser; +import com.tashow.cloud.security.security.core.util.SecurityFrameworkUtils; +import com.tashow.cloud.web.web.core.handler.GlobalExceptionHandler; +import com.tashow.cloud.web.web.core.util.WebFrameworkUtils; +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.security.access.AccessDeniedException; +import org.springframework.web.filter.OncePerRequestFilter; + +import java.io.IOException; +import java.net.URLDecoder; +import java.nio.charset.StandardCharsets; + +/** + * Token 过滤器,验证 token 的有效性 + * 验证通过后,获得 {@link LoginUser} 信息,并加入到 Spring Security 上下文 + * + * @author 芋道源码 + */ +@RequiredArgsConstructor +@Slf4j +public class TokenAuthenticationFilter extends OncePerRequestFilter { + + private final SecurityProperties securityProperties; + + private final GlobalExceptionHandler globalExceptionHandler; + + private final OAuth2TokenApi oauth2TokenApi; + + @Override + @SuppressWarnings("NullableProblems") + protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) + throws ServletException, IOException { + // 情况一,基于 header[login-user] 获得用户,例如说来自 Gateway 或者其它服务透传 + LoginUser loginUser = buildLoginUserByHeader(request); + + // 情况二,基于 Token 获得用户 + // 注意,这里主要满足直接使用 Nginx 直接转发到 Spring Cloud 服务的场景。 + if (loginUser == null) { + String token = SecurityFrameworkUtils.obtainAuthorization(request, + securityProperties.getTokenHeader(), securityProperties.getTokenParameter()); + if (StrUtil.isNotEmpty(token)) { + Integer userType = WebFrameworkUtils.getLoginUserType(request); + try { + // 1.1 基于 token 构建登录用户 + loginUser = buildLoginUserByToken(token, userType); + // 1.2 模拟 Login 功能,方便日常开发调试 + if (loginUser == null) { + loginUser = mockLoginUser(request, token, userType); + } + } catch (Throwable ex) { + CommonResult result = globalExceptionHandler.allExceptionHandler(request, ex); + ServletUtils.writeJSON(response, result); + return; + } + } + } + + // 设置当前用户 + if (loginUser != null) { + SecurityFrameworkUtils.setLoginUser(loginUser, request); + } + // 继续过滤链 + chain.doFilter(request, response); + } + + private LoginUser buildLoginUserByToken(String token, Integer userType) { + try { + // 校验访问令牌 + OAuth2AccessTokenCheckRespDTO accessToken = oauth2TokenApi.checkAccessToken(token).getCheckedData(); + if (accessToken == null) { + return null; + } + // 用户类型不匹配,无权限 + // 注意:只有 /admin-api/* 和 /app-api/* 有 userType,才需要比对用户类型 + // 类似 WebSocket 的 /ws/* 连接地址,是不需要比对用户类型的 + if (userType != null + && ObjectUtil.notEqual(accessToken.getUserType(), userType)) { + throw new AccessDeniedException("错误的用户类型"); + } + // 构建登录用户 + return new LoginUser().setId(accessToken.getUserId()).setUserType(accessToken.getUserType()) + .setInfo(accessToken.getUserInfo()) // 额外的用户信息 + .setTenantId(accessToken.getTenantId()).setScopes(accessToken.getScopes()) + .setExpiresTime(accessToken.getExpiresTime()); + } catch (ServiceException serviceException) { + // 校验 Token 不通过时,考虑到一些接口是无需登录的,所以直接返回 null 即可 + return null; + } + } + + /** + * 模拟登录用户,方便日常开发调试 + * + * 注意,在线上环境下,一定要关闭该功能!!! + * + * @param request 请求 + * @param token 模拟的 token,格式为 {@link SecurityProperties#getMockSecret()} + 用户编号 + * @param userType 用户类型 + * @return 模拟的 LoginUser + */ + private LoginUser mockLoginUser(HttpServletRequest request, String token, Integer userType) { + if (!securityProperties.getMockEnable()) { + return null; + } + // 必须以 mockSecret 开头 + if (!token.startsWith(securityProperties.getMockSecret())) { + return null; + } + // 构建模拟用户 + Long userId = Long.valueOf(token.substring(securityProperties.getMockSecret().length())); + return new LoginUser().setId(userId).setUserType(userType) + .setTenantId(WebFrameworkUtils.getTenantId(request)); + } + + private LoginUser buildLoginUserByHeader(HttpServletRequest request) { + String loginUserStr = request.getHeader(SecurityFrameworkUtils.LOGIN_USER_HEADER); + if (StrUtil.isEmpty(loginUserStr)) { + return null; + } + try { + loginUserStr = URLDecoder.decode(loginUserStr, StandardCharsets.UTF_8); // 解码,解决中文乱码问题 + return JsonUtils.parseObject(loginUserStr, LoginUser.class); + } catch (Exception ex) { + log.error("[buildLoginUserByHeader][解析 LoginUser({}) 发生异常]", loginUserStr, ex); ; + throw ex; + } + } + +} diff --git a/tashow-framework/tashow-framework-security/src/main/java/com/tashow/cloud/security/security/core/handler/AccessDeniedHandlerImpl.java b/tashow-framework/tashow-framework-security/src/main/java/com/tashow/cloud/security/security/core/handler/AccessDeniedHandlerImpl.java new file mode 100644 index 0000000..17110a2 --- /dev/null +++ b/tashow-framework/tashow-framework-security/src/main/java/com/tashow/cloud/security/security/core/handler/AccessDeniedHandlerImpl.java @@ -0,0 +1,41 @@ +package com.tashow.cloud.security.security.core.handler; + +import com.tashow.cloud.common.pojo.CommonResult; +import com.tashow.cloud.common.util.servlet.ServletUtils; +import com.tashow.cloud.security.security.core.util.SecurityFrameworkUtils; +import lombok.extern.slf4j.Slf4j; +import org.springframework.security.access.AccessDeniedException; +import org.springframework.security.web.access.AccessDeniedHandler; +import org.springframework.security.web.access.ExceptionTranslationFilter; + +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import java.io.IOException; + +import static com.tashow.cloud.common.exception.enums.GlobalErrorCodeConstants.FORBIDDEN; + + +/** + * 访问一个需要认证的 URL 资源,已经认证(登录)但是没有权限的情况下,返回 {@link GlobalErrorCodeConstants#FORBIDDEN} 错误码。 + * + * 补充:Spring Security 通过 {@link ExceptionTranslationFilter#handleAccessDeniedException(HttpServletRequest, HttpServletResponse, FilterChain, AccessDeniedException)} 方法,调用当前类 + * + * @author 芋道源码 + */ +@Slf4j +@SuppressWarnings("JavadocReference") +public class AccessDeniedHandlerImpl implements AccessDeniedHandler { + + @Override + public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException e) + throws IOException, ServletException { + // 打印 warn 的原因是,不定期合并 warn,看看有没恶意破坏 + log.warn("[commence][访问 URL({}) 时,用户({}) 权限不够]", request.getRequestURI(), + SecurityFrameworkUtils.getLoginUserId(), e); + // 返回 403 + ServletUtils.writeJSON(response, CommonResult.error(FORBIDDEN)); + } + +} diff --git a/tashow-framework/tashow-framework-security/src/main/java/com/tashow/cloud/security/security/core/handler/AuthenticationEntryPointImpl.java b/tashow-framework/tashow-framework-security/src/main/java/com/tashow/cloud/security/security/core/handler/AuthenticationEntryPointImpl.java new file mode 100644 index 0000000..2c90a2e --- /dev/null +++ b/tashow-framework/tashow-framework-security/src/main/java/com/tashow/cloud/security/security/core/handler/AuthenticationEntryPointImpl.java @@ -0,0 +1,35 @@ +package com.tashow.cloud.security.security.core.handler; + +import com.tashow.cloud.common.pojo.CommonResult; +import com.tashow.cloud.common.util.servlet.ServletUtils; +import lombok.extern.slf4j.Slf4j; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.web.AuthenticationEntryPoint; +import org.springframework.security.web.access.ExceptionTranslationFilter; + +import jakarta.servlet.FilterChain; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; + +import static com.tashow.cloud.common.exception.enums.GlobalErrorCodeConstants.UNAUTHORIZED; + + +/** + * 访问一个需要认证的 URL 资源,但是此时自己尚未认证(登录)的情况下,返回 {@link GlobalErrorCodeConstants#UNAUTHORIZED} 错误码,从而使前端重定向到登录页 + * + * 补充:Spring Security 通过 {@link ExceptionTranslationFilter#sendStartAuthentication(HttpServletRequest, HttpServletResponse, FilterChain, AuthenticationException)} 方法,调用当前类 + * + * @author ruoyi + */ +@Slf4j +@SuppressWarnings("JavadocReference") // 忽略文档引用报错 +public class AuthenticationEntryPointImpl implements AuthenticationEntryPoint { + + @Override + public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) { + log.debug("[commence][访问 URL({}) 时,没有登录]", request.getRequestURI(), e); + // 返回 401 + ServletUtils.writeJSON(response, CommonResult.error(UNAUTHORIZED)); + } + +} diff --git a/tashow-framework/tashow-framework-security/src/main/java/com/tashow/cloud/security/security/core/rpc/LoginUserRequestInterceptor.java b/tashow-framework/tashow-framework-security/src/main/java/com/tashow/cloud/security/security/core/rpc/LoginUserRequestInterceptor.java new file mode 100644 index 0000000..a1dce4a --- /dev/null +++ b/tashow-framework/tashow-framework-security/src/main/java/com/tashow/cloud/security/security/core/rpc/LoginUserRequestInterceptor.java @@ -0,0 +1,37 @@ +package com.tashow.cloud.security.security.core.rpc; + +import com.tashow.cloud.common.util.json.JsonUtils; +import com.tashow.cloud.security.security.core.LoginUser; +import com.tashow.cloud.security.security.core.util.SecurityFrameworkUtils; +import feign.RequestInterceptor; +import feign.RequestTemplate; +import lombok.extern.slf4j.Slf4j; + +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; + +/** + * LoginUser 的 RequestInterceptor 实现类:Feign 请求时,将 {@link LoginUser} 设置到 header 中,继续透传给被调用的服务 + * + * @author 芋道源码 + */ +@Slf4j +public class LoginUserRequestInterceptor implements RequestInterceptor { + + @Override + public void apply(RequestTemplate requestTemplate) { + LoginUser user = SecurityFrameworkUtils.getLoginUser(); + if (user == null) { + return; + } + try { + String userStr = JsonUtils.toJsonString(user); + userStr = URLEncoder.encode(userStr, StandardCharsets.UTF_8); // 编码,避免中文乱码 + requestTemplate.header(SecurityFrameworkUtils.LOGIN_USER_HEADER, userStr); + } catch (Exception ex) { + log.error("[apply][序列化 LoginUser({}) 发生异常]", user, ex); + throw ex; + } + } + +} diff --git a/tashow-framework/tashow-framework-security/src/main/java/com/tashow/cloud/security/security/core/service/SecurityFrameworkService.java b/tashow-framework/tashow-framework-security/src/main/java/com/tashow/cloud/security/security/core/service/SecurityFrameworkService.java new file mode 100644 index 0000000..f134a28 --- /dev/null +++ b/tashow-framework/tashow-framework-security/src/main/java/com/tashow/cloud/security/security/core/service/SecurityFrameworkService.java @@ -0,0 +1,59 @@ +package com.tashow.cloud.security.security.core.service; + +/** + * Security 框架 Service 接口,定义权限相关的校验操作 + * + * @author 芋道源码 + */ +public interface SecurityFrameworkService { + + /** + * 判断是否有权限 + * + * @param permission 权限 + * @return 是否 + */ + boolean hasPermission(String permission); + + /** + * 判断是否有权限,任一一个即可 + * + * @param permissions 权限 + * @return 是否 + */ + boolean hasAnyPermissions(String... permissions); + + /** + * 判断是否有角色 + * + * 注意,角色使用的是 SysRoleDO 的 code 标识 + * + * @param role 角色 + * @return 是否 + */ + boolean hasRole(String role); + + /** + * 判断是否有角色,任一一个即可 + * + * @param roles 角色数组 + * @return 是否 + */ + boolean hasAnyRoles(String... roles); + + /** + * 判断是否有授权 + * + * @param scope 授权 + * @return 是否 + */ + boolean hasScope(String scope); + + /** + * 判断是否有授权范围,任一一个即可 + * + * @param scope 授权范围数组 + * @return 是否 + */ + boolean hasAnyScopes(String... scope); +} diff --git a/tashow-framework/tashow-framework-security/src/main/java/com/tashow/cloud/security/security/core/service/SecurityFrameworkServiceImpl.java b/tashow-framework/tashow-framework-security/src/main/java/com/tashow/cloud/security/security/core/service/SecurityFrameworkServiceImpl.java new file mode 100644 index 0000000..8fe7c4d --- /dev/null +++ b/tashow-framework/tashow-framework-security/src/main/java/com/tashow/cloud/security/security/core/service/SecurityFrameworkServiceImpl.java @@ -0,0 +1,102 @@ +package com.tashow.cloud.security.security.core.service; + +import cn.hutool.core.collection.CollUtil; +import com.tashow.cloud.common.core.KeyValue; +import com.tashow.cloud.systemapi.api.permission.PermissionApi; +import com.google.common.cache.CacheLoader; +import com.google.common.cache.LoadingCache; +import com.tashow.cloud.security.security.core.LoginUser; +import com.tashow.cloud.security.security.core.util.SecurityFrameworkUtils; +import lombok.AllArgsConstructor; +import lombok.SneakyThrows; + +import java.time.Duration; +import java.util.Arrays; +import java.util.List; + +import static com.tashow.cloud.common.util.cache.CacheUtils.buildCache; +import static com.tashow.cloud.web.web.core.util.WebFrameworkUtils.getLoginUserId; + +/** + * 默认的 {@link SecurityFrameworkService} 实现类 + * + * @author 芋道源码 + */ +@AllArgsConstructor +public class SecurityFrameworkServiceImpl implements SecurityFrameworkService { + + private final PermissionApi permissionApi; + + /** + * 针对 {@link #hasAnyRoles(String...)} 的缓存 + */ + private final LoadingCache>, Boolean> hasAnyRolesCache = buildCache( + Duration.ofMinutes(1L), // 过期时间 1 分钟 + new CacheLoader>, Boolean>() { + + @Override + public Boolean load(KeyValue> key) { + return permissionApi.hasAnyRoles(key.getKey(), key.getValue().toArray(new String[0])).getCheckedData(); + } + + }); + + /** + * 针对 {@link #hasAnyPermissions(String...)} 的缓存 + */ + private final LoadingCache>, Boolean> hasAnyPermissionsCache = buildCache( + Duration.ofMinutes(1L), // 过期时间 1 分钟 + new CacheLoader>, Boolean>() { + + @Override + public Boolean load(KeyValue> key) { + return permissionApi.hasAnyPermissions(key.getKey(), key.getValue().toArray(new String[0])).getCheckedData(); + } + + }); + + @Override + public boolean hasPermission(String permission) { + return hasAnyPermissions(permission); + } + + @Override + @SneakyThrows + public boolean hasAnyPermissions(String... permissions) { + Long userId = getLoginUserId(); + if (userId == null) { + return false; + } + return hasAnyPermissionsCache.get(new KeyValue<>(userId, Arrays.asList(permissions))); + } + + @Override + public boolean hasRole(String role) { + return hasAnyRoles(role); + } + + @Override + @SneakyThrows + public boolean hasAnyRoles(String... roles) { + Long userId = getLoginUserId(); + if (userId == null) { + return false; + } + return hasAnyRolesCache.get(new KeyValue<>(userId, Arrays.asList(roles))); + } + + @Override + public boolean hasScope(String scope) { + return hasAnyScopes(scope); + } + + @Override + public boolean hasAnyScopes(String... scope) { + LoginUser user = SecurityFrameworkUtils.getLoginUser(); + if (user == null) { + return false; + } + return CollUtil.containsAny(user.getScopes(), Arrays.asList(scope)); + } + +} diff --git a/tashow-framework/tashow-framework-security/src/main/java/com/tashow/cloud/security/security/core/util/SecurityFrameworkUtils.java b/tashow-framework/tashow-framework-security/src/main/java/com/tashow/cloud/security/security/core/util/SecurityFrameworkUtils.java new file mode 100644 index 0000000..e21610c --- /dev/null +++ b/tashow-framework/tashow-framework-security/src/main/java/com/tashow/cloud/security/security/core/util/SecurityFrameworkUtils.java @@ -0,0 +1,142 @@ +package com.tashow.cloud.security.security.core.util; + +import cn.hutool.core.map.MapUtil; +import cn.hutool.core.util.StrUtil; +import com.tashow.cloud.security.security.core.LoginUser; +import com.tashow.cloud.web.web.core.util.WebFrameworkUtils; +import jakarta.servlet.http.HttpServletRequest; +import org.springframework.lang.Nullable; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContext; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.web.authentication.WebAuthenticationDetailsSource; +import org.springframework.util.StringUtils; + +import java.util.Collections; + +/** + * 安全服务工具类 + * + * @author 芋道源码 + */ +public class SecurityFrameworkUtils { + + /** + * HEADER 认证头 value 的前缀 + */ + public static final String AUTHORIZATION_BEARER = "Bearer"; + + public static final String LOGIN_USER_HEADER = "login-user"; + + private SecurityFrameworkUtils() {} + + /** + * 从请求中,获得认证 Token + * + * @param request 请求 + * @param headerName 认证 Token 对应的 Header 名字 + * @param parameterName 认证 Token 对应的 Parameter 名字 + * @return 认证 Token + */ + public static String obtainAuthorization(HttpServletRequest request, + String headerName, String parameterName) { + // 1. 获得 Token。优先级:Header > Parameter + String token = request.getHeader(headerName); + if (StrUtil.isEmpty(token)) { + token = request.getParameter(parameterName); + } + if (!StringUtils.hasText(token)) { + return null; + } + // 2. 去除 Token 中带的 Bearer + int index = token.indexOf(AUTHORIZATION_BEARER + " "); + return index >= 0 ? token.substring(index + 7).trim() : token; + } + + /** + * 获得当前认证信息 + * + * @return 认证信息 + */ + public static Authentication getAuthentication() { + SecurityContext context = SecurityContextHolder.getContext(); + if (context == null) { + return null; + } + return context.getAuthentication(); + } + + /** + * 获取当前用户 + * + * @return 当前用户 + */ + @Nullable + public static LoginUser getLoginUser() { + Authentication authentication = getAuthentication(); + if (authentication == null) { + return null; + } + return authentication.getPrincipal() instanceof LoginUser ? (LoginUser) authentication.getPrincipal() : null; + } + + /** + * 获得当前用户的编号,从上下文中 + * + * @return 用户编号 + */ + @Nullable + public static Long getLoginUserId() { + LoginUser loginUser = getLoginUser(); + return loginUser != null ? loginUser.getId() : null; + } + + /** + * 获得当前用户的昵称,从上下文中 + * + * @return 昵称 + */ + @Nullable + public static String getLoginUserNickname() { + LoginUser loginUser = getLoginUser(); + return loginUser != null ? MapUtil.getStr(loginUser.getInfo(), LoginUser.INFO_KEY_NICKNAME) : null; + } + + /** + * 获得当前用户的部门编号,从上下文中 + * + * @return 部门编号 + */ + @Nullable + public static Long getLoginUserDeptId() { + LoginUser loginUser = getLoginUser(); + return loginUser != null ? MapUtil.getLong(loginUser.getInfo(), LoginUser.INFO_KEY_DEPT_ID) : null; + } + + /** + * 设置当前用户 + * + * @param loginUser 登录用户 + * @param request 请求 + */ + public static void setLoginUser(LoginUser loginUser, HttpServletRequest request) { + // 创建 Authentication,并设置到上下文 + Authentication authentication = buildAuthentication(loginUser, request); + SecurityContextHolder.getContext().setAuthentication(authentication); + + // 额外设置到 request 中,用于 ApiAccessLogFilter 可以获取到用户编号; + // 原因是,Spring Security 的 Filter 在 ApiAccessLogFilter 后面,在它记录访问日志时,线上上下文已经没有用户编号等信息 + WebFrameworkUtils.setLoginUserId(request, loginUser.getId()); + WebFrameworkUtils.setLoginUserType(request, loginUser.getUserType()); + } + + private static Authentication buildAuthentication(LoginUser loginUser, HttpServletRequest request) { + // 创建 UsernamePasswordAuthenticationToken 对象 + UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken( + loginUser, null, Collections.emptyList()); + authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); + return authenticationToken; + } + +} diff --git a/tashow-framework/tashow-framework-security/src/main/java/com/tashow/cloud/security/security/package-info.java b/tashow-framework/tashow-framework-security/src/main/java/com/tashow/cloud/security/security/package-info.java new file mode 100644 index 0000000..69b5126 --- /dev/null +++ b/tashow-framework/tashow-framework-security/src/main/java/com/tashow/cloud/security/security/package-info.java @@ -0,0 +1,7 @@ +/** + * 基于 Spring Security 框架 + * 实现安全认证功能 + * + * @author 芋道源码 + */ +package com.tashow.cloud.security.security; diff --git a/tashow-framework/tashow-framework-security/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/tashow-framework/tashow-framework-security/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports new file mode 100644 index 0000000..8a0e38f --- /dev/null +++ b/tashow-framework/tashow-framework-security/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -0,0 +1,5 @@ +com.tashow.cloud.security.security.config.SecurityRpcAutoConfiguration +com.tashow.cloud.security.security.config.SecurityAutoConfiguration +com.tashow.cloud.security.security.config.WebSecurityConfigurerAdapter +com.tashow.cloud.security.operatelog.config.OperateLogConfiguration +com.tashow.cloud.security.operatelog.config.OperateLogRpcAutoConfiguration diff --git a/tashow-framework/tashow-framework-tenant/pom.xml b/tashow-framework/tashow-framework-tenant/pom.xml new file mode 100644 index 0000000..3f0d16f --- /dev/null +++ b/tashow-framework/tashow-framework-tenant/pom.xml @@ -0,0 +1,91 @@ + + + + com.tashow.cloud + tashow-framework + ${revision} + + 4.0.0 + tashow-framework-tenant + jar + + ${project.artifactId} + 多租户 + + + + com.tashow.cloud + tashow-common + + + + + com.tashow.cloud + tashow-framework-security + + + + + com.tashow.cloud + tashow-data-mybatis + + + + com.tashow.cloud + tashow-data-redis + + + + + com.tashow.cloud + tashow-framework-rpc + true + + + + + com.tashow.cloud + tashow-framework-job + true + + + + + com.tashow.cloud + tashow-framework-mq + true + + + org.springframework.kafka + spring-kafka + true + + + org.springframework.amqp + spring-rabbit + true + + + org.apache.rocketmq + rocketmq-spring-boot-starter + true + + + + + org.springframework.boot + spring-boot-starter-test + test + + + + + com.google.guava + guava + + + + + diff --git a/tashow-framework/tashow-framework-tenant/src/main/java/com/tashow/cloud/tenant/config/TenantAutoConfiguration.java b/tashow-framework/tashow-framework-tenant/src/main/java/com/tashow/cloud/tenant/config/TenantAutoConfiguration.java new file mode 100644 index 0000000..6112d02 --- /dev/null +++ b/tashow-framework/tashow-framework-tenant/src/main/java/com/tashow/cloud/tenant/config/TenantAutoConfiguration.java @@ -0,0 +1,119 @@ +package com.tashow.cloud.tenant.config; + +import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor; +import com.baomidou.mybatisplus.extension.plugins.inner.TenantLineInnerInterceptor; +import com.tashow.cloud.common.enums.WebFilterOrderEnum; +import com.tashow.cloud.mybatis.mybatis.core.util.MyBatisUtils; +import com.tashow.cloud.redis.config.TashowCacheProperties; +import com.tashow.cloud.systemapi.api.tenant.TenantApi; +import com.tashow.cloud.tenant.core.aop.TenantIgnoreAspect; +import com.tashow.cloud.tenant.core.db.TenantDatabaseInterceptor; +import com.tashow.cloud.tenant.core.job.TenantJobAspect; +import com.tashow.cloud.tenant.core.mq.rabbitmq.TenantRabbitMQInitializer; +import com.tashow.cloud.tenant.core.redis.TenantRedisCacheManager; +import com.tashow.cloud.tenant.core.security.TenantSecurityWebFilter; +import com.tashow.cloud.tenant.core.service.TenantFrameworkService; +import com.tashow.cloud.tenant.core.service.TenantFrameworkServiceImpl; +import com.tashow.cloud.tenant.core.web.TenantContextWebFilter; +import com.tashow.cloud.web.web.config.WebProperties; +import com.tashow.cloud.web.web.core.handler.GlobalExceptionHandler; +import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.boot.web.servlet.FilterRegistrationBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Primary; +import org.springframework.data.redis.cache.BatchStrategies; +import org.springframework.data.redis.cache.RedisCacheConfiguration; +import org.springframework.data.redis.cache.RedisCacheManager; +import org.springframework.data.redis.cache.RedisCacheWriter; +import org.springframework.data.redis.connection.RedisConnectionFactory; +import org.springframework.data.redis.core.RedisTemplate; + +import java.util.Objects; + +@AutoConfiguration +@ConditionalOnProperty(prefix = "tashow.tenant", value = "enable", matchIfMissing = true) // 允许使用 yudao.tenant.enable=false 禁用多租户 +@EnableConfigurationProperties(TenantProperties.class) +public class TenantAutoConfiguration { + + @Bean + public TenantFrameworkService tenantFrameworkService(TenantApi tenantApi) { + return new TenantFrameworkServiceImpl(tenantApi); + } + + // ========== AOP ========== + + @Bean + public TenantIgnoreAspect tenantIgnoreAspect() { + return new TenantIgnoreAspect(); + } + + // ========== DB ========== + + @Bean + public TenantLineInnerInterceptor tenantLineInnerInterceptor(TenantProperties properties, + MybatisPlusInterceptor interceptor) { + TenantLineInnerInterceptor inner = new TenantLineInnerInterceptor(new TenantDatabaseInterceptor(properties)); + // 添加到 interceptor 中 + // 需要加在首个,主要是为了在分页插件前面。这个是 MyBatis Plus 的规定 + MyBatisUtils.addInterceptor(interceptor, inner, 0); + return inner; + } + + // ========== WEB ========== + + @Bean + public FilterRegistrationBean tenantContextWebFilter() { + FilterRegistrationBean registrationBean = new FilterRegistrationBean<>(); + registrationBean.setFilter(new TenantContextWebFilter()); + registrationBean.setOrder(WebFilterOrderEnum.TENANT_CONTEXT_FILTER); + return registrationBean; + } + + // ========== Security ========== + + @Bean + public FilterRegistrationBean tenantSecurityWebFilter(TenantProperties tenantProperties, + WebProperties webProperties, + GlobalExceptionHandler globalExceptionHandler, + TenantFrameworkService tenantFrameworkService) { + FilterRegistrationBean registrationBean = new FilterRegistrationBean<>(); + registrationBean.setFilter(new TenantSecurityWebFilter(tenantProperties, webProperties, + globalExceptionHandler, tenantFrameworkService)); + registrationBean.setOrder(WebFilterOrderEnum.TENANT_SECURITY_FILTER); + return registrationBean; + } + + // ========== Job ========== + + @Bean + @ConditionalOnClass(name = "com.xxl.job.core.handler.annotation.XxlJob") + public TenantJobAspect tenantJobAspect(TenantFrameworkService tenantFrameworkService) { + return new TenantJobAspect(tenantFrameworkService); + } + + + @Bean + @ConditionalOnClass(name = "org.springframework.amqp.rabbit.core.RabbitTemplate") + public TenantRabbitMQInitializer tenantRabbitMQInitializer() { + return new TenantRabbitMQInitializer(); + } + + // ========== Redis ========== + + @Bean + @Primary // 引入租户时,tenantRedisCacheManager 为主 Bean + public RedisCacheManager tenantRedisCacheManager(RedisTemplate redisTemplate, + RedisCacheConfiguration redisCacheConfiguration, + TashowCacheProperties yudaoTashowCacheProperties, + TenantProperties tenantProperties) { + // 创建 RedisCacheWriter 对象 + RedisConnectionFactory connectionFactory = Objects.requireNonNull(redisTemplate.getConnectionFactory()); + RedisCacheWriter cacheWriter = RedisCacheWriter.nonLockingRedisCacheWriter(connectionFactory, + BatchStrategies.scan(yudaoTashowCacheProperties.getRedisScanBatchSize())); + // 创建 TenantRedisCacheManager 对象 + return new TenantRedisCacheManager(cacheWriter, redisCacheConfiguration, tenantProperties.getIgnoreCaches()); + } +} diff --git a/tashow-framework/tashow-framework-tenant/src/main/java/com/tashow/cloud/tenant/config/TenantProperties.java b/tashow-framework/tashow-framework-tenant/src/main/java/com/tashow/cloud/tenant/config/TenantProperties.java new file mode 100644 index 0000000..4beca50 --- /dev/null +++ b/tashow-framework/tashow-framework-tenant/src/main/java/com/tashow/cloud/tenant/config/TenantProperties.java @@ -0,0 +1,49 @@ +package com.tashow.cloud.tenant.config; + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; + +import java.util.Collections; +import java.util.Set; + +/** + * 多租户配置 + * + * @author 芋道源码 + */ +@ConfigurationProperties(prefix = "tashow.tenant") +@Data +public class TenantProperties { + + /** + * 租户是否开启 + */ + private static final Boolean ENABLE_DEFAULT = true; + + /** + * 是否开启 + */ + private Boolean enable = ENABLE_DEFAULT; + + /** + * 需要忽略多租户的请求 + * + * 默认情况下,每个请求需要带上 tenant-id 的请求头。但是,部分请求是无需带上的,例如说短信回调、支付回调等 Open API! + */ + private Set ignoreUrls = Collections.emptySet(); + + /** + * 需要忽略多租户的表 + * + * 即默认所有表都开启多租户的功能,所以记得添加对应的 tenant_id 字段哟 + */ + private Set ignoreTables = Collections.emptySet(); + + /** + * 需要忽略多租户的 Spring Cache 缓存 + * + * 即默认所有缓存都开启多租户的功能,所以记得添加对应的 tenant_id 字段哟 + */ + private Set ignoreCaches = Collections.emptySet(); + +} diff --git a/tashow-framework/tashow-framework-tenant/src/main/java/com/tashow/cloud/tenant/config/TenantRpcAutoConfiguration.java b/tashow-framework/tashow-framework-tenant/src/main/java/com/tashow/cloud/tenant/config/TenantRpcAutoConfiguration.java new file mode 100644 index 0000000..c905345 --- /dev/null +++ b/tashow-framework/tashow-framework-tenant/src/main/java/com/tashow/cloud/tenant/config/TenantRpcAutoConfiguration.java @@ -0,0 +1,21 @@ +package com.tashow.cloud.tenant.config; + +import com.tashow.cloud.systemapi.api.tenant.TenantApi; +import com.tashow.cloud.tenant.core.rpc.TenantRequestInterceptor; +import com.tashow.cloud.tenant.core.rpc.TenantRequestInterceptor; +import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.cloud.openfeign.EnableFeignClients; +import org.springframework.context.annotation.Bean; + +@AutoConfiguration +@ConditionalOnProperty(prefix = "tashow.tenant", value = "enable", matchIfMissing = true) // 允许使用 yudao.tenant.enable=false 禁用多租户 +@EnableFeignClients(clients = TenantApi.class) // 主要是引入相关的 API 服务 +public class TenantRpcAutoConfiguration { + + @Bean + public TenantRequestInterceptor tenantRequestInterceptor() { + return new TenantRequestInterceptor(); + } + +} diff --git a/tashow-framework/tashow-framework-tenant/src/main/java/com/tashow/cloud/tenant/core/aop/TenantIgnore.java b/tashow-framework/tashow-framework-tenant/src/main/java/com/tashow/cloud/tenant/core/aop/TenantIgnore.java new file mode 100644 index 0000000..e6be0b7 --- /dev/null +++ b/tashow-framework/tashow-framework-tenant/src/main/java/com/tashow/cloud/tenant/core/aop/TenantIgnore.java @@ -0,0 +1,18 @@ +package com.tashow.cloud.tenant.core.aop; + +import java.lang.annotation.*; + +/** + * 忽略租户,标记指定方法不进行租户的自动过滤 + * + * 注意,只有 DB 的场景会过滤,其它场景暂时不过滤: + * 1、Redis 场景:因为是基于 Key 实现多租户的能力,所以忽略没有意义,不像 DB 是一个 column 实现的 + * 2、MQ 场景:有点难以抉择,目前可以通过 Consumer 手动在消费的方法上,添加 @TenantIgnore 进行忽略 + * + * @author 芋道源码 + */ +@Target({ElementType.METHOD}) +@Retention(RetentionPolicy.RUNTIME) +@Inherited +public @interface TenantIgnore { +} diff --git a/tashow-framework/tashow-framework-tenant/src/main/java/com/tashow/cloud/tenant/core/aop/TenantIgnoreAspect.java b/tashow-framework/tashow-framework-tenant/src/main/java/com/tashow/cloud/tenant/core/aop/TenantIgnoreAspect.java new file mode 100644 index 0000000..182095f --- /dev/null +++ b/tashow-framework/tashow-framework-tenant/src/main/java/com/tashow/cloud/tenant/core/aop/TenantIgnoreAspect.java @@ -0,0 +1,35 @@ +package com.tashow.cloud.tenant.core.aop; + +import com.tashow.cloud.tenant.core.context.TenantContextHolder; +import com.tashow.cloud.tenant.core.util.TenantUtils; +import lombok.extern.slf4j.Slf4j; +import org.aspectj.lang.ProceedingJoinPoint; +import org.aspectj.lang.annotation.Around; +import org.aspectj.lang.annotation.Aspect; + +/** + * 忽略多租户的 Aspect,基于 {@link TenantIgnore} 注解实现,用于一些全局的逻辑。 + * 例如说,一个定时任务,读取所有数据,进行处理。 + * 又例如说,读取所有数据,进行缓存。 + * + * 整体逻辑的实现,和 {@link TenantUtils#executeIgnore(Runnable)} 需要保持一致 + * + * @author 芋道源码 + */ +@Aspect +@Slf4j +public class TenantIgnoreAspect { + + @Around("@annotation(tenantIgnore)") + public Object around(ProceedingJoinPoint joinPoint, TenantIgnore tenantIgnore) throws Throwable { + Boolean oldIgnore = TenantContextHolder.isIgnore(); + try { + TenantContextHolder.setIgnore(true); + // 执行逻辑 + return joinPoint.proceed(); + } finally { + TenantContextHolder.setIgnore(oldIgnore); + } + } + +} diff --git a/tashow-framework/tashow-framework-tenant/src/main/java/com/tashow/cloud/tenant/core/context/TenantContextHolder.java b/tashow-framework/tashow-framework-tenant/src/main/java/com/tashow/cloud/tenant/core/context/TenantContextHolder.java new file mode 100644 index 0000000..704a9f9 --- /dev/null +++ b/tashow-framework/tashow-framework-tenant/src/main/java/com/tashow/cloud/tenant/core/context/TenantContextHolder.java @@ -0,0 +1,68 @@ +package com.tashow.cloud.tenant.core.context; + +import com.alibaba.ttl.TransmittableThreadLocal; +import com.tashow.cloud.common.enums.DocumentEnum; + +/** + * 多租户上下文 Holder + * + * @author 芋道源码 + */ +public class TenantContextHolder { + + /** + * 当前租户编号 + */ + private static final ThreadLocal TENANT_ID = new TransmittableThreadLocal<>(); + + /** + * 是否忽略租户 + */ + private static final ThreadLocal IGNORE = new TransmittableThreadLocal<>(); + + /** + * 获得租户编号 + * + * @return 租户编号 + */ + public static Long getTenantId() { + return TENANT_ID.get(); + } + + /** + * 获得租户编号。如果不存在,则抛出 NullPointerException 异常 + * + * @return 租户编号 + */ + public static Long getRequiredTenantId() { + Long tenantId = getTenantId(); + if (tenantId == null) { + throw new NullPointerException("TenantContextHolder 不存在租户编号!可参考文档:" + + DocumentEnum.TENANT.getUrl()); + } + return tenantId; + } + + public static void setTenantId(Long tenantId) { + TENANT_ID.set(tenantId); + } + + public static void setIgnore(Boolean ignore) { + IGNORE.set(ignore); + } + + /** + * 当前是否忽略租户 + * + * @return 是否忽略 + */ + public static boolean isIgnore() { + return Boolean.TRUE.equals(IGNORE.get()); + } + + public static void clear() { + TENANT_ID.remove(); + IGNORE.remove(); + } + +} diff --git a/tashow-framework/tashow-framework-tenant/src/main/java/com/tashow/cloud/tenant/core/db/TenantBaseDO.java b/tashow-framework/tashow-framework-tenant/src/main/java/com/tashow/cloud/tenant/core/db/TenantBaseDO.java new file mode 100644 index 0000000..405e8d8 --- /dev/null +++ b/tashow-framework/tashow-framework-tenant/src/main/java/com/tashow/cloud/tenant/core/db/TenantBaseDO.java @@ -0,0 +1,21 @@ +package com.tashow.cloud.tenant.core.db; + +import com.tashow.cloud.mybatis.mybatis.core.dataobject.BaseDO; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 拓展多租户的 BaseDO 基类 + * + * @author 芋道源码 + */ +@Data +@EqualsAndHashCode(callSuper = true) +public abstract class TenantBaseDO extends BaseDO { + + /** + * 多租户编号 + */ + private Long tenantId; + +} diff --git a/tashow-framework/tashow-framework-tenant/src/main/java/com/tashow/cloud/tenant/core/db/TenantDatabaseInterceptor.java b/tashow-framework/tashow-framework-tenant/src/main/java/com/tashow/cloud/tenant/core/db/TenantDatabaseInterceptor.java new file mode 100644 index 0000000..0f3672f --- /dev/null +++ b/tashow-framework/tashow-framework-tenant/src/main/java/com/tashow/cloud/tenant/core/db/TenantDatabaseInterceptor.java @@ -0,0 +1,44 @@ +package com.tashow.cloud.tenant.core.db; + +import cn.hutool.core.collection.CollUtil; +import com.baomidou.mybatisplus.extension.plugins.handler.TenantLineHandler; +import com.baomidou.mybatisplus.extension.toolkit.SqlParserUtils; +import com.tashow.cloud.tenant.config.TenantProperties; +import com.tashow.cloud.tenant.core.context.TenantContextHolder; +import net.sf.jsqlparser.expression.Expression; +import net.sf.jsqlparser.expression.LongValue; + +import java.util.HashSet; +import java.util.Set; + +/** + * 基于 MyBatis Plus 多租户的功能,实现 DB 层面的多租户的功能 + * + * @author 芋道源码 + */ +public class TenantDatabaseInterceptor implements TenantLineHandler { + + private final Set ignoreTables = new HashSet<>(); + + public TenantDatabaseInterceptor(TenantProperties properties) { + // 不同 DB 下,大小写的习惯不同,所以需要都添加进去 + properties.getIgnoreTables().forEach(table -> { + ignoreTables.add(table.toLowerCase()); + ignoreTables.add(table.toUpperCase()); + }); + // 在 OracleKeyGenerator 中,生成主键时,会查询这个表,查询这个表后,会自动拼接 TENANT_ID 导致报错 + ignoreTables.add("DUAL"); + } + + @Override + public Expression getTenantId() { + return new LongValue(TenantContextHolder.getRequiredTenantId()); + } + + @Override + public boolean ignoreTable(String tableName) { + return TenantContextHolder.isIgnore() // 情况一,全局忽略多租户 + || CollUtil.contains(ignoreTables, SqlParserUtils.removeWrapperSymbol(tableName)); // 情况二,忽略多租户的表 + } + +} diff --git a/tashow-framework/tashow-framework-tenant/src/main/java/com/tashow/cloud/tenant/core/job/TenantJob.java b/tashow-framework/tashow-framework-tenant/src/main/java/com/tashow/cloud/tenant/core/job/TenantJob.java new file mode 100644 index 0000000..72c01d5 --- /dev/null +++ b/tashow-framework/tashow-framework-tenant/src/main/java/com/tashow/cloud/tenant/core/job/TenantJob.java @@ -0,0 +1,14 @@ +package com.tashow.cloud.tenant.core.job; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * 多租户 Job 注解 + */ +@Target({ElementType.METHOD}) +@Retention(RetentionPolicy.RUNTIME) +public @interface TenantJob { +} diff --git a/tashow-framework/tashow-framework-tenant/src/main/java/com/tashow/cloud/tenant/core/job/TenantJobAspect.java b/tashow-framework/tashow-framework-tenant/src/main/java/com/tashow/cloud/tenant/core/job/TenantJobAspect.java new file mode 100644 index 0000000..60797e2 --- /dev/null +++ b/tashow-framework/tashow-framework-tenant/src/main/java/com/tashow/cloud/tenant/core/job/TenantJobAspect.java @@ -0,0 +1,75 @@ +package com.tashow.cloud.tenant.core.job; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.exceptions.ExceptionUtil; +import cn.hutool.core.util.StrUtil; +import com.tashow.cloud.common.util.json.JsonUtils; +import com.tashow.cloud.tenant.core.service.TenantFrameworkService; +import com.tashow.cloud.tenant.core.util.TenantUtils; +import com.xxl.job.core.context.XxlJobContext; +import com.xxl.job.core.context.XxlJobHelper; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.exception.ExceptionUtils; +import org.aspectj.lang.ProceedingJoinPoint; +import org.aspectj.lang.annotation.Around; +import org.aspectj.lang.annotation.Aspect; + +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicBoolean; + +/** + * 多租户 JobHandler AOP + * 任务执行时,会按照租户逐个执行 Job 的逻辑 + * + * 注意,需要保证 JobHandler 的幂等性。因为 Job 因为某个租户执行失败重试时,之前执行成功的租户也会再次执行。 + * + * @author 芋道源码 + */ +@Aspect +@RequiredArgsConstructor +@Slf4j +public class TenantJobAspect { + + private final TenantFrameworkService tenantFrameworkService; + + @Around("@annotation(tenantJob)") + public void around(ProceedingJoinPoint joinPoint, TenantJob tenantJob) { + // 获得租户列表 + List tenantIds = tenantFrameworkService.getTenantIds(); + if (CollUtil.isEmpty(tenantIds)) { + return; + } + + // 逐个租户,执行 Job + Map results = new ConcurrentHashMap<>(); + AtomicBoolean success = new AtomicBoolean(true); // 标记,是否存在失败的情况 + XxlJobContext xxlJobContext = XxlJobContext.getXxlJobContext(); // XXL-Job 上下文 + tenantIds.parallelStream().forEach(tenantId -> { + // TODO 芋艿:先通过 parallel 实现并行;1)多个租户,是一条执行日志;2)异常的情况 + TenantUtils.execute(tenantId, () -> { + try { + XxlJobContext.setXxlJobContext(xxlJobContext); + // 执行 Job + Object result = joinPoint.proceed(); + results.put(tenantId, StrUtil.toStringOrEmpty(result)); + } catch (Throwable e) { + results.put(tenantId, ExceptionUtil.getRootCauseMessage(e)); + success.set(false); + // 打印异常 + XxlJobHelper.log(StrUtil.format("[多租户({}) 执行任务({}),发生异常:{}]", + tenantId, joinPoint.getSignature(), ExceptionUtils.getStackTrace(e))); + } + }); + }); + // 记录执行结果 + if (success.get()) { + XxlJobHelper.handleSuccess(JsonUtils.toJsonString(results)); + } else { + XxlJobHelper.handleFail(JsonUtils.toJsonString(results)); + } + } + +} diff --git a/tashow-framework/tashow-framework-tenant/src/main/java/com/tashow/cloud/tenant/core/mq/package-info.java b/tashow-framework/tashow-framework-tenant/src/main/java/com/tashow/cloud/tenant/core/mq/package-info.java new file mode 100644 index 0000000..3ba04e3 --- /dev/null +++ b/tashow-framework/tashow-framework-tenant/src/main/java/com/tashow/cloud/tenant/core/mq/package-info.java @@ -0,0 +1 @@ +package com.tashow.cloud.tenant.core.mq; \ No newline at end of file diff --git a/tashow-framework/tashow-framework-tenant/src/main/java/com/tashow/cloud/tenant/core/mq/rabbitmq/TenantRabbitMQInitializer.java b/tashow-framework/tashow-framework-tenant/src/main/java/com/tashow/cloud/tenant/core/mq/rabbitmq/TenantRabbitMQInitializer.java new file mode 100644 index 0000000..e8f0ecd --- /dev/null +++ b/tashow-framework/tashow-framework-tenant/src/main/java/com/tashow/cloud/tenant/core/mq/rabbitmq/TenantRabbitMQInitializer.java @@ -0,0 +1,23 @@ +package com.tashow.cloud.tenant.core.mq.rabbitmq; + +import org.springframework.amqp.rabbit.core.RabbitTemplate; +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.config.BeanPostProcessor; + +/** + * 多租户的 RabbitMQ 初始化器 + * + * @author 芋道源码 + */ +public class TenantRabbitMQInitializer implements BeanPostProcessor { + + @Override + public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { + if (bean instanceof RabbitTemplate) { + RabbitTemplate rabbitTemplate = (RabbitTemplate) bean; + rabbitTemplate.addBeforePublishPostProcessors(new TenantRabbitMQMessagePostProcessor()); + } + return bean; + } + +} diff --git a/tashow-framework/tashow-framework-tenant/src/main/java/com/tashow/cloud/tenant/core/mq/rabbitmq/TenantRabbitMQMessagePostProcessor.java b/tashow-framework/tashow-framework-tenant/src/main/java/com/tashow/cloud/tenant/core/mq/rabbitmq/TenantRabbitMQMessagePostProcessor.java new file mode 100644 index 0000000..74a42fa --- /dev/null +++ b/tashow-framework/tashow-framework-tenant/src/main/java/com/tashow/cloud/tenant/core/mq/rabbitmq/TenantRabbitMQMessagePostProcessor.java @@ -0,0 +1,32 @@ +package com.tashow.cloud.tenant.core.mq.rabbitmq; + +import com.tashow.cloud.tenant.core.context.TenantContextHolder; +import org.apache.kafka.clients.producer.ProducerInterceptor; +import org.springframework.amqp.AmqpException; +import org.springframework.amqp.core.Message; +import org.springframework.amqp.core.MessagePostProcessor; +import org.springframework.messaging.handler.invocation.InvocableHandlerMethod; + +import static com.tashow.cloud.web.web.core.util.WebFrameworkUtils.HEADER_TENANT_ID; + + +/** + * RabbitMQ 消息队列的多租户 {@link ProducerInterceptor} 实现类 + * + * 1. Producer 发送消息时,将 {@link TenantContextHolder} 租户编号,添加到消息的 Header 中 + * 2. Consumer 消费消息时,将消息的 Header 的租户编号,添加到 {@link TenantContextHolder} 中,通过 {@link InvocableHandlerMethod} 实现 + * + * @author 芋道源码 + */ +public class TenantRabbitMQMessagePostProcessor implements MessagePostProcessor { + + @Override + public Message postProcessMessage(Message message) throws AmqpException { + Long tenantId = TenantContextHolder.getTenantId(); + if (tenantId != null) { + message.getMessageProperties().getHeaders().put(HEADER_TENANT_ID, tenantId); + } + return message; + } + +} diff --git a/tashow-framework/tashow-framework-tenant/src/main/java/com/tashow/cloud/tenant/core/redis/TenantRedisCacheManager.java b/tashow-framework/tashow-framework-tenant/src/main/java/com/tashow/cloud/tenant/core/redis/TenantRedisCacheManager.java new file mode 100644 index 0000000..7b3f679 --- /dev/null +++ b/tashow-framework/tashow-framework-tenant/src/main/java/com/tashow/cloud/tenant/core/redis/TenantRedisCacheManager.java @@ -0,0 +1,46 @@ +package com.tashow.cloud.tenant.core.redis; + +import cn.hutool.core.collection.CollUtil; +import com.tashow.cloud.redis.core.TimeoutRedisCacheManager; +import com.tashow.cloud.tenant.core.context.TenantContextHolder; +import lombok.extern.slf4j.Slf4j; +import org.springframework.cache.Cache; +import org.springframework.data.redis.cache.RedisCacheConfiguration; +import org.springframework.data.redis.cache.RedisCacheManager; +import org.springframework.data.redis.cache.RedisCacheWriter; + +import java.util.Set; + +/** + * 多租户的 {@link RedisCacheManager} 实现类 + * + * 操作指定 name 的 {@link Cache} 时,自动拼接租户后缀,格式为 name + ":" + tenantId + 后缀 + * + * @author airhead + */ +@Slf4j +public class TenantRedisCacheManager extends TimeoutRedisCacheManager { + + private final Set ignoreCaches; + + public TenantRedisCacheManager(RedisCacheWriter cacheWriter, + RedisCacheConfiguration defaultCacheConfiguration, + Set ignoreCaches) { + super(cacheWriter, defaultCacheConfiguration); + this.ignoreCaches = ignoreCaches; + } + + @Override + public Cache getCache(String name) { + // 如果开启多租户,则 name 拼接租户后缀 + if (!TenantContextHolder.isIgnore() + && TenantContextHolder.getTenantId() != null + && !CollUtil.contains(ignoreCaches, name)) { + name = name + ":" + TenantContextHolder.getTenantId(); + } + + // 继续基于父方法 + return super.getCache(name); + } + +} diff --git a/tashow-framework/tashow-framework-tenant/src/main/java/com/tashow/cloud/tenant/core/rpc/TenantRequestInterceptor.java b/tashow-framework/tashow-framework-tenant/src/main/java/com/tashow/cloud/tenant/core/rpc/TenantRequestInterceptor.java new file mode 100644 index 0000000..8200cbd --- /dev/null +++ b/tashow-framework/tashow-framework-tenant/src/main/java/com/tashow/cloud/tenant/core/rpc/TenantRequestInterceptor.java @@ -0,0 +1,25 @@ +package com.tashow.cloud.tenant.core.rpc; + +import com.tashow.cloud.tenant.core.context.TenantContextHolder; +import feign.RequestInterceptor; +import feign.RequestTemplate; + +import static com.tashow.cloud.web.web.core.util.WebFrameworkUtils.HEADER_TENANT_ID; + + +/** + * Tenant 的 RequestInterceptor 实现类:Feign 请求时,将 {@link TenantContextHolder} 设置到 header 中,继续透传给被调用的服务 + * + * @author 芋道源码 + */ +public class TenantRequestInterceptor implements RequestInterceptor { + + @Override + public void apply(RequestTemplate requestTemplate) { + Long tenantId = TenantContextHolder.getTenantId(); + if (tenantId != null) { + requestTemplate.header(HEADER_TENANT_ID, String.valueOf(tenantId)); + } + } + +} diff --git a/tashow-framework/tashow-framework-tenant/src/main/java/com/tashow/cloud/tenant/core/security/TenantSecurityWebFilter.java b/tashow-framework/tashow-framework-tenant/src/main/java/com/tashow/cloud/tenant/core/security/TenantSecurityWebFilter.java new file mode 100644 index 0000000..f6c20d6 --- /dev/null +++ b/tashow-framework/tashow-framework-tenant/src/main/java/com/tashow/cloud/tenant/core/security/TenantSecurityWebFilter.java @@ -0,0 +1,117 @@ +package com.tashow.cloud.tenant.core.security; + +import cn.hutool.core.collection.CollUtil; +import com.tashow.cloud.common.exception.enums.GlobalErrorCodeConstants; +import com.tashow.cloud.common.pojo.CommonResult; +import com.tashow.cloud.common.util.servlet.ServletUtils; +import com.tashow.cloud.security.security.core.LoginUser; +import com.tashow.cloud.security.security.core.util.SecurityFrameworkUtils; +import com.tashow.cloud.tenant.config.TenantProperties; +import com.tashow.cloud.tenant.core.context.TenantContextHolder; +import com.tashow.cloud.tenant.core.service.TenantFrameworkService; +import com.tashow.cloud.web.web.config.WebProperties; +import com.tashow.cloud.web.web.core.filter.ApiRequestFilter; +import com.tashow.cloud.web.web.core.handler.GlobalExceptionHandler; +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import lombok.extern.slf4j.Slf4j; +import org.springframework.util.AntPathMatcher; + +import java.io.IOException; +import java.util.Objects; + +/** + * 多租户 Security Web 过滤器 + * 1. 如果是登陆的用户,校验是否有权限访问该租户,避免越权问题。 + * 2. 如果请求未带租户的编号,检查是否是忽略的 URL,否则也不允许访问。 + * 3. 校验租户是合法,例如说被禁用、到期 + * + * @author 芋道源码 + */ +@Slf4j +public class TenantSecurityWebFilter extends ApiRequestFilter { + + private final TenantProperties tenantProperties; + + private final AntPathMatcher pathMatcher; + + private final GlobalExceptionHandler globalExceptionHandler; + private final TenantFrameworkService tenantFrameworkService; + + public TenantSecurityWebFilter(TenantProperties tenantProperties, + WebProperties webProperties, + GlobalExceptionHandler globalExceptionHandler, + TenantFrameworkService tenantFrameworkService) { + super(webProperties); + this.tenantProperties = tenantProperties; + this.pathMatcher = new AntPathMatcher(); + this.globalExceptionHandler = globalExceptionHandler; + this.tenantFrameworkService = tenantFrameworkService; + } + + @Override + protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) + throws ServletException, IOException { + Long tenantId = TenantContextHolder.getTenantId(); + // 1. 登陆的用户,校验是否有权限访问该租户,避免越权问题。 + LoginUser user = SecurityFrameworkUtils.getLoginUser(); + if (user != null) { + // 如果获取不到租户编号,则尝试使用登陆用户的租户编号 + if (tenantId == null) { + tenantId = user.getTenantId(); + TenantContextHolder.setTenantId(tenantId); + // 如果传递了租户编号,则进行比对租户编号,避免越权问题 + } else if (!Objects.equals(user.getTenantId(), TenantContextHolder.getTenantId())) { + log.error("[doFilterInternal][租户({}) User({}/{}) 越权访问租户({}) URL({}/{})]", + user.getTenantId(), user.getId(), user.getUserType(), + TenantContextHolder.getTenantId(), request.getRequestURI(), request.getMethod()); + ServletUtils.writeJSON(response, CommonResult.error(GlobalErrorCodeConstants.FORBIDDEN.getCode(), + "您无权访问该租户的数据")); + return; + } + } + + // 如果非允许忽略租户的 URL,则校验租户是否合法 + if (!isIgnoreUrl(request)) { + // 2. 如果请求未带租户的编号,不允许访问。 + if (tenantId == null) { + log.error("[doFilterInternal][URL({}/{}) 未传递租户编号]", request.getRequestURI(), request.getMethod()); + ServletUtils.writeJSON(response, CommonResult.error(GlobalErrorCodeConstants.BAD_REQUEST.getCode(), + "请求的租户标识未传递,请进行排查")); + return; + } + // 3. 校验租户是合法,例如说被禁用、到期 + try { + tenantFrameworkService.validTenant(tenantId); + } catch (Throwable ex) { + CommonResult result = globalExceptionHandler.allExceptionHandler(request, ex); + ServletUtils.writeJSON(response, result); + return; + } + } else { // 如果是允许忽略租户的 URL,若未传递租户编号,则默认忽略租户编号,避免报错 + if (tenantId == null) { + TenantContextHolder.setIgnore(true); + } + } + + // 继续过滤 + chain.doFilter(request, response); + } + + private boolean isIgnoreUrl(HttpServletRequest request) { + // 快速匹配,保证性能 + if (CollUtil.contains(tenantProperties.getIgnoreUrls(), request.getRequestURI())) { + return true; + } + // 逐个 Ant 路径匹配 + for (String url : tenantProperties.getIgnoreUrls()) { + if (pathMatcher.match(url, request.getRequestURI())) { + return true; + } + } + return false; + } + +} diff --git a/tashow-framework/tashow-framework-tenant/src/main/java/com/tashow/cloud/tenant/core/service/TenantFrameworkService.java b/tashow-framework/tashow-framework-tenant/src/main/java/com/tashow/cloud/tenant/core/service/TenantFrameworkService.java new file mode 100644 index 0000000..a830ad1 --- /dev/null +++ b/tashow-framework/tashow-framework-tenant/src/main/java/com/tashow/cloud/tenant/core/service/TenantFrameworkService.java @@ -0,0 +1,26 @@ +package com.tashow.cloud.tenant.core.service; + +import java.util.List; + +/** + * Tenant 框架 Service 接口,定义获取租户信息 + * + * @author 芋道源码 + */ +public interface TenantFrameworkService { + + /** + * 获得所有租户 + * + * @return 租户编号数组 + */ + List getTenantIds(); + + /** + * 校验租户是否合法 + * + * @param id 租户编号 + */ + void validTenant(Long id); + +} diff --git a/tashow-framework/tashow-framework-tenant/src/main/java/com/tashow/cloud/tenant/core/service/TenantFrameworkServiceImpl.java b/tashow-framework/tashow-framework-tenant/src/main/java/com/tashow/cloud/tenant/core/service/TenantFrameworkServiceImpl.java new file mode 100644 index 0000000..8fec264 --- /dev/null +++ b/tashow-framework/tashow-framework-tenant/src/main/java/com/tashow/cloud/tenant/core/service/TenantFrameworkServiceImpl.java @@ -0,0 +1,66 @@ +package com.tashow.cloud.tenant.core.service; + +import com.tashow.cloud.common.pojo.CommonResult; +import com.tashow.cloud.systemapi.api.tenant.TenantApi; +import com.google.common.cache.CacheLoader; +import com.google.common.cache.LoadingCache; +import lombok.RequiredArgsConstructor; +import lombok.SneakyThrows; + +import java.time.Duration; +import java.util.List; + +import static com.tashow.cloud.common.util.cache.CacheUtils.buildAsyncReloadingCache; + + +/** + * Tenant 框架 Service 实现类 + * + * @author 芋道源码 + */ +@RequiredArgsConstructor +public class TenantFrameworkServiceImpl implements TenantFrameworkService { + + private final TenantApi tenantApi; + + /** + * 针对 {@link #getTenantIds()} 的缓存 + */ + private final LoadingCache> getTenantIdsCache = buildAsyncReloadingCache( + Duration.ofMinutes(1L), // 过期时间 1 分钟 + new CacheLoader>() { + + @Override + public List load(Object key) { + return tenantApi.getTenantIdList().getCheckedData(); + } + + }); + + /** + * 针对 {@link #validTenant(Long)} 的缓存 + */ + private final LoadingCache> validTenantCache = buildAsyncReloadingCache( + Duration.ofMinutes(1L), // 过期时间 1 分钟 + new CacheLoader>() { + + @Override + public CommonResult load(Long id) { + return tenantApi.validTenant(id); + } + + }); + + @Override + @SneakyThrows + public List getTenantIds() { + return getTenantIdsCache.get(Boolean.TRUE); + } + + @Override + @SneakyThrows + public void validTenant(Long id) { + validTenantCache.get(id).checkError(); + } + +} diff --git a/tashow-framework/tashow-framework-tenant/src/main/java/com/tashow/cloud/tenant/core/util/TenantUtils.java b/tashow-framework/tashow-framework-tenant/src/main/java/com/tashow/cloud/tenant/core/util/TenantUtils.java new file mode 100644 index 0000000..23bd2d9 --- /dev/null +++ b/tashow-framework/tashow-framework-tenant/src/main/java/com/tashow/cloud/tenant/core/util/TenantUtils.java @@ -0,0 +1,94 @@ +package com.tashow.cloud.tenant.core.util; + +import com.tashow.cloud.tenant.core.context.TenantContextHolder; + +import java.util.Map; +import java.util.concurrent.Callable; + +import static com.tashow.cloud.web.web.core.util.WebFrameworkUtils.HEADER_TENANT_ID; + + +/** + * 多租户 Util + * + * @author 芋道源码 + */ +public class TenantUtils { + + /** + * 使用指定租户,执行对应的逻辑 + * + * 注意,如果当前是忽略租户的情况下,会被强制设置成不忽略租户 + * 当然,执行完成后,还是会恢复回去 + * + * @param tenantId 租户编号 + * @param runnable 逻辑 + */ + public static void execute(Long tenantId, Runnable runnable) { + Long oldTenantId = TenantContextHolder.getTenantId(); + Boolean oldIgnore = TenantContextHolder.isIgnore(); + try { + TenantContextHolder.setTenantId(tenantId); + TenantContextHolder.setIgnore(false); + // 执行逻辑 + runnable.run(); + } finally { + TenantContextHolder.setTenantId(oldTenantId); + TenantContextHolder.setIgnore(oldIgnore); + } + } + + /** + * 使用指定租户,执行对应的逻辑 + * + * 注意,如果当前是忽略租户的情况下,会被强制设置成不忽略租户 + * 当然,执行完成后,还是会恢复回去 + * + * @param tenantId 租户编号 + * @param callable 逻辑 + */ + public static V execute(Long tenantId, Callable callable) { + Long oldTenantId = TenantContextHolder.getTenantId(); + Boolean oldIgnore = TenantContextHolder.isIgnore(); + try { + TenantContextHolder.setTenantId(tenantId); + TenantContextHolder.setIgnore(false); + // 执行逻辑 + return callable.call(); + } catch (Exception e) { + throw new RuntimeException(e); + } finally { + TenantContextHolder.setTenantId(oldTenantId); + TenantContextHolder.setIgnore(oldIgnore); + } + } + + /** + * 忽略租户,执行对应的逻辑 + * + * @param runnable 逻辑 + */ + public static void executeIgnore(Runnable runnable) { + Boolean oldIgnore = TenantContextHolder.isIgnore(); + try { + TenantContextHolder.setIgnore(true); + // 执行逻辑 + runnable.run(); + } finally { + TenantContextHolder.setIgnore(oldIgnore); + } + } + + /** + * 将多租户编号,添加到 header 中 + * + * @param headers HTTP 请求 headers + * @param tenantId 租户编号 + */ + public static void addTenantHeader(Map headers, Long tenantId) { + if (tenantId != null) { + headers.put(HEADER_TENANT_ID, tenantId.toString()); + } + } + +} diff --git a/tashow-framework/tashow-framework-tenant/src/main/java/com/tashow/cloud/tenant/core/web/TenantContextWebFilter.java b/tashow-framework/tashow-framework-tenant/src/main/java/com/tashow/cloud/tenant/core/web/TenantContextWebFilter.java new file mode 100644 index 0000000..3e7c42b --- /dev/null +++ b/tashow-framework/tashow-framework-tenant/src/main/java/com/tashow/cloud/tenant/core/web/TenantContextWebFilter.java @@ -0,0 +1,37 @@ +package com.tashow.cloud.tenant.core.web; + +import com.tashow.cloud.tenant.core.context.TenantContextHolder; +import com.tashow.cloud.web.web.core.util.WebFrameworkUtils; +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.springframework.web.filter.OncePerRequestFilter; + +import java.io.IOException; + +/** + * 多租户 Context Web 过滤器 + * 将请求 Header 中的 tenant-id 解析出来,添加到 {@link TenantContextHolder} 中,这样后续的 DB 等操作,可以获得到租户编号。 + * + * @author 芋道源码 + */ +public class TenantContextWebFilter extends OncePerRequestFilter { + + @Override + protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) + throws ServletException, IOException { + // 设置 + Long tenantId = WebFrameworkUtils.getTenantId(request); + if (tenantId != null) { + TenantContextHolder.setTenantId(tenantId); + } + try { + chain.doFilter(request, response); + } finally { + // 清理 + TenantContextHolder.clear(); + } + } + +} diff --git a/tashow-framework/tashow-framework-tenant/src/main/java/org/springframework/messaging/handler/invocation/InvocableHandlerMethod.java b/tashow-framework/tashow-framework-tenant/src/main/java/org/springframework/messaging/handler/invocation/InvocableHandlerMethod.java new file mode 100644 index 0000000..3a73860 --- /dev/null +++ b/tashow-framework/tashow-framework-tenant/src/main/java/org/springframework/messaging/handler/invocation/InvocableHandlerMethod.java @@ -0,0 +1,275 @@ +/* + * Copyright 2002-2023 the original author or authors. + * + * 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 + * + * https://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.springframework.messaging.handler.invocation; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Type; +import java.util.Arrays; + +import com.tashow.cloud.tenant.core.util.TenantUtils; +import org.springframework.core.DefaultParameterNameDiscoverer; +import org.springframework.core.MethodParameter; +import org.springframework.core.ParameterNameDiscoverer; +import org.springframework.core.ResolvableType; +import org.springframework.lang.Nullable; +import org.springframework.messaging.Message; +import org.springframework.messaging.handler.HandlerMethod; +import org.springframework.util.ObjectUtils; + +import static com.tashow.cloud.web.web.core.util.WebFrameworkUtils.HEADER_TENANT_ID; + + +/** + * Extension of {@link HandlerMethod} that invokes the underlying method with + * argument values resolved from the current HTTP request through a list of + * {@link HandlerMethodArgumentResolver}. + * + * 针对 rabbitmq-spring 和 kafka-spring,不存在合适的拓展点,可以实现 Consumer 消费前,读取 Header 中的 tenant-id 设置到 {@link TenantContextHolder} 中 + * TODO 芋艿:持续跟进,看看有没新的拓展点 + * + * @author Rossen Stoyanchev + * @author Juergen Hoeller + * @since 4.0 + */ +public class InvocableHandlerMethod extends HandlerMethod { + + private static final Object[] EMPTY_ARGS = new Object[0]; + + + private HandlerMethodArgumentResolverComposite resolvers = new HandlerMethodArgumentResolverComposite(); + + private ParameterNameDiscoverer parameterNameDiscoverer = new DefaultParameterNameDiscoverer(); + + + /** + * Create an instance from a {@code HandlerMethod}. + */ + public InvocableHandlerMethod(HandlerMethod handlerMethod) { + super(handlerMethod); + } + + /** + * Create an instance from a bean instance and a method. + */ + public InvocableHandlerMethod(Object bean, Method method) { + super(bean, method); + } + + /** + * Construct a new handler method with the given bean instance, method name and parameters. + * @param bean the object bean + * @param methodName the method name + * @param parameterTypes the method parameter types + * @throws NoSuchMethodException when the method cannot be found + */ + public InvocableHandlerMethod(Object bean, String methodName, Class... parameterTypes) + throws NoSuchMethodException { + + super(bean, methodName, parameterTypes); + } + + + /** + * Set {@link HandlerMethodArgumentResolver HandlerMethodArgumentResolvers} to use for resolving method argument values. + */ + public void setMessageMethodArgumentResolvers(HandlerMethodArgumentResolverComposite argumentResolvers) { + this.resolvers = argumentResolvers; + } + + /** + * Set the ParameterNameDiscoverer for resolving parameter names when needed + * (e.g. default request attribute name). + *

Default is a {@link org.springframework.core.DefaultParameterNameDiscoverer}. + */ + public void setParameterNameDiscoverer(ParameterNameDiscoverer parameterNameDiscoverer) { + this.parameterNameDiscoverer = parameterNameDiscoverer; + } + + + /** + * Invoke the method after resolving its argument values in the context of the given message. + *

Argument values are commonly resolved through + * {@link HandlerMethodArgumentResolver HandlerMethodArgumentResolvers}. + * The {@code providedArgs} parameter however may supply argument values to be used directly, + * i.e. without argument resolution. + *

Delegates to {@link #getMethodArgumentValues} and calls {@link #doInvoke} with the + * resolved arguments. + * @param message the current message being processed + * @param providedArgs "given" arguments matched by type, not resolved + * @return the raw value returned by the invoked method + * @throws Exception raised if no suitable argument resolver can be found, + * or if the method raised an exception + * @see #getMethodArgumentValues + * @see #doInvoke + */ + @Nullable + public Object invoke(Message message, Object... providedArgs) throws Exception { + Object[] args = getMethodArgumentValues(message, providedArgs); + if (logger.isTraceEnabled()) { + logger.trace("Arguments: " + Arrays.toString(args)); + } + // 注意:如下是本类的改动点!!! + // 情况一:无租户编号的情况 + Long tenantId= parseTenantId(message); + if (tenantId == null) { + return doInvoke(args); + } + // 情况二:有租户的情况下 + return TenantUtils.execute(tenantId, () -> doInvoke(args)); + } + + private Long parseTenantId(Message message) { + Object tenantId = message.getHeaders().get(HEADER_TENANT_ID); + if (tenantId == null) { + return null; + } + if (tenantId instanceof Long) { + return (Long) tenantId; + } + if (tenantId instanceof Number) { + return ((Number) tenantId).longValue(); + } + if (tenantId instanceof String) { + return Long.parseLong((String) tenantId); + } + if (tenantId instanceof byte[]) { + return Long.parseLong(new String((byte[]) tenantId)); + } + throw new IllegalArgumentException("未知的数据类型:" + tenantId); + } + + /** + * Get the method argument values for the current message, checking the provided + * argument values and falling back to the configured argument resolvers. + *

The resulting array will be passed into {@link #doInvoke}. + * @since 5.1.2 + */ + protected Object[] getMethodArgumentValues(Message message, Object... providedArgs) throws Exception { + MethodParameter[] parameters = getMethodParameters(); + if (ObjectUtils.isEmpty(parameters)) { + return EMPTY_ARGS; + } + + Object[] args = new Object[parameters.length]; + for (int i = 0; i < parameters.length; i++) { + MethodParameter parameter = parameters[i]; + parameter.initParameterNameDiscovery(this.parameterNameDiscoverer); + args[i] = findProvidedArgument(parameter, providedArgs); + if (args[i] != null) { + continue; + } + if (!this.resolvers.supportsParameter(parameter)) { + throw new MethodArgumentResolutionException( + message, parameter, formatArgumentError(parameter, "No suitable resolver")); + } + try { + args[i] = this.resolvers.resolveArgument(parameter, message); + } + catch (Exception ex) { + // Leave stack trace for later, exception may actually be resolved and handled... + if (logger.isDebugEnabled()) { + String exMsg = ex.getMessage(); + if (exMsg != null && !exMsg.contains(parameter.getExecutable().toGenericString())) { + logger.debug(formatArgumentError(parameter, exMsg)); + } + } + throw ex; + } + } + return args; + } + + /** + * Invoke the handler method with the given argument values. + */ + @Nullable + protected Object doInvoke(Object... args) throws Exception { + try { + return getBridgedMethod().invoke(getBean(), args); + } + catch (IllegalArgumentException ex) { + assertTargetBean(getBridgedMethod(), getBean(), args); + String text = (ex.getMessage() == null || ex.getCause() instanceof NullPointerException) ? + "Illegal argument": ex.getMessage(); + throw new IllegalStateException(formatInvokeError(text, args), ex); + } + catch (InvocationTargetException ex) { + // Unwrap for HandlerExceptionResolvers ... + Throwable targetException = ex.getTargetException(); + if (targetException instanceof RuntimeException runtimeException) { + throw runtimeException; + } + else if (targetException instanceof Error error) { + throw error; + } + else if (targetException instanceof Exception exception) { + throw exception; + } + else { + throw new IllegalStateException(formatInvokeError("Invocation failure", args), targetException); + } + } + } + + MethodParameter getAsyncReturnValueType(@Nullable Object returnValue) { + return new AsyncResultMethodParameter(returnValue); + } + + + private class AsyncResultMethodParameter extends AnnotatedMethodParameter { + + @Nullable + private final Object returnValue; + + private final ResolvableType returnType; + + public AsyncResultMethodParameter(@Nullable Object returnValue) { + super(-1); + this.returnValue = returnValue; + this.returnType = ResolvableType.forType(super.getGenericParameterType()).getGeneric(); + } + + protected AsyncResultMethodParameter(AsyncResultMethodParameter original) { + super(original); + this.returnValue = original.returnValue; + this.returnType = original.returnType; + } + + @Override + public Class getParameterType() { + if (this.returnValue != null) { + return this.returnValue.getClass(); + } + if (!ResolvableType.NONE.equals(this.returnType)) { + return this.returnType.toClass(); + } + return super.getParameterType(); + } + + @Override + public Type getGenericParameterType() { + return this.returnType.getType(); + } + + @Override + public AsyncResultMethodParameter clone() { + return new AsyncResultMethodParameter(this); + } + } + +} diff --git a/tashow-framework/tashow-framework-tenant/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/tashow-framework/tashow-framework-tenant/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports new file mode 100644 index 0000000..3ec3751 --- /dev/null +++ b/tashow-framework/tashow-framework-tenant/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -0,0 +1,2 @@ +com.tashow.cloud.tenant.config.TenantRpcAutoConfiguration +com.tashow.cloud.tenant.config.TenantAutoConfiguration diff --git a/tashow-framework/tashow-framework-web/pom.xml b/tashow-framework/tashow-framework-web/pom.xml new file mode 100644 index 0000000..cbe0fa2 --- /dev/null +++ b/tashow-framework/tashow-framework-web/pom.xml @@ -0,0 +1,83 @@ + + + 4.0.0 + + com.tashow.cloud + tashow-framework + ${revision} + + tashow-framework-web + jar + + ${project.artifactId} + Web 框架,全局异常、API 日志、脱敏、错误码等 + + + + com.tashow.cloud + tashow-common + + + + + org.springframework.boot + spring-boot-configuration-processor + true + + + + + org.springframework.boot + spring-boot-starter-web + + + + org.springframework.boot + spring-boot-starter-validation + + + + org.springframework.security + spring-security-core + provided + + + + + com.tashow.cloud + tashow-framework-rpc + true + + + + + com.tashow.cloud + tashow-infra-api + + + com.tashow.cloud + tashow-system-api + + + + + org.jsoup + jsoup + + + + + org.springframework.boot + spring-boot-starter-test + test + + + org.mockito + mockito-inline + test + + + + diff --git a/tashow-framework/tashow-framework-web/src/main/java/com/tashow/cloud/web/apilog/config/ApiLogAutoConfiguration.java b/tashow-framework/tashow-framework-web/src/main/java/com/tashow/cloud/web/apilog/config/ApiLogAutoConfiguration.java new file mode 100644 index 0000000..fb5d57a --- /dev/null +++ b/tashow-framework/tashow-framework-web/src/main/java/com/tashow/cloud/web/apilog/config/ApiLogAutoConfiguration.java @@ -0,0 +1,45 @@ +package com.tashow.cloud.web.apilog.config; + +import com.tashow.cloud.infraapi.api.logger.ApiAccessLogApi; +import com.tashow.cloud.common.enums.WebFilterOrderEnum; +import com.tashow.cloud.web.apilog.core.filter.ApiAccessLogFilter; +import com.tashow.cloud.web.apilog.core.interceptor.ApiAccessLogInterceptor; +import com.tashow.cloud.web.web.config.WebAutoConfiguration; +import com.tashow.cloud.web.web.config.WebProperties; +import jakarta.servlet.Filter; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.web.servlet.FilterRegistrationBean; +import org.springframework.context.annotation.Bean; +import org.springframework.web.servlet.config.annotation.InterceptorRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +@AutoConfiguration(after = WebAutoConfiguration.class) +public class ApiLogAutoConfiguration implements WebMvcConfigurer { + + /** + * 创建 ApiAccessLogFilter Bean,记录 API 请求日志 + */ + @Bean + @ConditionalOnProperty(prefix = "tashow.access-log", value = "enable", matchIfMissing = true) // 允许使用 yudao.access-log.enable=false 禁用访问日志 + @SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection") + public FilterRegistrationBean apiAccessLogFilter(WebProperties webProperties, + @Value("${spring.application.name}") String applicationName, + ApiAccessLogApi apiAccessLogApi) { + ApiAccessLogFilter filter = new ApiAccessLogFilter(webProperties, applicationName, apiAccessLogApi); + return createFilterBean(filter, WebFilterOrderEnum.API_ACCESS_LOG_FILTER); + } + + private static FilterRegistrationBean createFilterBean(T filter, Integer order) { + FilterRegistrationBean bean = new FilterRegistrationBean<>(filter); + bean.setOrder(order); + return bean; + } + + @Override + public void addInterceptors(InterceptorRegistry registry) { + registry.addInterceptor(new ApiAccessLogInterceptor()); + } + +} diff --git a/tashow-framework/tashow-framework-web/src/main/java/com/tashow/cloud/web/apilog/config/ApiLogRpcAutoConfiguration.java b/tashow-framework/tashow-framework-web/src/main/java/com/tashow/cloud/web/apilog/config/ApiLogRpcAutoConfiguration.java new file mode 100644 index 0000000..a745878 --- /dev/null +++ b/tashow-framework/tashow-framework-web/src/main/java/com/tashow/cloud/web/apilog/config/ApiLogRpcAutoConfiguration.java @@ -0,0 +1,16 @@ +package com.tashow.cloud.web.apilog.config; + +import com.tashow.cloud.infraapi.api.logger.ApiAccessLogApi; +import com.tashow.cloud.infraapi.api.logger.ApiErrorLogApi; +import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.cloud.openfeign.EnableFeignClients; + +/** + * API 日志使用到 Feign 的配置项 + * + * @author 芋道源码 + */ +@AutoConfiguration +@EnableFeignClients(clients = {ApiAccessLogApi.class, ApiErrorLogApi.class}) // 主要是引入相关的 API 服务 +public class ApiLogRpcAutoConfiguration { +} diff --git a/tashow-framework/tashow-framework-web/src/main/java/com/tashow/cloud/web/apilog/core/annotation/ApiAccessLog.java b/tashow-framework/tashow-framework-web/src/main/java/com/tashow/cloud/web/apilog/core/annotation/ApiAccessLog.java new file mode 100644 index 0000000..4484c11 --- /dev/null +++ b/tashow-framework/tashow-framework-web/src/main/java/com/tashow/cloud/web/apilog/core/annotation/ApiAccessLog.java @@ -0,0 +1,65 @@ +package com.tashow.cloud.web.apilog.core.annotation; + +import com.tashow.cloud.web.apilog.core.enums.OperateTypeEnum; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * 访问日志注解 + * + * @author 芋道源码 + */ +@Target({ElementType.METHOD}) +@Retention(RetentionPolicy.RUNTIME) +public @interface ApiAccessLog { + + // ========== 开关字段 ========== + + /** + * 是否记录访问日志 + */ + boolean enable() default true; + + /** + * 是否记录请求参数 + * + *

默认记录,主要考虑请求数据一般不大。可手动设置为 false 进行关闭 + */ + boolean requestEnable() default true; + + /** + * 是否记录响应结果 + * + *

默认不记录,主要考虑响应数据可能比较大。可手动设置为 true 进行打开 + */ + boolean responseEnable() default false; + + /** + * 敏感参数数组 + * + *

添加后,请求参数、响应结果不会记录该参数 + */ + String[] sanitizeKeys() default {}; + + // ========== 模块字段 ========== + + /** + * 操作模块 + */ + String operateModule() default ""; + + /** + * 操作名 + */ + String operateName() default ""; + + /** + * 操作分类 + * + *

实际并不是数组,因为枚举不能设置 null 作为默认值 + */ + OperateTypeEnum[] operateType() default {}; +} diff --git a/tashow-framework/tashow-framework-web/src/main/java/com/tashow/cloud/web/apilog/core/enums/OperateTypeEnum.java b/tashow-framework/tashow-framework-web/src/main/java/com/tashow/cloud/web/apilog/core/enums/OperateTypeEnum.java new file mode 100644 index 0000000..35f7697 --- /dev/null +++ b/tashow-framework/tashow-framework-web/src/main/java/com/tashow/cloud/web/apilog/core/enums/OperateTypeEnum.java @@ -0,0 +1,51 @@ +package com.tashow.cloud.web.apilog.core.enums; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * 操作日志的操作类型 + * + * @author ruoyi + */ +@Getter +@AllArgsConstructor +public enum OperateTypeEnum { + + /** + * 查询 + */ + GET(1), + /** + * 新增 + */ + CREATE(2), + /** + * 修改 + */ + UPDATE(3), + /** + * 删除 + */ + DELETE(4), + /** + * 导出 + */ + EXPORT(5), + /** + * 导入 + */ + IMPORT(6), + /** + * 其它 + * + * 在无法归类时,可以选择使用其它。因为还有操作名可以进一步标识 + */ + OTHER(0); + + /** + * 类型 + */ + private final Integer type; + +} diff --git a/tashow-framework/tashow-framework-web/src/main/java/com/tashow/cloud/web/apilog/core/filter/ApiAccessLogFilter.java b/tashow-framework/tashow-framework-web/src/main/java/com/tashow/cloud/web/apilog/core/filter/ApiAccessLogFilter.java new file mode 100644 index 0000000..cee148c --- /dev/null +++ b/tashow-framework/tashow-framework-web/src/main/java/com/tashow/cloud/web/apilog/core/filter/ApiAccessLogFilter.java @@ -0,0 +1,231 @@ +package com.tashow.cloud.web.apilog.core.filter; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.date.LocalDateTimeUtil; +import cn.hutool.core.exceptions.ExceptionUtil; +import cn.hutool.core.map.MapUtil; +import cn.hutool.core.util.ArrayUtil; +import cn.hutool.core.util.BooleanUtil; +import cn.hutool.core.util.StrUtil; +import com.fasterxml.jackson.databind.JsonNode; +import com.tashow.cloud.common.exception.enums.GlobalErrorCodeConstants; +import com.tashow.cloud.common.pojo.CommonResult; +import com.tashow.cloud.common.util.json.JsonUtils; +import com.tashow.cloud.common.util.monitor.TracerUtils; +import com.tashow.cloud.common.util.servlet.ServletUtils; +import com.tashow.cloud.infraapi.api.logger.ApiAccessLogApi; +import com.tashow.cloud.infraapi.api.logger.dto.ApiAccessLogCreateReqDTO; +import com.tashow.cloud.web.apilog.core.annotation.ApiAccessLog; +import com.tashow.cloud.web.apilog.core.enums.OperateTypeEnum; +import com.tashow.cloud.web.web.config.WebProperties; +import com.tashow.cloud.web.web.core.filter.ApiRequestFilter; +import com.tashow.cloud.web.web.core.util.WebFrameworkUtils; +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.method.HandlerMethod; + +import java.io.IOException; +import java.time.LocalDateTime; +import java.time.temporal.ChronoUnit; +import java.util.Iterator; +import java.util.Map; + +import static com.tashow.cloud.common.util.json.JsonUtils.toJsonString; +import static com.tashow.cloud.web.apilog.core.interceptor.ApiAccessLogInterceptor.ATTRIBUTE_HANDLER_METHOD; + +/** + * API 访问日志 Filter + * + *

目的:记录 API 访问日志到数据库中 + * + * @author 芋道源码 + */ +@Slf4j +public class ApiAccessLogFilter extends ApiRequestFilter { + + private static final String[] SANITIZE_KEYS = new String[]{"password", "token", "accessToken", "refreshToken"}; + + private final String applicationName; + + private final ApiAccessLogApi apiAccessLogApi; + + public ApiAccessLogFilter(WebProperties webProperties, String applicationName, ApiAccessLogApi apiAccessLogApi) { + super(webProperties); + this.applicationName = applicationName; + this.apiAccessLogApi = apiAccessLogApi; + } + + @Override + @SuppressWarnings("NullableProblems") + protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { + // 获得开始时间 + LocalDateTime beginTime = LocalDateTime.now(); + // 提前获得参数,避免 XssFilter 过滤处理 + Map queryString = ServletUtils.getParamMap(request); + String requestBody = ServletUtils.isJsonRequest(request) ? ServletUtils.getBody(request) : null; + + try { + // 继续过滤器 + filterChain.doFilter(request, response); + // 正常执行,记录日志 + createApiAccessLog(request, beginTime, queryString, requestBody, null); + } catch (Exception ex) { + // 异常执行,记录日志 + createApiAccessLog(request, beginTime, queryString, requestBody, ex); + throw ex; + } + } + + private void createApiAccessLog(HttpServletRequest request, LocalDateTime beginTime, Map queryString, String requestBody, Exception ex) { + ApiAccessLogCreateReqDTO accessLog = new ApiAccessLogCreateReqDTO(); + try { + boolean enable = buildApiAccessLog(accessLog, request, beginTime, queryString, requestBody, ex); + if (!enable) { + return; + } + apiAccessLogApi.createApiAccessLogAsync(accessLog); + } catch (Throwable th) { + log.error("[createApiAccessLog][url({}) log({}) 发生异常]", request.getRequestURI(), toJsonString(accessLog), th); + } + } + + private boolean buildApiAccessLog(ApiAccessLogCreateReqDTO accessLog, HttpServletRequest request, LocalDateTime beginTime, Map queryString, String requestBody, Exception ex) { + // 判断:是否要记录操作日志 + HandlerMethod handlerMethod = (HandlerMethod) request.getAttribute(ATTRIBUTE_HANDLER_METHOD); + ApiAccessLog accessLogAnnotation = null; + if (handlerMethod != null) { + accessLogAnnotation = handlerMethod.getMethodAnnotation(ApiAccessLog.class); + if (accessLogAnnotation != null && BooleanUtil.isFalse(accessLogAnnotation.enable())) { + return false; + } + } + + // 处理用户信息 + accessLog.setUserId(WebFrameworkUtils.getLoginUserId(request)).setUserType(WebFrameworkUtils.getLoginUserType(request)); + // 设置访问结果 + CommonResult result = WebFrameworkUtils.getCommonResult(request); + if (result != null) { + accessLog.setResultCode(result.getCode()).setResultMsg(result.getMsg()); + } else if (ex != null) { + accessLog.setResultCode(GlobalErrorCodeConstants.INTERNAL_SERVER_ERROR.getCode()).setResultMsg(ExceptionUtil.getRootCauseMessage(ex)); + } else { + accessLog.setResultCode(GlobalErrorCodeConstants.SUCCESS.getCode()).setResultMsg(""); + } + // 设置请求字段 + accessLog.setTraceId(TracerUtils.getTraceId()).setApplicationName(applicationName).setRequestUrl(request.getRequestURI()).setRequestMethod(request.getMethod()).setUserAgent(ServletUtils.getUserAgent(request)).setUserIp(ServletUtils.getClientIP(request)); + String[] sanitizeKeys = accessLogAnnotation != null ? accessLogAnnotation.sanitizeKeys() : null; + Boolean requestEnable = accessLogAnnotation != null ? accessLogAnnotation.requestEnable() : Boolean.TRUE; + if (!BooleanUtil.isFalse(requestEnable)) { // 默认记录,所以判断 !false + Map requestParams = MapUtil.builder().put("query", sanitizeMap(queryString, sanitizeKeys)).put("body", sanitizeJson(requestBody, sanitizeKeys)).build(); + accessLog.setRequestParams(toJsonString(requestParams)); + } + Boolean responseEnable = accessLogAnnotation != null ? accessLogAnnotation.responseEnable() : Boolean.FALSE; + if (BooleanUtil.isTrue(responseEnable)) { // 默认不记录,默认强制要求 true + accessLog.setResponseBody(sanitizeJson(result, sanitizeKeys)); + } + // 持续时间 + accessLog.setBeginTime(beginTime).setEndTime(LocalDateTime.now()).setDuration((int) LocalDateTimeUtil.between(accessLog.getBeginTime(), accessLog.getEndTime(), ChronoUnit.MILLIS)); + + // 操作模块 + if (handlerMethod != null) { + String operateModule = accessLogAnnotation != null && StrUtil.isNotBlank(accessLogAnnotation.operateModule()) ? accessLogAnnotation.operateModule() : null; + String operateName = accessLogAnnotation != null && StrUtil.isNotBlank(accessLogAnnotation.operateName()) ? accessLogAnnotation.operateName() : null; + OperateTypeEnum operateType = accessLogAnnotation != null && accessLogAnnotation.operateType().length > 0 ? accessLogAnnotation.operateType()[0] : parseOperateLogType(request); + accessLog.setOperateModule(operateModule).setOperateName(operateName).setOperateType(operateType.getType()); + } + return true; + } + + // ========== 解析 @ApiAccessLog、@Swagger 注解 ========== + + private static OperateTypeEnum parseOperateLogType(HttpServletRequest request) { + RequestMethod requestMethod = RequestMethod.resolve(request.getMethod()); + if (requestMethod == null) { + return OperateTypeEnum.OTHER; + } + switch (requestMethod) { + case GET: + return OperateTypeEnum.GET; + case POST: + return OperateTypeEnum.CREATE; + case PUT: + return OperateTypeEnum.UPDATE; + case DELETE: + return OperateTypeEnum.DELETE; + default: + return OperateTypeEnum.OTHER; + } + } + + // ========== 请求和响应的脱敏逻辑,移除类似 password、token 等敏感字段 ========== + + private static String sanitizeMap(Map map, String[] sanitizeKeys) { + if (CollUtil.isEmpty(map)) { + return null; + } + if (sanitizeKeys != null) { + MapUtil.removeAny(map, sanitizeKeys); + } + MapUtil.removeAny(map, SANITIZE_KEYS); + return toJsonString(map); + } + + private static String sanitizeJson(String jsonString, String[] sanitizeKeys) { + if (StrUtil.isEmpty(jsonString)) { + return null; + } + try { + JsonNode rootNode = JsonUtils.parseTree(jsonString); + sanitizeJson(rootNode, sanitizeKeys); + return toJsonString(rootNode); + } catch (Exception e) { + // 脱敏失败的情况下,直接忽略异常,避免影响用户请求 + log.error("[sanitizeJson][脱敏({}) 发生异常]", jsonString, e); + return jsonString; + } + } + + private static String sanitizeJson(CommonResult commonResult, String[] sanitizeKeys) { + if (commonResult == null) { + return null; + } + String jsonString = toJsonString(commonResult); + try { + JsonNode rootNode = JsonUtils.parseTree(jsonString); + sanitizeJson(rootNode.get("data"), sanitizeKeys); // 只处理 data 字段,不处理 code、msg 字段,避免错误被脱敏掉 + return toJsonString(rootNode); + } catch (Exception e) { + // 脱敏失败的情况下,直接忽略异常,避免影响用户请求 + log.error("[sanitizeJson][脱敏({}) 发生异常]", jsonString, e); + return jsonString; + } + } + + private static void sanitizeJson(JsonNode node, String[] sanitizeKeys) { + // 情况一:数组,遍历处理 + if (node.isArray()) { + for (JsonNode childNode : node) { + sanitizeJson(childNode, sanitizeKeys); + } + return; + } + // 情况二:非 Object,只是某个值,直接返回 + if (!node.isObject()) { + return; + } + // 情况三:Object,遍历处理 + Iterator> iterator = node.properties().iterator(); + while (iterator.hasNext()) { + Map.Entry entry = iterator.next(); + if (ArrayUtil.contains(sanitizeKeys, entry.getKey()) || ArrayUtil.contains(SANITIZE_KEYS, entry.getKey())) { + iterator.remove(); + continue; + } + sanitizeJson(entry.getValue(), sanitizeKeys); + } + } +} diff --git a/tashow-framework/tashow-framework-web/src/main/java/com/tashow/cloud/web/apilog/core/interceptor/ApiAccessLogInterceptor.java b/tashow-framework/tashow-framework-web/src/main/java/com/tashow/cloud/web/apilog/core/interceptor/ApiAccessLogInterceptor.java new file mode 100644 index 0000000..6de4c67 --- /dev/null +++ b/tashow-framework/tashow-framework-web/src/main/java/com/tashow/cloud/web/apilog/core/interceptor/ApiAccessLogInterceptor.java @@ -0,0 +1,103 @@ +package com.tashow.cloud.web.apilog.core.interceptor; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.io.FileUtil; +import cn.hutool.core.io.resource.ResourceUtil; +import cn.hutool.core.util.StrUtil; +import com.tashow.cloud.common.util.servlet.ServletUtils; +import com.tashow.cloud.common.util.spring.SpringUtils; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import lombok.extern.slf4j.Slf4j; +import org.springframework.util.StopWatch; +import org.springframework.web.method.HandlerMethod; +import org.springframework.web.servlet.HandlerInterceptor; + +import java.lang.reflect.Method; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.stream.IntStream; + +/** + * API 访问日志 Interceptor + * + * 目的:在非 prod 环境时,打印 request 和 response 两条日志到日志文件(控制台)中。 + * + * @author 芋道源码 + */ +@Slf4j +public class ApiAccessLogInterceptor implements HandlerInterceptor { + + public static final String ATTRIBUTE_HANDLER_METHOD = "HANDLER_METHOD"; + + private static final String ATTRIBUTE_STOP_WATCH = "ApiAccessLogInterceptor.StopWatch"; + + @Override + public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) { + // 记录 HandlerMethod,提供给 ApiAccessLogFilter 使用 + HandlerMethod handlerMethod = handler instanceof HandlerMethod ? (HandlerMethod) handler : null; + if (handlerMethod != null) { + request.setAttribute(ATTRIBUTE_HANDLER_METHOD, handlerMethod); + } + + // 打印 request 日志 + if (!SpringUtils.isProd()) { + Map queryString = ServletUtils.getParamMap(request); + String requestBody = ServletUtils.isJsonRequest(request) ? ServletUtils.getBody(request) : null; + if (CollUtil.isEmpty(queryString) && StrUtil.isEmpty(requestBody)) { + log.info("[preHandle][开始请求 URL({}) 无参数]", request.getRequestURI()); + } else { + log.info("[preHandle][开始请求 URL({}) 参数({})]", request.getRequestURI(), + StrUtil.blankToDefault(requestBody, queryString.toString())); + } + // 计时 + StopWatch stopWatch = new StopWatch(); + stopWatch.start(); + request.setAttribute(ATTRIBUTE_STOP_WATCH, stopWatch); + // 打印 Controller 路径 + printHandlerMethodPosition(handlerMethod); + } + return true; + } + + @Override + public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) { + // 打印 response 日志ss + if (!SpringUtils.isProd()) { + StopWatch stopWatch = (StopWatch) request.getAttribute(ATTRIBUTE_STOP_WATCH); + stopWatch.stop(); + log.info("[afterCompletion][完成请求 URL({}) 耗时({} ms)]", + request.getRequestURI(), stopWatch.getTotalTimeMillis()); + } + } + + /** + * 打印 Controller 方法路径 + */ + private void printHandlerMethodPosition(HandlerMethod handlerMethod) { + if (handlerMethod == null) { + return; + } + Method method = handlerMethod.getMethod(); + Class clazz = method.getDeclaringClass(); + try { + // 获取 method 的 lineNumber + List clazzContents = FileUtil.readUtf8Lines( + ResourceUtil.getResource(null, clazz).getPath().replace("/target/classes/", "/src/main/java/") + + clazz.getSimpleName() + ".java"); + Optional lineNumber = IntStream.range(0, clazzContents.size()) + .filter(i -> clazzContents.get(i).contains(" " + method.getName() + "(")) // 简单匹配,不考虑方法重名 + .mapToObj(i -> i + 1) // 行号从 1 开始 + .findFirst(); + if (!lineNumber.isPresent()) { + return; + } + // 打印结果 + System.out.printf("\tController 方法路径:%s(%s.java:%d)\n", clazz.getName(), clazz.getSimpleName(), lineNumber.get()); + } catch (Exception ignore) { + // 忽略异常。原因:仅仅打印,非重要逻辑 + } + } + +} diff --git a/tashow-framework/tashow-framework-web/src/main/java/com/tashow/cloud/web/apilog/package-info.java b/tashow-framework/tashow-framework-web/src/main/java/com/tashow/cloud/web/apilog/package-info.java new file mode 100644 index 0000000..f35e3d1 --- /dev/null +++ b/tashow-framework/tashow-framework-web/src/main/java/com/tashow/cloud/web/apilog/package-info.java @@ -0,0 +1,8 @@ +/** + * API 日志:包含两类 + * 1. API 访问日志:记录用户访问 API 的访问日志,定期归档历史日志。 + * 2. 异常日志:记录用户访问 API 的系统异常,方便日常排查问题与告警。 + * + * @author 芋道源码 + */ +package com.tashow.cloud.web.apilog; diff --git a/tashow-framework/tashow-framework-web/src/main/java/com/tashow/cloud/web/banner/config/BannerAutoConfiguration.java b/tashow-framework/tashow-framework-web/src/main/java/com/tashow/cloud/web/banner/config/BannerAutoConfiguration.java new file mode 100644 index 0000000..294a32e --- /dev/null +++ b/tashow-framework/tashow-framework-web/src/main/java/com/tashow/cloud/web/banner/config/BannerAutoConfiguration.java @@ -0,0 +1,20 @@ +package com.tashow.cloud.web.banner.config; + +import com.tashow.cloud.web.banner.core.BannerApplicationRunner; +import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.context.annotation.Bean; + +/** + * Banner 的自动配置类 + * + * @author 芋道源码 + */ +@AutoConfiguration +public class BannerAutoConfiguration { + + @Bean + public BannerApplicationRunner bannerApplicationRunner() { + return new BannerApplicationRunner(); + } + +} diff --git a/tashow-framework/tashow-framework-web/src/main/java/com/tashow/cloud/web/banner/core/BannerApplicationRunner.java b/tashow-framework/tashow-framework-web/src/main/java/com/tashow/cloud/web/banner/core/BannerApplicationRunner.java new file mode 100644 index 0000000..0a14321 --- /dev/null +++ b/tashow-framework/tashow-framework-web/src/main/java/com/tashow/cloud/web/banner/core/BannerApplicationRunner.java @@ -0,0 +1,32 @@ +package com.tashow.cloud.web.banner.core; + +import cn.hutool.core.thread.ThreadUtil; +import lombok.extern.slf4j.Slf4j; +import org.springframework.boot.ApplicationArguments; +import org.springframework.boot.ApplicationRunner; + +import java.util.concurrent.TimeUnit; + +/** + * 项目启动成功后,提供文档相关的地址 + * + * @author 芋道源码 + */ +@Slf4j +public class BannerApplicationRunner implements ApplicationRunner { + + @Override + public void run(ApplicationArguments args) { + ThreadUtil.execute(() -> { + ThreadUtil.sleep(1, TimeUnit.SECONDS); // 延迟 1 秒,保证输出到结尾 + log.info("\n----------------------------------------------------------\n\t" + + "项目启动成功!\n\t" + + "接口文档: \t{} \n\t" + + "----------------------------------------------------------", + "https://cloud.iocoder.cn/api-doc/" + ); + + }); + } + +} diff --git a/tashow-framework/tashow-framework-web/src/main/java/com/tashow/cloud/web/banner/package-info.java b/tashow-framework/tashow-framework-web/src/main/java/com/tashow/cloud/web/banner/package-info.java new file mode 100644 index 0000000..85be471 --- /dev/null +++ b/tashow-framework/tashow-framework-web/src/main/java/com/tashow/cloud/web/banner/package-info.java @@ -0,0 +1,6 @@ +/** + * Banner 用于在 console 控制台,打印开发文档、接口文档等 + * + * @author 芋道源码 + */ +package com.tashow.cloud.web.banner; diff --git a/tashow-framework/tashow-framework-web/src/main/java/com/tashow/cloud/web/desensitize/core/base/annotation/DesensitizeBy.java b/tashow-framework/tashow-framework-web/src/main/java/com/tashow/cloud/web/desensitize/core/base/annotation/DesensitizeBy.java new file mode 100644 index 0000000..d7dbb68 --- /dev/null +++ b/tashow-framework/tashow-framework-web/src/main/java/com/tashow/cloud/web/desensitize/core/base/annotation/DesensitizeBy.java @@ -0,0 +1,28 @@ +package com.tashow.cloud.web.desensitize.core.base.annotation; + +import com.fasterxml.jackson.annotation.JacksonAnnotationsInside; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.tashow.cloud.web.desensitize.core.base.handler.DesensitizationHandler; +import com.tashow.cloud.web.desensitize.core.base.serializer.StringDesensitizeSerializer; + +import java.lang.annotation.*; + +/** + * 顶级脱敏注解,自定义注解需要使用此注解 + * + * @author gaibu + */ +@Documented +@Target(ElementType.ANNOTATION_TYPE) +@Retention(RetentionPolicy.RUNTIME) +@JacksonAnnotationsInside // 此注解是其他所有 jackson 注解的元注解,打上了此注解的注解表明是 jackson 注解的一部分 +@JsonSerialize(using = StringDesensitizeSerializer.class) // 指定序列化器 +public @interface DesensitizeBy { + + /** + * 脱敏处理器 + */ + @SuppressWarnings("rawtypes") + Class handler(); + +} diff --git a/tashow-framework/tashow-framework-web/src/main/java/com/tashow/cloud/web/desensitize/core/base/handler/DesensitizationHandler.java b/tashow-framework/tashow-framework-web/src/main/java/com/tashow/cloud/web/desensitize/core/base/handler/DesensitizationHandler.java new file mode 100644 index 0000000..692e134 --- /dev/null +++ b/tashow-framework/tashow-framework-web/src/main/java/com/tashow/cloud/web/desensitize/core/base/handler/DesensitizationHandler.java @@ -0,0 +1,40 @@ +package com.tashow.cloud.web.desensitize.core.base.handler; + +import cn.hutool.core.util.ReflectUtil; + +import java.lang.annotation.Annotation; + +/** + * 脱敏处理器接口 + * + * @author gaibu + */ +public interface DesensitizationHandler { + + /** + * 脱敏 + * + * @param origin 原始字符串 + * @param annotation 注解信息 + * @return 脱敏后的字符串 + */ + String desensitize(String origin, T annotation); + + /** + * 是否禁用脱敏的 Spring EL 表达式 + * + * 如果返回 true 则跳过脱敏 + * + * @param annotation 注解信息 + * @return 是否禁用脱敏的 Spring EL 表达式 + */ + default String getDisable(T annotation) { + // 约定:默认就是 enable() 属性。如果不符合,子类重写 + try { + return (String) ReflectUtil.invoke(annotation, "disable"); + } catch (Exception ex) { + return ""; + } + } + +} diff --git a/tashow-framework/tashow-framework-web/src/main/java/com/tashow/cloud/web/desensitize/core/base/serializer/StringDesensitizeSerializer.java b/tashow-framework/tashow-framework-web/src/main/java/com/tashow/cloud/web/desensitize/core/base/serializer/StringDesensitizeSerializer.java new file mode 100644 index 0000000..f932a21 --- /dev/null +++ b/tashow-framework/tashow-framework-web/src/main/java/com/tashow/cloud/web/desensitize/core/base/serializer/StringDesensitizeSerializer.java @@ -0,0 +1,92 @@ +package com.tashow.cloud.web.desensitize.core.base.serializer; + +import cn.hutool.core.annotation.AnnotationUtil; +import cn.hutool.core.lang.Singleton; +import cn.hutool.core.util.ArrayUtil; +import cn.hutool.core.util.ReflectUtil; +import cn.hutool.core.util.StrUtil; +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.BeanProperty; +import com.fasterxml.jackson.databind.JsonSerializer; +import com.fasterxml.jackson.databind.SerializerProvider; +import com.fasterxml.jackson.databind.ser.ContextualSerializer; +import com.fasterxml.jackson.databind.ser.std.StdSerializer; +import com.tashow.cloud.web.desensitize.core.base.annotation.DesensitizeBy; +import com.tashow.cloud.web.desensitize.core.base.handler.DesensitizationHandler; +import lombok.Getter; +import lombok.Setter; + +import java.io.IOException; +import java.lang.annotation.Annotation; +import java.lang.reflect.Field; + +/** + * 脱敏序列化器 + * + * 实现 JSON 返回数据时,使用 {@link DesensitizationHandler} 对声明脱敏注解的字段,进行脱敏处理。 + * + * @author gaibu + */ +@SuppressWarnings("rawtypes") +public class StringDesensitizeSerializer extends StdSerializer implements ContextualSerializer { + + @Getter + @Setter + private DesensitizationHandler desensitizationHandler; + + protected StringDesensitizeSerializer() { + super(String.class); + } + + @Override + public JsonSerializer createContextual(SerializerProvider serializerProvider, BeanProperty beanProperty) { + DesensitizeBy annotation = beanProperty.getAnnotation(DesensitizeBy.class); + if (annotation == null) { + return this; + } + // 创建一个 StringDesensitizeSerializer 对象,使用 DesensitizeBy 对应的处理器 + StringDesensitizeSerializer serializer = new StringDesensitizeSerializer(); + serializer.setDesensitizationHandler(Singleton.get(annotation.handler())); + return serializer; + } + + @Override + @SuppressWarnings("unchecked") + public void serialize(String value, JsonGenerator gen, SerializerProvider serializerProvider) throws IOException { + if (StrUtil.isBlank(value)) { + gen.writeNull(); + return; + } + // 获取序列化字段 + Field field = getField(gen); + + // 自定义处理器 + DesensitizeBy[] annotations = AnnotationUtil.getCombinationAnnotations(field, DesensitizeBy.class); + if (ArrayUtil.isEmpty(annotations)) { + gen.writeString(value); + return; + } + for (Annotation annotation : field.getAnnotations()) { + if (AnnotationUtil.hasAnnotation(annotation.annotationType(), DesensitizeBy.class)) { + value = this.desensitizationHandler.desensitize(value, annotation); + gen.writeString(value); + return; + } + } + gen.writeString(value); + } + + /** + * 获取字段 + * + * @param generator JsonGenerator + * @return 字段 + */ + private Field getField(JsonGenerator generator) { + String currentName = generator.getOutputContext().getCurrentName(); + Object currentValue = generator.getCurrentValue(); + Class currentValueClass = currentValue.getClass(); + return ReflectUtil.getField(currentValueClass, currentName); + } + +} diff --git a/tashow-framework/tashow-framework-web/src/main/java/com/tashow/cloud/web/desensitize/core/regex/annotation/EmailDesensitize.java b/tashow-framework/tashow-framework-web/src/main/java/com/tashow/cloud/web/desensitize/core/regex/annotation/EmailDesensitize.java new file mode 100644 index 0000000..e6c5a38 --- /dev/null +++ b/tashow-framework/tashow-framework-web/src/main/java/com/tashow/cloud/web/desensitize/core/regex/annotation/EmailDesensitize.java @@ -0,0 +1,40 @@ +package com.tashow.cloud.web.desensitize.core.regex.annotation; + +import com.fasterxml.jackson.annotation.JacksonAnnotationsInside; +import com.tashow.cloud.web.desensitize.core.base.annotation.DesensitizeBy; +import com.tashow.cloud.web.desensitize.core.regex.handler.EmailDesensitizationHandler; + +import java.lang.annotation.*; + +/** + * 邮箱脱敏注解 + * + * @author gaibu + */ +@Documented +@Target({ElementType.FIELD}) +@Retention(RetentionPolicy.RUNTIME) +@JacksonAnnotationsInside +@DesensitizeBy(handler = EmailDesensitizationHandler.class) +public @interface EmailDesensitize { + + /** + * 匹配的正则表达式 + */ + String regex() default "(^.)[^@]*(@.*$)"; + + /** + * 替换规则,邮箱; + * + * 比如:example@gmail.com 脱敏之后为 e****@gmail.com + */ + String replacer() default "$1****$2"; + + /** + * 是否禁用脱敏 + * + * 支持 Spring EL 表达式,如果返回 true 则跳过脱敏 + */ + String disable() default ""; + +} diff --git a/tashow-framework/tashow-framework-web/src/main/java/com/tashow/cloud/web/desensitize/core/regex/annotation/RegexDesensitize.java b/tashow-framework/tashow-framework-web/src/main/java/com/tashow/cloud/web/desensitize/core/regex/annotation/RegexDesensitize.java new file mode 100644 index 0000000..1ed76b5 --- /dev/null +++ b/tashow-framework/tashow-framework-web/src/main/java/com/tashow/cloud/web/desensitize/core/regex/annotation/RegexDesensitize.java @@ -0,0 +1,42 @@ +package com.tashow.cloud.web.desensitize.core.regex.annotation; + +import com.fasterxml.jackson.annotation.JacksonAnnotationsInside; +import com.tashow.cloud.web.desensitize.core.base.annotation.DesensitizeBy; +import com.tashow.cloud.web.desensitize.core.regex.handler.DefaultRegexDesensitizationHandler; + +import java.lang.annotation.*; + +/** + * 正则脱敏注解 + * + * @author gaibu + */ +@Documented +@Target({ElementType.FIELD, ElementType.ANNOTATION_TYPE}) +@Retention(RetentionPolicy.RUNTIME) +@JacksonAnnotationsInside +@DesensitizeBy(handler = DefaultRegexDesensitizationHandler.class) +public @interface RegexDesensitize { + + /** + * 匹配的正则表达式(默认匹配所有) + */ + String regex() default "^[\\s\\S]*$"; + + /** + * 替换规则,会将匹配到的字符串全部替换成 replacer + * + * 例如:regex=123; replacer=****** + * 原始字符串 123456789 + * 脱敏后字符串 ******456789 + */ + String replacer() default "******"; + + /** + * 是否禁用脱敏 + * + * 支持 Spring EL 表达式,如果返回 true 则跳过脱敏 + */ + String disable() default ""; + +} diff --git a/tashow-framework/tashow-framework-web/src/main/java/com/tashow/cloud/web/desensitize/core/regex/handler/AbstractRegexDesensitizationHandler.java b/tashow-framework/tashow-framework-web/src/main/java/com/tashow/cloud/web/desensitize/core/regex/handler/AbstractRegexDesensitizationHandler.java new file mode 100644 index 0000000..a828f2a --- /dev/null +++ b/tashow-framework/tashow-framework-web/src/main/java/com/tashow/cloud/web/desensitize/core/regex/handler/AbstractRegexDesensitizationHandler.java @@ -0,0 +1,46 @@ +package com.tashow.cloud.web.desensitize.core.regex.handler; + +import com.tashow.cloud.common.util.spring.SpringExpressionUtils; +import com.tashow.cloud.web.desensitize.core.base.handler.DesensitizationHandler; + +import java.lang.annotation.Annotation; + +/** + * 正则表达式脱敏处理器抽象类,已实现通用的方法 + * + * @author gaibu + */ +public abstract class AbstractRegexDesensitizationHandler + implements DesensitizationHandler { + + @Override + public String desensitize(String origin, T annotation) { + // 1. 判断是否禁用脱敏 + Object disable = SpringExpressionUtils.parseExpression(getDisable(annotation)); + if (Boolean.TRUE.equals(disable)) { + return origin; + } + + // 2. 执行脱敏 + String regex = getRegex(annotation); + String replacer = getReplacer(annotation); + return origin.replaceAll(regex, replacer); + } + + /** + * 获取注解上的 regex 参数 + * + * @param annotation 注解信息 + * @return 正则表达式 + */ + abstract String getRegex(T annotation); + + /** + * 获取注解上的 replacer 参数 + * + * @param annotation 注解信息 + * @return 待替换的字符串 + */ + abstract String getReplacer(T annotation); + +} diff --git a/tashow-framework/tashow-framework-web/src/main/java/com/tashow/cloud/web/desensitize/core/regex/handler/DefaultRegexDesensitizationHandler.java b/tashow-framework/tashow-framework-web/src/main/java/com/tashow/cloud/web/desensitize/core/regex/handler/DefaultRegexDesensitizationHandler.java new file mode 100644 index 0000000..0aada4a --- /dev/null +++ b/tashow-framework/tashow-framework-web/src/main/java/com/tashow/cloud/web/desensitize/core/regex/handler/DefaultRegexDesensitizationHandler.java @@ -0,0 +1,27 @@ +package com.tashow.cloud.web.desensitize.core.regex.handler; + +import com.tashow.cloud.web.desensitize.core.regex.annotation.RegexDesensitize; + +/** + * {@link RegexDesensitize} 的正则脱敏处理器 + * + * @author gaibu + */ +public class DefaultRegexDesensitizationHandler extends AbstractRegexDesensitizationHandler { + + @Override + String getRegex(RegexDesensitize annotation) { + return annotation.regex(); + } + + @Override + String getReplacer(RegexDesensitize annotation) { + return annotation.replacer(); + } + + @Override + public String getDisable(RegexDesensitize annotation) { + return annotation.disable(); + } + +} diff --git a/tashow-framework/tashow-framework-web/src/main/java/com/tashow/cloud/web/desensitize/core/regex/handler/EmailDesensitizationHandler.java b/tashow-framework/tashow-framework-web/src/main/java/com/tashow/cloud/web/desensitize/core/regex/handler/EmailDesensitizationHandler.java new file mode 100644 index 0000000..bb22ee3 --- /dev/null +++ b/tashow-framework/tashow-framework-web/src/main/java/com/tashow/cloud/web/desensitize/core/regex/handler/EmailDesensitizationHandler.java @@ -0,0 +1,22 @@ +package com.tashow.cloud.web.desensitize.core.regex.handler; + +import com.tashow.cloud.web.desensitize.core.regex.annotation.EmailDesensitize; + +/** + * {@link EmailDesensitize} 的脱敏处理器 + * + * @author gaibu + */ +public class EmailDesensitizationHandler extends AbstractRegexDesensitizationHandler { + + @Override + String getRegex(EmailDesensitize annotation) { + return annotation.regex(); + } + + @Override + String getReplacer(EmailDesensitize annotation) { + return annotation.replacer(); + } + +} diff --git a/tashow-framework/tashow-framework-web/src/main/java/com/tashow/cloud/web/desensitize/core/slider/annotation/BankCardDesensitize.java b/tashow-framework/tashow-framework-web/src/main/java/com/tashow/cloud/web/desensitize/core/slider/annotation/BankCardDesensitize.java new file mode 100644 index 0000000..3334c21 --- /dev/null +++ b/tashow-framework/tashow-framework-web/src/main/java/com/tashow/cloud/web/desensitize/core/slider/annotation/BankCardDesensitize.java @@ -0,0 +1,43 @@ +package com.tashow.cloud.web.desensitize.core.slider.annotation; + +import com.fasterxml.jackson.annotation.JacksonAnnotationsInside; +import com.tashow.cloud.web.desensitize.core.base.annotation.DesensitizeBy; +import com.tashow.cloud.web.desensitize.core.slider.handler.BankCardDesensitization; + +import java.lang.annotation.*; + +/** + * 银行卡号 + * + * @author gaibu + */ +@Documented +@Target({ElementType.FIELD}) +@Retention(RetentionPolicy.RUNTIME) +@JacksonAnnotationsInside +@DesensitizeBy(handler = BankCardDesensitization.class) +public @interface BankCardDesensitize { + + /** + * 前缀保留长度 + */ + int prefixKeep() default 6; + + /** + * 后缀保留长度 + */ + int suffixKeep() default 2; + + /** + * 替换规则,银行卡号; 比如:9988002866797031 脱敏之后为 998800********31 + */ + String replacer() default "*"; + + /** + * 是否禁用脱敏 + * + * 支持 Spring EL 表达式,如果返回 true 则跳过脱敏 + */ + String disable() default ""; + +} diff --git a/tashow-framework/tashow-framework-web/src/main/java/com/tashow/cloud/web/desensitize/core/slider/annotation/CarLicenseDesensitize.java b/tashow-framework/tashow-framework-web/src/main/java/com/tashow/cloud/web/desensitize/core/slider/annotation/CarLicenseDesensitize.java new file mode 100644 index 0000000..8b41545 --- /dev/null +++ b/tashow-framework/tashow-framework-web/src/main/java/com/tashow/cloud/web/desensitize/core/slider/annotation/CarLicenseDesensitize.java @@ -0,0 +1,43 @@ +package com.tashow.cloud.web.desensitize.core.slider.annotation; + +import com.fasterxml.jackson.annotation.JacksonAnnotationsInside; +import com.tashow.cloud.web.desensitize.core.base.annotation.DesensitizeBy; +import com.tashow.cloud.web.desensitize.core.slider.handler.CarLicenseDesensitization; + +import java.lang.annotation.*; + +/** + * 车牌号 + * + * @author gaibu + */ +@Documented +@Target({ElementType.FIELD}) +@Retention(RetentionPolicy.RUNTIME) +@JacksonAnnotationsInside +@DesensitizeBy(handler = CarLicenseDesensitization.class) +public @interface CarLicenseDesensitize { + + /** + * 前缀保留长度 + */ + int prefixKeep() default 3; + + /** + * 后缀保留长度 + */ + int suffixKeep() default 1; + + /** + * 替换规则,车牌号;比如:粤A66666 脱敏之后为粤A6***6 + */ + String replacer() default "*"; + + /** + * 是否禁用脱敏 + * + * 支持 Spring EL 表达式,如果返回 true 则跳过脱敏 + */ + String disable() default ""; + +} diff --git a/tashow-framework/tashow-framework-web/src/main/java/com/tashow/cloud/web/desensitize/core/slider/annotation/ChineseNameDesensitize.java b/tashow-framework/tashow-framework-web/src/main/java/com/tashow/cloud/web/desensitize/core/slider/annotation/ChineseNameDesensitize.java new file mode 100644 index 0000000..9f41f87 --- /dev/null +++ b/tashow-framework/tashow-framework-web/src/main/java/com/tashow/cloud/web/desensitize/core/slider/annotation/ChineseNameDesensitize.java @@ -0,0 +1,43 @@ +package com.tashow.cloud.web.desensitize.core.slider.annotation; + +import com.fasterxml.jackson.annotation.JacksonAnnotationsInside; +import com.tashow.cloud.web.desensitize.core.base.annotation.DesensitizeBy; +import com.tashow.cloud.web.desensitize.core.slider.handler.ChineseNameDesensitization; + +import java.lang.annotation.*; + +/** + * 中文名 + * + * @author gaibu + */ +@Documented +@Target({ElementType.FIELD}) +@Retention(RetentionPolicy.RUNTIME) +@JacksonAnnotationsInside +@DesensitizeBy(handler = ChineseNameDesensitization.class) +public @interface ChineseNameDesensitize { + + /** + * 前缀保留长度 + */ + int prefixKeep() default 1; + + /** + * 后缀保留长度 + */ + int suffixKeep() default 0; + + /** + * 替换规则,中文名;比如:刘子豪脱敏之后为刘** + */ + String replacer() default "*"; + + /** + * 是否禁用脱敏 + * + * 支持 Spring EL 表达式,如果返回 true 则跳过脱敏 + */ + String disable() default ""; + +} diff --git a/tashow-framework/tashow-framework-web/src/main/java/com/tashow/cloud/web/desensitize/core/slider/annotation/FixedPhoneDesensitize.java b/tashow-framework/tashow-framework-web/src/main/java/com/tashow/cloud/web/desensitize/core/slider/annotation/FixedPhoneDesensitize.java new file mode 100644 index 0000000..8c6af1b --- /dev/null +++ b/tashow-framework/tashow-framework-web/src/main/java/com/tashow/cloud/web/desensitize/core/slider/annotation/FixedPhoneDesensitize.java @@ -0,0 +1,43 @@ +package com.tashow.cloud.web.desensitize.core.slider.annotation; + +import com.fasterxml.jackson.annotation.JacksonAnnotationsInside; +import com.tashow.cloud.web.desensitize.core.base.annotation.DesensitizeBy; +import com.tashow.cloud.web.desensitize.core.slider.handler.FixedPhoneDesensitization; + +import java.lang.annotation.*; + +/** + * 固定电话 + * + * @author gaibu + */ +@Documented +@Target({ElementType.FIELD}) +@Retention(RetentionPolicy.RUNTIME) +@JacksonAnnotationsInside +@DesensitizeBy(handler = FixedPhoneDesensitization.class) +public @interface FixedPhoneDesensitize { + + /** + * 前缀保留长度 + */ + int prefixKeep() default 4; + + /** + * 后缀保留长度 + */ + int suffixKeep() default 2; + + /** + * 替换规则,固定电话;比如:01086551122 脱敏之后为 0108*****22 + */ + String replacer() default "*"; + + /** + * 是否禁用脱敏 + * + * 支持 Spring EL 表达式,如果返回 true 则跳过脱敏 + */ + String disable() default ""; + +} diff --git a/tashow-framework/tashow-framework-web/src/main/java/com/tashow/cloud/web/desensitize/core/slider/annotation/IdCardDesensitize.java b/tashow-framework/tashow-framework-web/src/main/java/com/tashow/cloud/web/desensitize/core/slider/annotation/IdCardDesensitize.java new file mode 100644 index 0000000..1f3b774 --- /dev/null +++ b/tashow-framework/tashow-framework-web/src/main/java/com/tashow/cloud/web/desensitize/core/slider/annotation/IdCardDesensitize.java @@ -0,0 +1,43 @@ +package com.tashow.cloud.web.desensitize.core.slider.annotation; + +import com.fasterxml.jackson.annotation.JacksonAnnotationsInside; +import com.tashow.cloud.web.desensitize.core.base.annotation.DesensitizeBy; +import com.tashow.cloud.web.desensitize.core.slider.handler.IdCardDesensitization; + +import java.lang.annotation.*; + +/** + * 身份证 + * + * @author gaibu + */ +@Documented +@Target({ElementType.FIELD}) +@Retention(RetentionPolicy.RUNTIME) +@JacksonAnnotationsInside +@DesensitizeBy(handler = IdCardDesensitization.class) +public @interface IdCardDesensitize { + + /** + * 前缀保留长度 + */ + int prefixKeep() default 6; + + /** + * 后缀保留长度 + */ + int suffixKeep() default 2; + + /** + * 替换规则,身份证号码;比如:530321199204074611 脱敏之后为 530321**********11 + */ + String replacer() default "*"; + + /** + * 是否禁用脱敏 + * + * 支持 Spring EL 表达式,如果返回 true 则跳过脱敏 + */ + String disable() default ""; + +} diff --git a/tashow-framework/tashow-framework-web/src/main/java/com/tashow/cloud/web/desensitize/core/slider/annotation/MobileDesensitize.java b/tashow-framework/tashow-framework-web/src/main/java/com/tashow/cloud/web/desensitize/core/slider/annotation/MobileDesensitize.java new file mode 100644 index 0000000..4b3e94d --- /dev/null +++ b/tashow-framework/tashow-framework-web/src/main/java/com/tashow/cloud/web/desensitize/core/slider/annotation/MobileDesensitize.java @@ -0,0 +1,43 @@ +package com.tashow.cloud.web.desensitize.core.slider.annotation; + +import com.fasterxml.jackson.annotation.JacksonAnnotationsInside; +import com.tashow.cloud.web.desensitize.core.base.annotation.DesensitizeBy; +import com.tashow.cloud.web.desensitize.core.slider.handler.MobileDesensitization; + +import java.lang.annotation.*; + +/** + * 手机号 + * + * @author gaibu + */ +@Documented +@Target({ElementType.FIELD}) +@Retention(RetentionPolicy.RUNTIME) +@JacksonAnnotationsInside +@DesensitizeBy(handler = MobileDesensitization.class) +public @interface MobileDesensitize { + + /** + * 前缀保留长度 + */ + int prefixKeep() default 3; + + /** + * 后缀保留长度 + */ + int suffixKeep() default 4; + + /** + * 替换规则,手机号;比如:13248765917 脱敏之后为 132****5917 + */ + String replacer() default "*"; + + /** + * 是否禁用脱敏 + * + * 支持 Spring EL 表达式,如果返回 true 则跳过脱敏 + */ + String disable() default ""; + +} diff --git a/tashow-framework/tashow-framework-web/src/main/java/com/tashow/cloud/web/desensitize/core/slider/annotation/PasswordDesensitize.java b/tashow-framework/tashow-framework-web/src/main/java/com/tashow/cloud/web/desensitize/core/slider/annotation/PasswordDesensitize.java new file mode 100644 index 0000000..1ce1398 --- /dev/null +++ b/tashow-framework/tashow-framework-web/src/main/java/com/tashow/cloud/web/desensitize/core/slider/annotation/PasswordDesensitize.java @@ -0,0 +1,45 @@ +package com.tashow.cloud.web.desensitize.core.slider.annotation; + +import com.fasterxml.jackson.annotation.JacksonAnnotationsInside; +import com.tashow.cloud.web.desensitize.core.base.annotation.DesensitizeBy; +import com.tashow.cloud.web.desensitize.core.slider.handler.PasswordDesensitization; + +import java.lang.annotation.*; + +/** + * 密码 + * + * @author gaibu + */ +@Documented +@Target({ElementType.FIELD}) +@Retention(RetentionPolicy.RUNTIME) +@JacksonAnnotationsInside +@DesensitizeBy(handler = PasswordDesensitization.class) +public @interface PasswordDesensitize { + + /** + * 前缀保留长度 + */ + int prefixKeep() default 0; + + /** + * 后缀保留长度 + */ + int suffixKeep() default 0; + + /** + * 替换规则,密码; + * + * 比如:123456 脱敏之后为 ****** + */ + String replacer() default "*"; + + /** + * 是否禁用脱敏 + * + * 支持 Spring EL 表达式,如果返回 true 则跳过脱敏 + */ + String disable() default ""; + +} diff --git a/tashow-framework/tashow-framework-web/src/main/java/com/tashow/cloud/web/desensitize/core/slider/annotation/SliderDesensitize.java b/tashow-framework/tashow-framework-web/src/main/java/com/tashow/cloud/web/desensitize/core/slider/annotation/SliderDesensitize.java new file mode 100644 index 0000000..c0b5298 --- /dev/null +++ b/tashow-framework/tashow-framework-web/src/main/java/com/tashow/cloud/web/desensitize/core/slider/annotation/SliderDesensitize.java @@ -0,0 +1,47 @@ +package com.tashow.cloud.web.desensitize.core.slider.annotation; + +import com.fasterxml.jackson.annotation.JacksonAnnotationsInside; +import com.tashow.cloud.web.desensitize.core.base.annotation.DesensitizeBy; +import com.tashow.cloud.web.desensitize.core.slider.handler.DefaultDesensitizationHandler; + +import java.lang.annotation.*; + +/** + * 滑动脱敏注解 + * + * @author gaibu + */ +@Documented +@Target({ElementType.FIELD, ElementType.ANNOTATION_TYPE}) +@Retention(RetentionPolicy.RUNTIME) +@JacksonAnnotationsInside +@DesensitizeBy(handler = DefaultDesensitizationHandler.class) +public @interface SliderDesensitize { + + /** + * 后缀保留长度 + */ + int suffixKeep() default 0; + + /** + * 替换规则,会将前缀后缀保留后,全部替换成 replacer + * + * 例如:prefixKeep = 1; suffixKeep = 2; replacer = "*"; + * 原始字符串 123456 + * 脱敏后 1***56 + */ + String replacer() default "*"; + + /** + * 前缀保留长度 + */ + int prefixKeep() default 0; + + /** + * 是否禁用脱敏 + * + * 支持 Spring EL 表达式,如果返回 true 则跳过脱敏 + */ + String disable() default ""; + +} diff --git a/tashow-framework/tashow-framework-web/src/main/java/com/tashow/cloud/web/desensitize/core/slider/handler/AbstractSliderDesensitizationHandler.java b/tashow-framework/tashow-framework-web/src/main/java/com/tashow/cloud/web/desensitize/core/slider/handler/AbstractSliderDesensitizationHandler.java new file mode 100644 index 0000000..7a724a8 --- /dev/null +++ b/tashow-framework/tashow-framework-web/src/main/java/com/tashow/cloud/web/desensitize/core/slider/handler/AbstractSliderDesensitizationHandler.java @@ -0,0 +1,77 @@ +package com.tashow.cloud.web.desensitize.core.slider.handler; + +import com.tashow.cloud.common.util.spring.SpringExpressionUtils; +import com.tashow.cloud.web.desensitize.core.base.handler.DesensitizationHandler; + +import java.lang.annotation.Annotation; + +/** + * 滑动脱敏处理器抽象类,已实现通用的方法 + * + * @author gaibu + */ +public abstract class AbstractSliderDesensitizationHandler + implements DesensitizationHandler { + + @Override + public String desensitize(String origin, T annotation) { + // 1. 判断是否禁用脱敏 + Object disable = SpringExpressionUtils.parseExpression(getDisable(annotation)); + if (Boolean.TRUE.equals(disable)) { + return origin; + } + + // 2. 执行脱敏 + int prefixKeep = getPrefixKeep(annotation); + int suffixKeep = getSuffixKeep(annotation); + String replacer = getReplacer(annotation); + int length = origin.length(); + int interval = length - prefixKeep - suffixKeep; + + // 情况一:原始字符串长度小于等于前后缀保留字符串长度,则原始字符串全部替换 + if (interval <= 0) { + return buildReplacerByLength(replacer, length); + } + + // 情况二:原始字符串长度大于前后缀保留字符串长度,则替换中间字符串 + return origin.substring(0, prefixKeep) + + buildReplacerByLength(replacer, interval) + + origin.substring(prefixKeep + interval); + } + + /** + * 根据长度循环构建替换符 + * + * @param replacer 替换符 + * @param length 长度 + * @return 构建后的替换符 + */ + private String buildReplacerByLength(String replacer, int length) { + return replacer.repeat(length); + } + + /** + * 前缀保留长度 + * + * @param annotation 注解信息 + * @return 前缀保留长度 + */ + abstract Integer getPrefixKeep(T annotation); + + /** + * 后缀保留长度 + * + * @param annotation 注解信息 + * @return 后缀保留长度 + */ + abstract Integer getSuffixKeep(T annotation); + + /** + * 替换符 + * + * @param annotation 注解信息 + * @return 替换符 + */ + abstract String getReplacer(T annotation); + +} diff --git a/tashow-framework/tashow-framework-web/src/main/java/com/tashow/cloud/web/desensitize/core/slider/handler/BankCardDesensitization.java b/tashow-framework/tashow-framework-web/src/main/java/com/tashow/cloud/web/desensitize/core/slider/handler/BankCardDesensitization.java new file mode 100644 index 0000000..3a71c91 --- /dev/null +++ b/tashow-framework/tashow-framework-web/src/main/java/com/tashow/cloud/web/desensitize/core/slider/handler/BankCardDesensitization.java @@ -0,0 +1,32 @@ +package com.tashow.cloud.web.desensitize.core.slider.handler; + +import com.tashow.cloud.web.desensitize.core.slider.annotation.BankCardDesensitize; + +/** + * {@link BankCardDesensitize} 的脱敏处理器 + * + * @author gaibu + */ +public class BankCardDesensitization extends AbstractSliderDesensitizationHandler { + + @Override + Integer getPrefixKeep(BankCardDesensitize annotation) { + return annotation.prefixKeep(); + } + + @Override + Integer getSuffixKeep(BankCardDesensitize annotation) { + return annotation.suffixKeep(); + } + + @Override + String getReplacer(BankCardDesensitize annotation) { + return annotation.replacer(); + } + + @Override + public String getDisable(BankCardDesensitize annotation) { + return ""; + } + +} diff --git a/tashow-framework/tashow-framework-web/src/main/java/com/tashow/cloud/web/desensitize/core/slider/handler/CarLicenseDesensitization.java b/tashow-framework/tashow-framework-web/src/main/java/com/tashow/cloud/web/desensitize/core/slider/handler/CarLicenseDesensitization.java new file mode 100644 index 0000000..0da8c24 --- /dev/null +++ b/tashow-framework/tashow-framework-web/src/main/java/com/tashow/cloud/web/desensitize/core/slider/handler/CarLicenseDesensitization.java @@ -0,0 +1,32 @@ +package com.tashow.cloud.web.desensitize.core.slider.handler; + +import com.tashow.cloud.web.desensitize.core.slider.annotation.CarLicenseDesensitize; + +/** + * {@link CarLicenseDesensitize} 的脱敏处理器 + * + * @author gaibu + */ +public class CarLicenseDesensitization extends AbstractSliderDesensitizationHandler { + + @Override + Integer getPrefixKeep(CarLicenseDesensitize annotation) { + return annotation.prefixKeep(); + } + + @Override + Integer getSuffixKeep(CarLicenseDesensitize annotation) { + return annotation.suffixKeep(); + } + + @Override + String getReplacer(CarLicenseDesensitize annotation) { + return annotation.replacer(); + } + + @Override + public String getDisable(CarLicenseDesensitize annotation) { + return annotation.disable(); + } + +} diff --git a/tashow-framework/tashow-framework-web/src/main/java/com/tashow/cloud/web/desensitize/core/slider/handler/ChineseNameDesensitization.java b/tashow-framework/tashow-framework-web/src/main/java/com/tashow/cloud/web/desensitize/core/slider/handler/ChineseNameDesensitization.java new file mode 100644 index 0000000..b403323 --- /dev/null +++ b/tashow-framework/tashow-framework-web/src/main/java/com/tashow/cloud/web/desensitize/core/slider/handler/ChineseNameDesensitization.java @@ -0,0 +1,27 @@ +package com.tashow.cloud.web.desensitize.core.slider.handler; + +import com.tashow.cloud.web.desensitize.core.slider.annotation.ChineseNameDesensitize; + +/** + * {@link ChineseNameDesensitize} 的脱敏处理器 + * + * @author gaibu + */ +public class ChineseNameDesensitization extends AbstractSliderDesensitizationHandler { + + @Override + Integer getPrefixKeep(ChineseNameDesensitize annotation) { + return annotation.prefixKeep(); + } + + @Override + Integer getSuffixKeep(ChineseNameDesensitize annotation) { + return annotation.suffixKeep(); + } + + @Override + String getReplacer(ChineseNameDesensitize annotation) { + return annotation.replacer(); + } + +} diff --git a/tashow-framework/tashow-framework-web/src/main/java/com/tashow/cloud/web/desensitize/core/slider/handler/DefaultDesensitizationHandler.java b/tashow-framework/tashow-framework-web/src/main/java/com/tashow/cloud/web/desensitize/core/slider/handler/DefaultDesensitizationHandler.java new file mode 100644 index 0000000..40166ca --- /dev/null +++ b/tashow-framework/tashow-framework-web/src/main/java/com/tashow/cloud/web/desensitize/core/slider/handler/DefaultDesensitizationHandler.java @@ -0,0 +1,27 @@ +package com.tashow.cloud.web.desensitize.core.slider.handler; + +import com.tashow.cloud.web.desensitize.core.slider.annotation.SliderDesensitize; + +/** + * {@link SliderDesensitize} 的脱敏处理器 + * + * @author gaibu + */ +public class DefaultDesensitizationHandler extends AbstractSliderDesensitizationHandler { + + @Override + Integer getPrefixKeep(SliderDesensitize annotation) { + return annotation.prefixKeep(); + } + + @Override + Integer getSuffixKeep(SliderDesensitize annotation) { + return annotation.suffixKeep(); + } + + @Override + String getReplacer(SliderDesensitize annotation) { + return annotation.replacer(); + } + +} diff --git a/tashow-framework/tashow-framework-web/src/main/java/com/tashow/cloud/web/desensitize/core/slider/handler/FixedPhoneDesensitization.java b/tashow-framework/tashow-framework-web/src/main/java/com/tashow/cloud/web/desensitize/core/slider/handler/FixedPhoneDesensitization.java new file mode 100644 index 0000000..26cb9d2 --- /dev/null +++ b/tashow-framework/tashow-framework-web/src/main/java/com/tashow/cloud/web/desensitize/core/slider/handler/FixedPhoneDesensitization.java @@ -0,0 +1,27 @@ +package com.tashow.cloud.web.desensitize.core.slider.handler; + +import com.tashow.cloud.web.desensitize.core.slider.annotation.FixedPhoneDesensitize; + +/** + * {@link FixedPhoneDesensitize} 的脱敏处理器 + * + * @author gaibu + */ +public class FixedPhoneDesensitization extends AbstractSliderDesensitizationHandler { + + @Override + Integer getPrefixKeep(FixedPhoneDesensitize annotation) { + return annotation.prefixKeep(); + } + + @Override + Integer getSuffixKeep(FixedPhoneDesensitize annotation) { + return annotation.suffixKeep(); + } + + @Override + String getReplacer(FixedPhoneDesensitize annotation) { + return annotation.replacer(); + } + +} diff --git a/tashow-framework/tashow-framework-web/src/main/java/com/tashow/cloud/web/desensitize/core/slider/handler/IdCardDesensitization.java b/tashow-framework/tashow-framework-web/src/main/java/com/tashow/cloud/web/desensitize/core/slider/handler/IdCardDesensitization.java new file mode 100644 index 0000000..96f5083 --- /dev/null +++ b/tashow-framework/tashow-framework-web/src/main/java/com/tashow/cloud/web/desensitize/core/slider/handler/IdCardDesensitization.java @@ -0,0 +1,26 @@ +package com.tashow.cloud.web.desensitize.core.slider.handler; + +import com.tashow.cloud.web.desensitize.core.slider.annotation.IdCardDesensitize; + +/** + * {@link IdCardDesensitize} 的脱敏处理器 + * + * @author gaibu + */ +public class IdCardDesensitization extends AbstractSliderDesensitizationHandler { + @Override + Integer getPrefixKeep(IdCardDesensitize annotation) { + return annotation.prefixKeep(); + } + + @Override + Integer getSuffixKeep(IdCardDesensitize annotation) { + return annotation.suffixKeep(); + } + + @Override + String getReplacer(IdCardDesensitize annotation) { + return annotation.replacer(); + } + +} diff --git a/tashow-framework/tashow-framework-web/src/main/java/com/tashow/cloud/web/desensitize/core/slider/handler/MobileDesensitization.java b/tashow-framework/tashow-framework-web/src/main/java/com/tashow/cloud/web/desensitize/core/slider/handler/MobileDesensitization.java new file mode 100644 index 0000000..c9a95f5 --- /dev/null +++ b/tashow-framework/tashow-framework-web/src/main/java/com/tashow/cloud/web/desensitize/core/slider/handler/MobileDesensitization.java @@ -0,0 +1,27 @@ +package com.tashow.cloud.web.desensitize.core.slider.handler; + +import com.tashow.cloud.web.desensitize.core.slider.annotation.MobileDesensitize; + +/** + * {@link MobileDesensitize} 的脱敏处理器 + * + * @author gaibu + */ +public class MobileDesensitization extends AbstractSliderDesensitizationHandler { + + @Override + Integer getPrefixKeep(MobileDesensitize annotation) { + return annotation.prefixKeep(); + } + + @Override + Integer getSuffixKeep(MobileDesensitize annotation) { + return annotation.suffixKeep(); + } + + @Override + String getReplacer(MobileDesensitize annotation) { + return annotation.replacer(); + } + +} diff --git a/tashow-framework/tashow-framework-web/src/main/java/com/tashow/cloud/web/desensitize/core/slider/handler/PasswordDesensitization.java b/tashow-framework/tashow-framework-web/src/main/java/com/tashow/cloud/web/desensitize/core/slider/handler/PasswordDesensitization.java new file mode 100644 index 0000000..bcfbbcb --- /dev/null +++ b/tashow-framework/tashow-framework-web/src/main/java/com/tashow/cloud/web/desensitize/core/slider/handler/PasswordDesensitization.java @@ -0,0 +1,26 @@ +package com.tashow.cloud.web.desensitize.core.slider.handler; + +import com.tashow.cloud.web.desensitize.core.slider.annotation.PasswordDesensitize; + +/** + * {@link PasswordDesensitize} 的码脱敏处理器 + * + * @author gaibu + */ +public class PasswordDesensitization extends AbstractSliderDesensitizationHandler { + @Override + Integer getPrefixKeep(PasswordDesensitize annotation) { + return annotation.prefixKeep(); + } + + @Override + Integer getSuffixKeep(PasswordDesensitize annotation) { + return annotation.suffixKeep(); + } + + @Override + String getReplacer(PasswordDesensitize annotation) { + return annotation.replacer(); + } + +} diff --git a/tashow-framework/tashow-framework-web/src/main/java/com/tashow/cloud/web/desensitize/package-info.java b/tashow-framework/tashow-framework-web/src/main/java/com/tashow/cloud/web/desensitize/package-info.java new file mode 100644 index 0000000..2c81459 --- /dev/null +++ b/tashow-framework/tashow-framework-web/src/main/java/com/tashow/cloud/web/desensitize/package-info.java @@ -0,0 +1,4 @@ +/** + * 脱敏组件:支持 JSON 返回数据时,将邮箱、手机等字段进行脱敏 + */ +package com.tashow.cloud.web.desensitize; diff --git a/tashow-framework/tashow-framework-web/src/main/java/com/tashow/cloud/web/jackson/config/JacksonAutoConfiguration.java b/tashow-framework/tashow-framework-web/src/main/java/com/tashow/cloud/web/jackson/config/JacksonAutoConfiguration.java new file mode 100644 index 0000000..070715f --- /dev/null +++ b/tashow-framework/tashow-framework-web/src/main/java/com/tashow/cloud/web/jackson/config/JacksonAutoConfiguration.java @@ -0,0 +1,52 @@ +package com.tashow.cloud.web.jackson.config; + +import cn.hutool.core.collection.CollUtil; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.module.SimpleModule; +import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer; +import com.fasterxml.jackson.datatype.jsr310.deser.LocalTimeDeserializer; +import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer; +import com.fasterxml.jackson.datatype.jsr310.ser.LocalTimeSerializer; +import com.tashow.cloud.common.util.json.JsonUtils; +import com.tashow.cloud.common.util.json.databind.NumberSerializer; +import com.tashow.cloud.common.util.json.databind.TimestampLocalDateTimeDeserializer; +import com.tashow.cloud.common.util.json.databind.TimestampLocalDateTimeSerializer; +import lombok.extern.slf4j.Slf4j; +import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.context.annotation.Bean; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.util.List; + +@AutoConfiguration +@Slf4j +public class JacksonAutoConfiguration { + + @Bean + @SuppressWarnings("InstantiationOfUtilityClass") + public JsonUtils jsonUtils(List objectMappers) { + // 1.1 创建 SimpleModule 对象 + SimpleModule simpleModule = new SimpleModule(); + simpleModule + // 新增 Long 类型序列化规则,数值超过 2^53-1,在 JS 会出现精度丢失问题,因此 Long 自动序列化为字符串类型 + .addSerializer(Long.class, NumberSerializer.INSTANCE) + .addSerializer(Long.TYPE, NumberSerializer.INSTANCE) + .addSerializer(LocalDate.class, LocalDateSerializer.INSTANCE) + .addDeserializer(LocalDate.class, LocalDateDeserializer.INSTANCE) + .addSerializer(LocalTime.class, LocalTimeSerializer.INSTANCE) + .addDeserializer(LocalTime.class, LocalTimeDeserializer.INSTANCE) + // 新增 LocalDateTime 序列化、反序列化规则,使用 Long 时间戳 + .addSerializer(LocalDateTime.class, TimestampLocalDateTimeSerializer.INSTANCE) + .addDeserializer(LocalDateTime.class, TimestampLocalDateTimeDeserializer.INSTANCE); + // 1.2 注册到 objectMapper + objectMappers.forEach(objectMapper -> objectMapper.registerModule(simpleModule)); + + // 2. 设置 objectMapper 到 JsonUtils + JsonUtils.init(CollUtil.getFirst(objectMappers)); + log.info("[init][初始化 JsonUtils 成功]"); + return new JsonUtils(); + } + +} diff --git a/tashow-framework/tashow-framework-web/src/main/java/com/tashow/cloud/web/jackson/core/package-info.java b/tashow-framework/tashow-framework-web/src/main/java/com/tashow/cloud/web/jackson/core/package-info.java new file mode 100644 index 0000000..e788384 --- /dev/null +++ b/tashow-framework/tashow-framework-web/src/main/java/com/tashow/cloud/web/jackson/core/package-info.java @@ -0,0 +1 @@ +package com.tashow.cloud.web.jackson.core; diff --git a/tashow-framework/tashow-framework-web/src/main/java/com/tashow/cloud/web/web/config/WebAutoConfiguration.java b/tashow-framework/tashow-framework-web/src/main/java/com/tashow/cloud/web/web/config/WebAutoConfiguration.java new file mode 100644 index 0000000..ad26e57 --- /dev/null +++ b/tashow-framework/tashow-framework-web/src/main/java/com/tashow/cloud/web/web/config/WebAutoConfiguration.java @@ -0,0 +1,131 @@ +package com.tashow.cloud.web.web.config; + +import com.tashow.cloud.common.enums.WebFilterOrderEnum; +import com.tashow.cloud.infraapi.api.logger.ApiErrorLogApi; +import com.tashow.cloud.web.web.core.filter.CacheRequestBodyFilter; +import com.tashow.cloud.web.web.core.filter.DemoFilter; +import com.tashow.cloud.web.web.core.handler.GlobalExceptionHandler; +import com.tashow.cloud.web.web.core.handler.GlobalResponseBodyHandler; +import com.tashow.cloud.web.web.core.util.WebFrameworkUtils; +import jakarta.annotation.Resource; +import jakarta.servlet.Filter; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.autoconfigure.web.client.RestTemplateAutoConfiguration; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.boot.web.client.RestTemplateBuilder; +import org.springframework.boot.web.servlet.FilterRegistrationBean; +import org.springframework.context.annotation.Bean; +import org.springframework.util.AntPathMatcher; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.client.RestTemplate; +import org.springframework.web.cors.CorsConfiguration; +import org.springframework.web.cors.UrlBasedCorsConfigurationSource; +import org.springframework.web.filter.CorsFilter; +import org.springframework.web.servlet.config.annotation.PathMatchConfigurer; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +@AutoConfiguration +@EnableConfigurationProperties(WebProperties.class) +public class WebAutoConfiguration implements WebMvcConfigurer { + + @Resource + private WebProperties webProperties; + /** + * 应用名 + */ + @Value("${spring.application.name}") + private String applicationName; + + @Override + public void configurePathMatch(PathMatchConfigurer configurer) { + configurePathMatch(configurer, webProperties.getAdminApi()); + configurePathMatch(configurer, webProperties.getAppApi()); + } + + /** + * 设置 API 前缀,仅仅匹配 controller 包下的 + * + * @param configurer 配置 + * @param api API 配置 + */ + private void configurePathMatch(PathMatchConfigurer configurer, WebProperties.Api api) { + AntPathMatcher antPathMatcher = new AntPathMatcher("."); + configurer.addPathPrefix(api.getPrefix(), clazz -> clazz.isAnnotationPresent(RestController.class) + && antPathMatcher.match(api.getController(), clazz.getPackage().getName())); // 仅仅匹配 controller 包 + } + + @Bean + @SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection") + public GlobalExceptionHandler globalExceptionHandler(ApiErrorLogApi apiErrorLogApi) { + return new GlobalExceptionHandler(applicationName, apiErrorLogApi); + } + + @Bean + public GlobalResponseBodyHandler globalResponseBodyHandler() { + return new GlobalResponseBodyHandler(); + } + + @Bean + @SuppressWarnings("InstantiationOfUtilityClass") + public WebFrameworkUtils webFrameworkUtils(WebProperties webProperties) { + // 由于 WebFrameworkUtils 需要使用到 webProperties 属性,所以注册为一个 Bean + return new WebFrameworkUtils(webProperties); + } + + // ========== Filter 相关 ========== + + /** + * 创建 CorsFilter Bean,解决跨域问题 + */ + @Bean + public FilterRegistrationBean corsFilterBean() { + // 创建 CorsConfiguration 对象 + CorsConfiguration config = new CorsConfiguration(); + config.setAllowCredentials(true); + config.addAllowedOriginPattern("*"); // 设置访问源地址 + config.addAllowedHeader("*"); // 设置访问源请求头 + config.addAllowedMethod("*"); // 设置访问源请求方法 + // 创建 UrlBasedCorsConfigurationSource 对象 + UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); + source.registerCorsConfiguration("/**", config); // 对接口配置跨域设置 + return createFilterBean(new CorsFilter(source), WebFilterOrderEnum.CORS_FILTER); + } + + /** + * 创建 RequestBodyCacheFilter Bean,可重复读取请求内容 + */ + @Bean + public FilterRegistrationBean requestBodyCacheFilter() { + return createFilterBean(new CacheRequestBodyFilter(), WebFilterOrderEnum.REQUEST_BODY_CACHE_FILTER); + } + + /** + * 创建 DemoFilter Bean,演示模式 + */ + @Bean + @ConditionalOnProperty(value = "yudao.demo", havingValue = "true") + public FilterRegistrationBean demoFilter() { + return createFilterBean(new DemoFilter(), WebFilterOrderEnum.DEMO_FILTER); + } + + public static FilterRegistrationBean createFilterBean(T filter, Integer order) { + FilterRegistrationBean bean = new FilterRegistrationBean<>(filter); + bean.setOrder(order); + return bean; + } + + /** + * 创建 RestTemplate 实例 + * + * @param restTemplateBuilder {@link RestTemplateAutoConfiguration#restTemplateBuilder} + */ + @Bean + @ConditionalOnMissingBean + public RestTemplate restTemplate(RestTemplateBuilder restTemplateBuilder) { + return restTemplateBuilder.build(); + } + +} diff --git a/tashow-framework/tashow-framework-web/src/main/java/com/tashow/cloud/web/web/config/WebProperties.java b/tashow-framework/tashow-framework-web/src/main/java/com/tashow/cloud/web/web/config/WebProperties.java new file mode 100644 index 0000000..00455f2 --- /dev/null +++ b/tashow-framework/tashow-framework-web/src/main/java/com/tashow/cloud/web/web/config/WebProperties.java @@ -0,0 +1,51 @@ +package com.tashow.cloud.web.web.config; + +import jakarta.validation.Valid; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.servlet.config.annotation.PathMatchConfigurer; + +@ConfigurationProperties(prefix = "tashow.web") +@Validated +@Data +public class WebProperties { + + @NotNull(message = "APP API 不能为空") + private Api appApi = new Api("/app-api", "**.controller.app.**"); + @NotNull(message = "Admin API 不能为空") + private Api adminApi = new Api("/admin-api", "**.controller.admin.**"); + + @Data + @AllArgsConstructor + @NoArgsConstructor + @Valid + public static class Api { + + /** + * API 前缀,实现所有 Controller 提供的 RESTFul API 的统一前缀 + * + * + * 意义:通过该前缀,避免 Swagger、Actuator 意外通过 Nginx 暴露出来给外部,带来安全性问题 + * 这样,Nginx 只需要配置转发到 /api/* 的所有接口即可。 + * + * @see WebAutoConfiguration#configurePathMatch(PathMatchConfigurer) + */ + @NotEmpty(message = "API 前缀不能为空") + private String prefix; + + /** + * Controller 所在包的 Ant 路径规则 + * + * 主要目的是,给该 Controller 设置指定的 {@link #prefix} + */ + @NotEmpty(message = "Controller 所在包不能为空") + private String controller; + + } + +} diff --git a/tashow-framework/tashow-framework-web/src/main/java/com/tashow/cloud/web/web/core/filter/ApiRequestFilter.java b/tashow-framework/tashow-framework-web/src/main/java/com/tashow/cloud/web/web/core/filter/ApiRequestFilter.java new file mode 100644 index 0000000..0123feb --- /dev/null +++ b/tashow-framework/tashow-framework-web/src/main/java/com/tashow/cloud/web/web/core/filter/ApiRequestFilter.java @@ -0,0 +1,26 @@ +package com.tashow.cloud.web.web.core.filter; + +import cn.hutool.core.util.StrUtil; +import com.tashow.cloud.web.web.config.WebProperties; +import jakarta.servlet.http.HttpServletRequest; +import lombok.RequiredArgsConstructor; +import org.springframework.web.filter.OncePerRequestFilter; + +/** + * 过滤 /admin-api、/app-api 等 API 请求的过滤器 + * + * @author 芋道源码 + */ +@RequiredArgsConstructor +public abstract class ApiRequestFilter extends OncePerRequestFilter { + + protected final WebProperties webProperties; + + @Override + protected boolean shouldNotFilter(HttpServletRequest request) { + // 只过滤 API 请求的地址 + String apiUri = request.getRequestURI().substring(request.getContextPath().length()); + return !StrUtil.startWithAny(apiUri, webProperties.getAdminApi().getPrefix(), webProperties.getAppApi().getPrefix()); + } + +} diff --git a/tashow-framework/tashow-framework-web/src/main/java/com/tashow/cloud/web/web/core/filter/CacheRequestBodyFilter.java b/tashow-framework/tashow-framework-web/src/main/java/com/tashow/cloud/web/web/core/filter/CacheRequestBodyFilter.java new file mode 100644 index 0000000..11245da --- /dev/null +++ b/tashow-framework/tashow-framework-web/src/main/java/com/tashow/cloud/web/web/core/filter/CacheRequestBodyFilter.java @@ -0,0 +1,31 @@ +package com.tashow.cloud.web.web.core.filter; + +import com.tashow.cloud.common.util.servlet.ServletUtils; +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.springframework.web.filter.OncePerRequestFilter; + +import java.io.IOException; + +/** + * Request Body 缓存 Filter,实现它的可重复读取 + * + * @author 芋道源码 + */ +public class CacheRequestBodyFilter extends OncePerRequestFilter { + + @Override + protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) + throws IOException, ServletException { + filterChain.doFilter(new CacheRequestBodyWrapper(request), response); + } + + @Override + protected boolean shouldNotFilter(HttpServletRequest request) { + // 只处理 json 请求内容 + return !ServletUtils.isJsonRequest(request); + } + +} diff --git a/tashow-framework/tashow-framework-web/src/main/java/com/tashow/cloud/web/web/core/filter/CacheRequestBodyWrapper.java b/tashow-framework/tashow-framework-web/src/main/java/com/tashow/cloud/web/web/core/filter/CacheRequestBodyWrapper.java new file mode 100644 index 0000000..af6fb2c --- /dev/null +++ b/tashow-framework/tashow-framework-web/src/main/java/com/tashow/cloud/web/web/core/filter/CacheRequestBodyWrapper.java @@ -0,0 +1,68 @@ +package com.tashow.cloud.web.web.core.filter; + +import com.tashow.cloud.common.util.servlet.ServletUtils; +import jakarta.servlet.ReadListener; +import jakarta.servlet.ServletInputStream; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletRequestWrapper; + +import java.io.BufferedReader; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStreamReader; + +/** + * Request Body 缓存 Wrapper + * + * @author 芋道源码 + */ +public class CacheRequestBodyWrapper extends HttpServletRequestWrapper { + + /** + * 缓存的内容 + */ + private final byte[] body; + + public CacheRequestBodyWrapper(HttpServletRequest request) { + super(request); + body = ServletUtils.getBodyBytes(request); + } + + @Override + public BufferedReader getReader() throws IOException { + return new BufferedReader(new InputStreamReader(this.getInputStream())); + } + + @Override + public ServletInputStream getInputStream() throws IOException { + final ByteArrayInputStream inputStream = new ByteArrayInputStream(body); + // 返回 ServletInputStream + return new ServletInputStream() { + + @Override + public int read() { + return inputStream.read(); + } + + @Override + public boolean isFinished() { + return false; + } + + @Override + public boolean isReady() { + return false; + } + + @Override + public void setReadListener(ReadListener readListener) {} + + @Override + public int available() { + return body.length; + } + + }; + } + +} diff --git a/tashow-framework/tashow-framework-web/src/main/java/com/tashow/cloud/web/web/core/filter/DemoFilter.java b/tashow-framework/tashow-framework-web/src/main/java/com/tashow/cloud/web/web/core/filter/DemoFilter.java new file mode 100644 index 0000000..4ec53f0 --- /dev/null +++ b/tashow-framework/tashow-framework-web/src/main/java/com/tashow/cloud/web/web/core/filter/DemoFilter.java @@ -0,0 +1,35 @@ +package com.tashow.cloud.web.web.core.filter; + +import cn.hutool.core.util.StrUtil; +import com.tashow.cloud.common.pojo.CommonResult; +import com.tashow.cloud.common.util.servlet.ServletUtils; +import com.tashow.cloud.web.web.core.util.WebFrameworkUtils; +import jakarta.servlet.FilterChain; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.springframework.web.filter.OncePerRequestFilter; + +import static com.tashow.cloud.common.exception.enums.GlobalErrorCodeConstants.DEMO_DENY; + + +/** + * 演示 Filter,禁止用户发起写操作,避免影响测试数据 + * + * @author 芋道源码 + */ +public class DemoFilter extends OncePerRequestFilter { + + @Override + protected boolean shouldNotFilter(HttpServletRequest request) { + String method = request.getMethod(); + return !StrUtil.equalsAnyIgnoreCase(method, "POST", "PUT", "DELETE") // 写操作时,不进行过滤率 + || WebFrameworkUtils.getLoginUserId(request) == null; // 非登录用户时,不进行过滤 + } + + @Override + protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) { + // 直接返回 DEMO_DENY 的结果。即,请求不继续 + ServletUtils.writeJSON(response, CommonResult.error(DEMO_DENY)); + } + +} diff --git a/tashow-framework/tashow-framework-web/src/main/java/com/tashow/cloud/web/web/core/handler/GlobalExceptionHandler.java b/tashow-framework/tashow-framework-web/src/main/java/com/tashow/cloud/web/web/core/handler/GlobalExceptionHandler.java new file mode 100644 index 0000000..31f77a3 --- /dev/null +++ b/tashow-framework/tashow-framework-web/src/main/java/com/tashow/cloud/web/web/core/handler/GlobalExceptionHandler.java @@ -0,0 +1,391 @@ +package com.tashow.cloud.web.web.core.handler; + +import cn.hutool.core.exceptions.ExceptionUtil; +import cn.hutool.core.map.MapUtil; +import cn.hutool.core.util.ObjUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.extra.servlet.JakartaServletUtil; +import com.tashow.cloud.common.exception.ServiceException; +import com.tashow.cloud.common.exception.util.ServiceExceptionUtil; +import com.tashow.cloud.common.pojo.CommonResult; +import com.tashow.cloud.common.util.collection.SetUtils; +import com.tashow.cloud.common.util.json.JsonUtils; +import com.tashow.cloud.common.util.monitor.TracerUtils; +import com.tashow.cloud.common.util.servlet.ServletUtils; +import com.tashow.cloud.infraapi.api.logger.ApiErrorLogApi; +import com.tashow.cloud.infraapi.api.logger.dto.ApiErrorLogCreateReqDTO; +import com.fasterxml.jackson.databind.exc.InvalidFormatException; +import com.tashow.cloud.web.web.core.util.WebFrameworkUtils; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.validation.ConstraintViolation; +import jakarta.validation.ConstraintViolationException; +import jakarta.validation.ValidationException; +import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.converter.HttpMessageNotReadableException; +import org.springframework.security.access.AccessDeniedException; +import org.springframework.util.Assert; +import org.springframework.validation.BindException; +import org.springframework.validation.FieldError; +import org.springframework.web.HttpRequestMethodNotSupportedException; +import org.springframework.web.bind.MethodArgumentNotValidException; +import org.springframework.web.bind.MissingServletRequestParameterException; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.RestControllerAdvice; +import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException; +import org.springframework.web.servlet.NoHandlerFoundException; +import org.springframework.web.servlet.resource.NoResourceFoundException; + +import java.time.LocalDateTime; +import java.util.Map; +import java.util.Set; + +import static com.tashow.cloud.common.exception.enums.GlobalErrorCodeConstants.*; + + +/** + * 全局异常处理器,将 Exception 翻译成 CommonResult + 对应的异常编号 + * + * @author 芋道源码 + */ +@RestControllerAdvice +@AllArgsConstructor +@Slf4j +public class GlobalExceptionHandler { + + /** + * 忽略的 ServiceException 错误提示,避免打印过多 logger + */ + public static final Set IGNORE_ERROR_MESSAGES = SetUtils.asSet("无效的刷新令牌"); + + @SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection") + private final String applicationName; + + private final ApiErrorLogApi apiErrorLogApi; + + /** + * 处理所有异常,主要是提供给 Filter 使用 + * 因为 Filter 不走 SpringMVC 的流程,但是我们又需要兜底处理异常,所以这里提供一个全量的异常处理过程,保持逻辑统一。 + * + * @param request 请求 + * @param ex 异常 + * @return 通用返回 + */ + public CommonResult allExceptionHandler(HttpServletRequest request, Throwable ex) { + if (ex instanceof MissingServletRequestParameterException) { + return missingServletRequestParameterExceptionHandler((MissingServletRequestParameterException) ex); + } + if (ex instanceof MethodArgumentTypeMismatchException) { + return methodArgumentTypeMismatchExceptionHandler((MethodArgumentTypeMismatchException) ex); + } + if (ex instanceof MethodArgumentNotValidException) { + return methodArgumentNotValidExceptionExceptionHandler((MethodArgumentNotValidException) ex); + } + if (ex instanceof BindException) { + return bindExceptionHandler((BindException) ex); + } + if (ex instanceof ConstraintViolationException) { + return constraintViolationExceptionHandler((ConstraintViolationException) ex); + } + if (ex instanceof ValidationException) { + return validationException((ValidationException) ex); + } + if (ex instanceof NoHandlerFoundException) { + return noHandlerFoundExceptionHandler((NoHandlerFoundException) ex); + } + if (ex instanceof NoResourceFoundException) { + return noResourceFoundExceptionHandler(request, (NoResourceFoundException) ex); + } + if (ex instanceof HttpRequestMethodNotSupportedException) { + return httpRequestMethodNotSupportedExceptionHandler((HttpRequestMethodNotSupportedException) ex); + } + if (ex instanceof ServiceException) { + return serviceExceptionHandler((ServiceException) ex); + } + if (ex instanceof AccessDeniedException) { + return accessDeniedExceptionHandler(request, (AccessDeniedException) ex); + } + return defaultExceptionHandler(request, ex); + } + + /** + * 处理 SpringMVC 请求参数缺失 + * + * 例如说,接口上设置了 @RequestParam("xx") 参数,结果并未传递 xx 参数 + */ + @ExceptionHandler(value = MissingServletRequestParameterException.class) + public CommonResult missingServletRequestParameterExceptionHandler(MissingServletRequestParameterException ex) { + log.warn("[missingServletRequestParameterExceptionHandler]", ex); + return CommonResult.error(BAD_REQUEST.getCode(), String.format("请求参数缺失:%s", ex.getParameterName())); + } + + /** + * 处理 SpringMVC 请求参数类型错误 + * + * 例如说,接口上设置了 @RequestParam("xx") 参数为 Integer,结果传递 xx 参数类型为 String + */ + @ExceptionHandler(MethodArgumentTypeMismatchException.class) + public CommonResult methodArgumentTypeMismatchExceptionHandler(MethodArgumentTypeMismatchException ex) { + log.warn("[methodArgumentTypeMismatchExceptionHandler]", ex); + return CommonResult.error(BAD_REQUEST.getCode(), String.format("请求参数类型错误:%s", ex.getMessage())); + } + + /** + * 处理 SpringMVC 参数校验不正确 + */ + @ExceptionHandler(MethodArgumentNotValidException.class) + public CommonResult methodArgumentNotValidExceptionExceptionHandler(MethodArgumentNotValidException ex) { + log.warn("[methodArgumentNotValidExceptionExceptionHandler]", ex); + FieldError fieldError = ex.getBindingResult().getFieldError(); + assert fieldError != null; // 断言,避免告警 + return CommonResult.error(BAD_REQUEST.getCode(), String.format("请求参数不正确:%s", fieldError.getDefaultMessage())); + } + + /** + * 处理 SpringMVC 参数绑定不正确,本质上也是通过 Validator 校验 + */ + @ExceptionHandler(BindException.class) + public CommonResult bindExceptionHandler(BindException ex) { + log.warn("[handleBindException]", ex); + FieldError fieldError = ex.getFieldError(); + assert fieldError != null; // 断言,避免告警 + return CommonResult.error(BAD_REQUEST.getCode(), String.format("请求参数不正确:%s", fieldError.getDefaultMessage())); + } + + /** + * 处理 SpringMVC 请求参数类型错误 + * + * 例如说,接口上设置了 @RequestBody实体中 xx 属性类型为 Integer,结果传递 xx 参数类型为 String + */ + @ExceptionHandler(HttpMessageNotReadableException.class) + public CommonResult methodArgumentTypeInvalidFormatExceptionHandler(HttpMessageNotReadableException ex) { + log.warn("[methodArgumentTypeInvalidFormatExceptionHandler]", ex); + if(ex.getCause() instanceof InvalidFormatException) { + InvalidFormatException invalidFormatException = (InvalidFormatException) ex.getCause(); + return CommonResult.error(BAD_REQUEST.getCode(), String.format("请求参数类型错误:%s", invalidFormatException.getValue())); + }else { + return defaultExceptionHandler(ServletUtils.getRequest(), ex); + } + } + + /** + * 处理 Validator 校验不通过产生的异常 + */ + @ExceptionHandler(value = ConstraintViolationException.class) + public CommonResult constraintViolationExceptionHandler(ConstraintViolationException ex) { + log.warn("[constraintViolationExceptionHandler]", ex); + ConstraintViolation constraintViolation = ex.getConstraintViolations().iterator().next(); + return CommonResult.error(BAD_REQUEST.getCode(), String.format("请求参数不正确:%s", constraintViolation.getMessage())); + } + + /** + * 处理 Dubbo Consumer 本地参数校验时,抛出的 ValidationException 异常 + */ + @ExceptionHandler(value = ValidationException.class) + public CommonResult validationException(ValidationException ex) { + log.warn("[constraintViolationExceptionHandler]", ex); + // 无法拼接明细的错误信息,因为 Dubbo Consumer 抛出 ValidationException 异常时,是直接的字符串信息,且人类不可读 + return CommonResult.error(BAD_REQUEST); + } + + /** + * 处理 SpringMVC 请求地址不存在 + * + * 注意,它需要设置如下两个配置项: + * 1. spring.mvc.throw-exception-if-no-handler-found 为 true + * 2. spring.mvc.static-path-pattern 为 /statics/** + */ + @ExceptionHandler(NoHandlerFoundException.class) + public CommonResult noHandlerFoundExceptionHandler(NoHandlerFoundException ex) { + log.warn("[noHandlerFoundExceptionHandler]", ex); + return CommonResult.error(NOT_FOUND.getCode(), String.format("请求地址不存在:%s", ex.getRequestURL())); + } + + /** + * 处理 SpringMVC 请求地址不存在 + */ + @ExceptionHandler(NoResourceFoundException.class) + private CommonResult noResourceFoundExceptionHandler(HttpServletRequest req, NoResourceFoundException ex) { + log.warn("[noResourceFoundExceptionHandler]", ex); + return CommonResult.error(NOT_FOUND.getCode(), String.format("请求地址不存在:%s", ex.getResourcePath())); + } + + /** + * 处理 SpringMVC 请求方法不正确 + * + * 例如说,A 接口的方法为 GET 方式,结果请求方法为 POST 方式,导致不匹配 + */ + @ExceptionHandler(HttpRequestMethodNotSupportedException.class) + public CommonResult httpRequestMethodNotSupportedExceptionHandler(HttpRequestMethodNotSupportedException ex) { + log.warn("[httpRequestMethodNotSupportedExceptionHandler]", ex); + return CommonResult.error(METHOD_NOT_ALLOWED.getCode(), String.format("请求方法不正确:%s", ex.getMessage())); + } + + /** + * 处理 Spring Security 权限不足的异常 + * + * 来源是,使用 @PreAuthorize 注解,AOP 进行权限拦截 + */ + @ExceptionHandler(value = AccessDeniedException.class) + public CommonResult accessDeniedExceptionHandler(HttpServletRequest req, AccessDeniedException ex) { + log.warn("[accessDeniedExceptionHandler][userId({}) 无法访问 url({})]", WebFrameworkUtils.getLoginUserId(req), + req.getRequestURL(), ex); + return CommonResult.error(FORBIDDEN); + } + + /** + * 处理业务异常 ServiceException + * + * 例如说,商品库存不足,用户手机号已存在。 + */ + @ExceptionHandler(value = ServiceException.class) + public CommonResult serviceExceptionHandler(ServiceException ex) { + // 不包含的时候,才进行打印,避免 ex 堆栈过多 + if (!IGNORE_ERROR_MESSAGES.contains(ex.getMessage())) { + // 即使打印,也只打印第一层 StackTraceElement,并且使用 warn 在控制台输出,更容易看到 + try { + StackTraceElement[] stackTraces = ex.getStackTrace(); + for (StackTraceElement stackTrace : stackTraces) { + if (ObjUtil.notEqual(stackTrace.getClassName(), ServiceExceptionUtil.class.getName())) { + log.warn("[serviceExceptionHandler]\n\t{}", stackTrace); + break; + } + } + } catch (Exception ignored) { + // 忽略日志,避免影响主流程 + } + } + return CommonResult.error(ex.getCode(), ex.getMessage()); + } + + /** + * 处理系统异常,兜底处理所有的一切 + */ + @ExceptionHandler(value = Exception.class) + public CommonResult defaultExceptionHandler(HttpServletRequest req, Throwable ex) { + // 情况一:处理表不存在的异常 + CommonResult tableNotExistsResult = handleTableNotExists(ex); + if (tableNotExistsResult != null) { + return tableNotExistsResult; + } + + // 情况二:处理异常 + log.error("[defaultExceptionHandler]", ex); + // 插入异常日志 + createExceptionLog(req, ex); + // 返回 ERROR CommonResult + return CommonResult.error(INTERNAL_SERVER_ERROR.getCode(), INTERNAL_SERVER_ERROR.getMsg()); + } + + private void createExceptionLog(HttpServletRequest req, Throwable e) { + // 插入错误日志 + ApiErrorLogCreateReqDTO errorLog = new ApiErrorLogCreateReqDTO(); + try { + // 初始化 errorLog + buildExceptionLog(errorLog, req, e); + // 执行插入 errorLog + apiErrorLogApi.createApiErrorLogAsync(errorLog); + } catch (Throwable th) { + log.error("[createExceptionLog][url({}) log({}) 发生异常]", req.getRequestURI(), JsonUtils.toJsonString(errorLog), th); + } + } + + private void buildExceptionLog(ApiErrorLogCreateReqDTO errorLog, HttpServletRequest request, Throwable e) { + // 处理用户信息 + errorLog.setUserId(WebFrameworkUtils.getLoginUserId(request)); + errorLog.setUserType(WebFrameworkUtils.getLoginUserType(request)); + // 设置异常字段 + errorLog.setExceptionName(e.getClass().getName()); + errorLog.setExceptionMessage(ExceptionUtil.getMessage(e)); + errorLog.setExceptionRootCauseMessage(ExceptionUtil.getRootCauseMessage(e)); + errorLog.setExceptionStackTrace(ExceptionUtil.stacktraceToString(e)); + StackTraceElement[] stackTraceElements = e.getStackTrace(); + Assert.notEmpty(stackTraceElements, "异常 stackTraceElements 不能为空"); + StackTraceElement stackTraceElement = stackTraceElements[0]; + errorLog.setExceptionClassName(stackTraceElement.getClassName()); + errorLog.setExceptionFileName(stackTraceElement.getFileName()); + errorLog.setExceptionMethodName(stackTraceElement.getMethodName()); + errorLog.setExceptionLineNumber(stackTraceElement.getLineNumber()); + // 设置其它字段 + errorLog.setTraceId(TracerUtils.getTraceId()); + errorLog.setApplicationName(applicationName); + errorLog.setRequestUrl(request.getRequestURI()); + Map requestParams = MapUtil.builder() + .put("query", JakartaServletUtil.getParamMap(request)) + .put("body", JakartaServletUtil.getBody(request)).build(); + errorLog.setRequestParams(JsonUtils.toJsonString(requestParams)); + errorLog.setRequestMethod(request.getMethod()); + errorLog.setUserAgent(ServletUtils.getUserAgent(request)); + errorLog.setUserIp(JakartaServletUtil.getClientIP(request)); + errorLog.setExceptionTime(LocalDateTime.now()); + } + + /** + * 处理 Table 不存在的异常情况 + * + * @param ex 异常 + * @return 如果是 Table 不存在的异常,则返回对应的 CommonResult + */ + private CommonResult handleTableNotExists(Throwable ex) { + String message = ExceptionUtil.getRootCauseMessage(ex); + if (!message.contains("doesn't exist")) { + return null; + } + // 1. 数据报表 + if (message.contains("report_")) { + log.error("[报表模块 yudao-module-report - 表结构未导入][参考 https://cloud.iocoder.cn/report/ 开启]"); + return CommonResult.error(NOT_IMPLEMENTED.getCode(), + "[报表模块 yudao-module-report - 表结构未导入][参考 https://cloud.iocoder.cn/report/ 开启]"); + } + // 2. 工作流 + if (message.contains("bpm_")) { + log.error("[工作流模块 yudao-module-bpm - 表结构未导入][参考 https://cloud.iocoder.cn/bpm/ 开启]"); + return CommonResult.error(NOT_IMPLEMENTED.getCode(), + "[工作流模块 yudao-module-bpm - 表结构未导入][参考 https://cloud.iocoder.cn/bpm/ 开启]"); + } + // 3. 微信公众号 + if (message.contains("mp_")) { + log.error("[微信公众号 yudao-module-mp - 表结构未导入][参考 https://cloud.iocoder.cn/mp/build/ 开启]"); + return CommonResult.error(NOT_IMPLEMENTED.getCode(), + "[微信公众号 yudao-module-mp - 表结构未导入][参考 https://cloud.iocoder.cn/mp/build/ 开启]"); + } + // 4. 商城系统 + if (StrUtil.containsAny(message, "product_", "promotion_", "trade_")) { + log.error("[商城系统 yudao-module-mall - 已禁用][参考 https://cloud.iocoder.cn/mall/build/ 开启]"); + return CommonResult.error(NOT_IMPLEMENTED.getCode(), + "[商城系统 yudao-module-mall - 已禁用][参考 https://cloud.iocoder.cn/mall/build/ 开启]"); + } + // 5. ERP 系统 + if (message.contains("erp_")) { + log.error("[ERP 系统 yudao-module-erp - 表结构未导入][参考 https://cloud.iocoder.cn/erp/build/ 开启]"); + return CommonResult.error(NOT_IMPLEMENTED.getCode(), + "[ERP 系统 yudao-module-erp - 表结构未导入][参考 https://cloud.iocoder.cn/erp/build/ 开启]"); + } + // 6. CRM 系统 + if (message.contains("crm_")) { + log.error("[CRM 系统 yudao-module-crm - 表结构未导入][参考 https://cloud.iocoder.cn/crm/build/ 开启]"); + return CommonResult.error(NOT_IMPLEMENTED.getCode(), + "[CRM 系统 yudao-module-crm - 表结构未导入][参考 https://cloud.iocoder.cn/crm/build/ 开启]"); + } + // 7. 支付平台 + if (message.contains("pay_")) { + log.error("[支付模块 yudao-module-pay - 表结构未导入][参考 https://cloud.iocoder.cn/pay/build/ 开启]"); + return CommonResult.error(NOT_IMPLEMENTED.getCode(), + "[支付模块 yudao-module-pay - 表结构未导入][参考 https://cloud.iocoder.cn/pay/build/ 开启]"); + } + // 8. AI 大模型 + if (message.contains("ai_")) { + log.error("[AI 大模型 yudao-module-ai - 表结构未导入][参考 https://cloud.iocoder.cn/ai/build/ 开启]"); + return CommonResult.error(NOT_IMPLEMENTED.getCode(), + "[AI 大模型 yudao-module-ai - 表结构未导入][参考 https://cloud.iocoder.cn/ai/build/ 开启]"); + } + // 9. IOT 物联网 + if (message.contains("iot_")) { + log.error("[IOT 物联网 yudao-module-iot - 表结构未导入][参考 https://doc.iocoder.cn/iot/build/ 开启]"); + return CommonResult.error(NOT_IMPLEMENTED.getCode(), + "[IOT 物联网 yudao-module-iot - 表结构未导入][参考 https://doc.iocoder.cn/iot/build/ 开启]"); + } + return null; + } + +} diff --git a/tashow-framework/tashow-framework-web/src/main/java/com/tashow/cloud/web/web/core/handler/GlobalResponseBodyHandler.java b/tashow-framework/tashow-framework-web/src/main/java/com/tashow/cloud/web/web/core/handler/GlobalResponseBodyHandler.java new file mode 100644 index 0000000..d86ca76 --- /dev/null +++ b/tashow-framework/tashow-framework-web/src/main/java/com/tashow/cloud/web/web/core/handler/GlobalResponseBodyHandler.java @@ -0,0 +1,46 @@ +package com.tashow.cloud.web.web.core.handler; + +import com.tashow.cloud.common.pojo.CommonResult; +import com.tashow.cloud.web.apilog.core.filter.ApiAccessLogFilter; +import com.tashow.cloud.web.web.core.util.WebFrameworkUtils; +import org.springframework.core.MethodParameter; +import org.springframework.http.MediaType; +import org.springframework.http.server.ServerHttpRequest; +import org.springframework.http.server.ServerHttpResponse; +import org.springframework.http.server.ServletServerHttpRequest; +import org.springframework.web.bind.annotation.ControllerAdvice; +import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice; + +/** + * 全局响应结果(ResponseBody)处理器 + * + * 不同于在网上看到的很多文章,会选择自动将 Controller 返回结果包上 {@link CommonResult}, + * 在 onemall 中,是 Controller 在返回时,主动自己包上 {@link CommonResult}。 + * 原因是,GlobalResponseBodyHandler 本质上是 AOP,它不应该改变 Controller 返回的数据结构 + * + * 目前,GlobalResponseBodyHandler 的主要作用是,记录 Controller 的返回结果, + * 方便 {@link ApiAccessLogFilter} 记录访问日志 + */ +@ControllerAdvice +public class GlobalResponseBodyHandler implements ResponseBodyAdvice { + + @Override + @SuppressWarnings("NullableProblems") // 避免 IDEA 警告 + public boolean supports(MethodParameter returnType, Class converterType) { + if (returnType.getMethod() == null) { + return false; + } + // 只拦截返回结果为 CommonResult 类型 + return returnType.getMethod().getReturnType() == CommonResult.class; + } + + @Override + @SuppressWarnings("NullableProblems") // 避免 IDEA 警告 + public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, + ServerHttpRequest request, ServerHttpResponse response) { + // 记录 Controller 结果 + WebFrameworkUtils.setCommonResult(((ServletServerHttpRequest) request).getServletRequest(), (CommonResult) body); + return body; + } + +} diff --git a/tashow-framework/tashow-framework-web/src/main/java/com/tashow/cloud/web/web/core/util/WebFrameworkUtils.java b/tashow-framework/tashow-framework-web/src/main/java/com/tashow/cloud/web/web/core/util/WebFrameworkUtils.java new file mode 100644 index 0000000..333c172 --- /dev/null +++ b/tashow-framework/tashow-framework-web/src/main/java/com/tashow/cloud/web/web/core/util/WebFrameworkUtils.java @@ -0,0 +1,166 @@ +package com.tashow.cloud.web.web.core.util; + +import cn.hutool.core.util.NumberUtil; +import com.tashow.cloud.common.enums.RpcConstants; +import com.tashow.cloud.common.enums.TerminalEnum; +import com.tashow.cloud.common.enums.UserTypeEnum; +import com.tashow.cloud.common.pojo.CommonResult; +import com.tashow.cloud.web.web.config.WebProperties; +import jakarta.servlet.ServletRequest; +import jakarta.servlet.http.HttpServletRequest; +import org.springframework.web.context.request.RequestAttributes; +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.context.request.ServletRequestAttributes; + +/** + * 专属于 web 包的工具类 + * + * @author 芋道源码 + */ +public class WebFrameworkUtils { + + private static final String REQUEST_ATTRIBUTE_LOGIN_USER_ID = "login_user_id"; + private static final String REQUEST_ATTRIBUTE_LOGIN_USER_TYPE = "login_user_type"; + + private static final String REQUEST_ATTRIBUTE_COMMON_RESULT = "common_result"; + + public static final String HEADER_TENANT_ID = "tenant-id"; + + /** + * 终端的 Header + * + * @see TerminalEnum + */ + public static final String HEADER_TERMINAL = "terminal"; + + private static WebProperties properties; + + public WebFrameworkUtils(WebProperties webProperties) { + WebFrameworkUtils.properties = webProperties; + } + + /** + * 获得租户编号,从 header 中 + * 考虑到其它 framework 组件也会使用到租户编号,所以不得不放在 WebFrameworkUtils 统一提供 + * + * @param request 请求 + * @return 租户编号 + */ + public static Long getTenantId(HttpServletRequest request) { + String tenantId = request.getHeader(HEADER_TENANT_ID); + return NumberUtil.isNumber(tenantId) ? Long.valueOf(tenantId) : null; + } + + public static void setLoginUserId(ServletRequest request, Long userId) { + request.setAttribute(REQUEST_ATTRIBUTE_LOGIN_USER_ID, userId); + } + + /** + * 设置用户类型 + * + * @param request 请求 + * @param userType 用户类型 + */ + public static void setLoginUserType(ServletRequest request, Integer userType) { + request.setAttribute(REQUEST_ATTRIBUTE_LOGIN_USER_TYPE, userType); + } + + /** + * 获得当前用户的编号,从请求中 + * 注意:该方法仅限于 framework 框架使用!!! + * + * @param request 请求 + * @return 用户编号 + */ + public static Long getLoginUserId(HttpServletRequest request) { + if (request == null) { + return null; + } + return (Long) request.getAttribute(REQUEST_ATTRIBUTE_LOGIN_USER_ID); + } + + /** + * 获得当前用户的类型 + * 注意:该方法仅限于 web 相关的 framework 组件使用!!! + * + * @param request 请求 + * @return 用户编号 + */ + public static Integer getLoginUserType(HttpServletRequest request) { + if (request == null) { + return null; + } + // 1. 优先,从 Attribute 中获取 + Integer userType = (Integer) request.getAttribute(REQUEST_ATTRIBUTE_LOGIN_USER_TYPE); + if (userType != null) { + return userType; + } + // 2. 其次,基于 URL 前缀的约定 + if (request.getServletPath().startsWith(properties.getAdminApi().getPrefix())) { + return UserTypeEnum.ADMIN.getValue(); + } + if (request.getServletPath().startsWith(properties.getAppApi().getPrefix())) { + return UserTypeEnum.MEMBER.getValue(); + } + return null; + } + + public static Integer getLoginUserType() { + HttpServletRequest request = getRequest(); + return getLoginUserType(request); + } + + public static Long getLoginUserId() { + HttpServletRequest request = getRequest(); + return getLoginUserId(request); + } + + public static Integer getTerminal() { + HttpServletRequest request = getRequest(); + if (request == null) { + return TerminalEnum.UNKNOWN.getTerminal(); + } + String terminalValue = request.getHeader(HEADER_TERMINAL); + return NumberUtil.parseInt(terminalValue, TerminalEnum.UNKNOWN.getTerminal()); + } + + public static void setCommonResult(ServletRequest request, CommonResult result) { + request.setAttribute(REQUEST_ATTRIBUTE_COMMON_RESULT, result); + } + + public static CommonResult getCommonResult(ServletRequest request) { + return (CommonResult) request.getAttribute(REQUEST_ATTRIBUTE_COMMON_RESULT); + } + + public static HttpServletRequest getRequest() { + RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes(); + if (!(requestAttributes instanceof ServletRequestAttributes)) { + return null; + } + ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) requestAttributes; + return servletRequestAttributes.getRequest(); + } + + /** + * 判断是否为 RPC 请求 + * + * @param request 请求 + * @return 是否为 RPC 请求 + */ + public static boolean isRpcRequest(HttpServletRequest request) { + return request.getRequestURI().startsWith(RpcConstants.RPC_API_PREFIX); + } + + /** + * 判断是否为 RPC 请求 + * + * 约定大于配置,只要以 Api 结尾,都认为是 RPC 接口 + * + * @param className 类名 + * @return 是否为 RPC 请求 + */ + public static boolean isRpcRequest(String className) { + return className.endsWith("Api"); + } + +} diff --git a/tashow-framework/tashow-framework-web/src/main/java/com/tashow/cloud/web/web/package-info.java b/tashow-framework/tashow-framework-web/src/main/java/com/tashow/cloud/web/web/package-info.java new file mode 100644 index 0000000..79c7df1 --- /dev/null +++ b/tashow-framework/tashow-framework-web/src/main/java/com/tashow/cloud/web/web/package-info.java @@ -0,0 +1,4 @@ +/** + * 针对 SpringMVC 的基础封装 + */ +package com.tashow.cloud.web.web; diff --git a/tashow-framework/tashow-framework-web/src/main/java/com/tashow/cloud/web/xss/config/XssProperties.java b/tashow-framework/tashow-framework-web/src/main/java/com/tashow/cloud/web/xss/config/XssProperties.java new file mode 100644 index 0000000..5739bad --- /dev/null +++ b/tashow-framework/tashow-framework-web/src/main/java/com/tashow/cloud/web/xss/config/XssProperties.java @@ -0,0 +1,29 @@ +package com.tashow.cloud.web.xss.config; + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.validation.annotation.Validated; + +import java.util.Collections; +import java.util.List; + +/** + * Xss 配置属性 + * + * @author 芋道源码 + */ +@ConfigurationProperties(prefix = "yudao.xss") +@Validated +@Data +public class XssProperties { + + /** + * 是否开启,默认为 true + */ + private boolean enable = true; + /** + * 需要排除的 URL,默认为空 + */ + private List excludeUrls = Collections.emptyList(); + +} diff --git a/tashow-framework/tashow-framework-web/src/main/java/com/tashow/cloud/web/xss/config/YudaoXssAutoConfiguration.java b/tashow-framework/tashow-framework-web/src/main/java/com/tashow/cloud/web/xss/config/YudaoXssAutoConfiguration.java new file mode 100644 index 0000000..a75bbb6 --- /dev/null +++ b/tashow-framework/tashow-framework-web/src/main/java/com/tashow/cloud/web/xss/config/YudaoXssAutoConfiguration.java @@ -0,0 +1,64 @@ +package com.tashow.cloud.web.xss.config; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.tashow.cloud.common.enums.WebFilterOrderEnum; +import com.tashow.cloud.web.xss.core.clean.JsoupXssCleaner; +import com.tashow.cloud.web.xss.core.clean.XssCleaner; +import com.tashow.cloud.web.xss.core.filter.XssFilter; +import com.tashow.cloud.web.xss.core.json.XssStringJsonDeserializer; +import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.boot.web.servlet.FilterRegistrationBean; +import org.springframework.context.annotation.Bean; +import org.springframework.util.PathMatcher; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +import static com.tashow.cloud.web.web.config.WebAutoConfiguration.createFilterBean; + + +@AutoConfiguration +@EnableConfigurationProperties(XssProperties.class) +@ConditionalOnProperty(prefix = "yudao.xss", name = "enable", havingValue = "true", matchIfMissing = true) // 设置为 false 时,禁用 +public class YudaoXssAutoConfiguration implements WebMvcConfigurer { + + /** + * Xss 清理者 + * + * @return XssCleaner + */ + @Bean + @ConditionalOnMissingBean(XssCleaner.class) + public XssCleaner xssCleaner() { + return new JsoupXssCleaner(); + } + + /** + * 注册 Jackson 的序列化器,用于处理 json 类型参数的 xss 过滤 + * + * @return Jackson2ObjectMapperBuilderCustomizer + */ + @Bean + @ConditionalOnMissingBean(name = "xssJacksonCustomizer") + @ConditionalOnBean(ObjectMapper.class) + @ConditionalOnProperty(value = "yudao.xss.enable", havingValue = "true") + public Jackson2ObjectMapperBuilderCustomizer xssJacksonCustomizer(XssProperties properties, + PathMatcher pathMatcher, + XssCleaner xssCleaner) { + // 在反序列化时进行 xss 过滤,可以替换使用 XssStringJsonSerializer,在序列化时进行处理 + return builder -> builder.deserializerByType(String.class, new XssStringJsonDeserializer(properties, pathMatcher, xssCleaner)); + } + + /** + * 创建 XssFilter Bean,解决 Xss 安全问题 + */ + @Bean + @ConditionalOnBean(XssCleaner.class) + public FilterRegistrationBean xssFilter(XssProperties properties, PathMatcher pathMatcher, XssCleaner xssCleaner) { + return createFilterBean(new XssFilter(properties, pathMatcher, xssCleaner), WebFilterOrderEnum.XSS_FILTER); + } + +} diff --git a/tashow-framework/tashow-framework-web/src/main/java/com/tashow/cloud/web/xss/core/clean/JsoupXssCleaner.java b/tashow-framework/tashow-framework-web/src/main/java/com/tashow/cloud/web/xss/core/clean/JsoupXssCleaner.java new file mode 100644 index 0000000..280065a --- /dev/null +++ b/tashow-framework/tashow-framework-web/src/main/java/com/tashow/cloud/web/xss/core/clean/JsoupXssCleaner.java @@ -0,0 +1,64 @@ +package com.tashow.cloud.web.xss.core.clean; + +import org.jsoup.Jsoup; +import org.jsoup.nodes.Document; +import org.jsoup.safety.Safelist; + +/** + * 基于 JSONP 实现 XSS 过滤字符串 + */ +public class JsoupXssCleaner implements XssCleaner { + + private final Safelist safelist; + + /** + * 用于在 src 属性使用相对路径时,强制转换为绝对路径。 为空时不处理,值应为绝对路径的前缀(包含协议部分) + */ + private final String baseUri; + + /** + * 无参构造,默认使用 {@link JsoupXssCleaner#buildSafelist} 方法构建一个安全列表 + */ + public JsoupXssCleaner() { + this.safelist = buildSafelist(); + this.baseUri = ""; + } + + /** + * 构建一个 Xss 清理的 Safelist 规则。 + * 基于 Safelist#relaxed() 的基础上: + * 1. 扩展支持了 style 和 class 属性 + * 2. a 标签额外支持了 target 属性 + * 3. img 标签额外支持了 data 协议,便于支持 base64 + * + * @return Safelist + */ + private Safelist buildSafelist() { + // 使用 jsoup 提供的默认的 + Safelist relaxedSafelist = Safelist.relaxed(); + // 富文本编辑时一些样式是使用 style 来进行实现的 + // 比如红色字体 style="color:red;", 所以需要给所有标签添加 style 属性 + // 注意:style 属性会有注入风险 + relaxedSafelist.addAttributes(":all", "style", "class"); + // 保留 a 标签的 target 属性 + relaxedSafelist.addAttributes("a", "target"); + // 支持img 为base64 + relaxedSafelist.addProtocols("img", "src", "data"); + + // 保留相对路径, 保留相对路径时,必须提供对应的 baseUri 属性,否则依然会被删除 + // WHITELIST.preserveRelativeLinks(false); + + // 移除 a 标签和 img 标签的一些协议限制,这会导致 xss 防注入失效,如 + // 虽然可以重写 WhiteList#isSafeAttribute 来处理,但是有隐患,所以暂时不支持相对路径 + // WHITELIST.removeProtocols("a", "href", "ftp", "http", "https", "mailto"); + // WHITELIST.removeProtocols("img", "src", "http", "https"); + return relaxedSafelist; + } + + @Override + public String clean(String html) { + return Jsoup.clean(html, baseUri, safelist, new Document.OutputSettings().prettyPrint(false)); + } + +} + diff --git a/tashow-framework/tashow-framework-web/src/main/java/com/tashow/cloud/web/xss/core/clean/XssCleaner.java b/tashow-framework/tashow-framework-web/src/main/java/com/tashow/cloud/web/xss/core/clean/XssCleaner.java new file mode 100644 index 0000000..b9eba68 --- /dev/null +++ b/tashow-framework/tashow-framework-web/src/main/java/com/tashow/cloud/web/xss/core/clean/XssCleaner.java @@ -0,0 +1,16 @@ +package com.tashow.cloud.web.xss.core.clean; + +/** + * 对 html 文本中的有 Xss 风险的数据进行清理 + */ +public interface XssCleaner { + + /** + * 清理有 Xss 风险的文本 + * + * @param html 原 html + * @return 清理后的 html + */ + String clean(String html); + +} diff --git a/tashow-framework/tashow-framework-web/src/main/java/com/tashow/cloud/web/xss/core/filter/XssFilter.java b/tashow-framework/tashow-framework-web/src/main/java/com/tashow/cloud/web/xss/core/filter/XssFilter.java new file mode 100644 index 0000000..250db1f --- /dev/null +++ b/tashow-framework/tashow-framework-web/src/main/java/com/tashow/cloud/web/xss/core/filter/XssFilter.java @@ -0,0 +1,52 @@ +package com.tashow.cloud.web.xss.core.filter; + +import com.tashow.cloud.web.xss.config.XssProperties; +import com.tashow.cloud.web.xss.core.clean.XssCleaner; +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import lombok.AllArgsConstructor; +import org.springframework.util.PathMatcher; +import org.springframework.web.filter.OncePerRequestFilter; + +import java.io.IOException; + +/** + * Xss 过滤器 + * + * @author 芋道源码 + */ +@AllArgsConstructor +public class XssFilter extends OncePerRequestFilter { + + /** + * 属性 + */ + private final XssProperties properties; + /** + * 路径匹配器 + */ + private final PathMatcher pathMatcher; + + private final XssCleaner xssCleaner; + + @Override + protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) + throws IOException, ServletException { + filterChain.doFilter(new XssRequestWrapper(request, xssCleaner), response); + } + + @Override + protected boolean shouldNotFilter(HttpServletRequest request) { + // 如果关闭,则不过滤 + if (!properties.isEnable()) { + return true; + } + + // 如果匹配到无需过滤,则不过滤 + String uri = request.getRequestURI(); + return properties.getExcludeUrls().stream().anyMatch(excludeUrl -> pathMatcher.match(excludeUrl, uri)); + } + +} diff --git a/tashow-framework/tashow-framework-web/src/main/java/com/tashow/cloud/web/xss/core/filter/XssRequestWrapper.java b/tashow-framework/tashow-framework-web/src/main/java/com/tashow/cloud/web/xss/core/filter/XssRequestWrapper.java new file mode 100644 index 0000000..6eec5da --- /dev/null +++ b/tashow-framework/tashow-framework-web/src/main/java/com/tashow/cloud/web/xss/core/filter/XssRequestWrapper.java @@ -0,0 +1,92 @@ +package com.tashow.cloud.web.xss.core.filter; + +import com.tashow.cloud.web.xss.core.clean.XssCleaner; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletRequestWrapper; + +import java.util.LinkedHashMap; +import java.util.Map; + +/** + * Xss 请求 Wrapper + * + * @author 芋道源码 + */ +public class XssRequestWrapper extends HttpServletRequestWrapper { + + private final XssCleaner xssCleaner; + + public XssRequestWrapper(HttpServletRequest request, XssCleaner xssCleaner) { + super(request); + this.xssCleaner = xssCleaner; + } + + // ============================ parameter ============================ + @Override + public Map getParameterMap() { + Map map = new LinkedHashMap<>(); + Map parameters = super.getParameterMap(); + for (Map.Entry entry : parameters.entrySet()) { + String[] values = entry.getValue(); + for (int i = 0; i < values.length; i++) { + values[i] = xssCleaner.clean(values[i]); + } + map.put(entry.getKey(), values); + } + return map; + } + + @Override + public String[] getParameterValues(String name) { + String[] values = super.getParameterValues(name); + if (values == null) { + return null; + } + int count = values.length; + String[] encodedValues = new String[count]; + for (int i = 0; i < count; i++) { + encodedValues[i] = xssCleaner.clean(values[i]); + } + return encodedValues; + } + + @Override + public String getParameter(String name) { + String value = super.getParameter(name); + if (value == null) { + return null; + } + return xssCleaner.clean(value); + } + + // ============================ attribute ============================ + @Override + public Object getAttribute(String name) { + Object value = super.getAttribute(name); + if (value instanceof String) { + return xssCleaner.clean((String) value); + } + return value; + } + + // ============================ header ============================ + @Override + public String getHeader(String name) { + String value = super.getHeader(name); + if (value == null) { + return null; + } + return xssCleaner.clean(value); + } + + // ============================ queryString ============================ + @Override + public String getQueryString() { + String value = super.getQueryString(); + if (value == null) { + return null; + } + return xssCleaner.clean(value); + } + +} diff --git a/tashow-framework/tashow-framework-web/src/main/java/com/tashow/cloud/web/xss/core/json/XssStringJsonDeserializer.java b/tashow-framework/tashow-framework-web/src/main/java/com/tashow/cloud/web/xss/core/json/XssStringJsonDeserializer.java new file mode 100644 index 0000000..22ba522 --- /dev/null +++ b/tashow-framework/tashow-framework-web/src/main/java/com/tashow/cloud/web/xss/core/json/XssStringJsonDeserializer.java @@ -0,0 +1,82 @@ +package com.tashow.cloud.web.xss.core.json; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonToken; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.deser.std.StringDeserializer; +import com.tashow.cloud.common.util.servlet.ServletUtils; +import com.tashow.cloud.web.xss.config.XssProperties; +import com.tashow.cloud.web.xss.core.clean.XssCleaner; +import jakarta.servlet.http.HttpServletRequest; +import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.util.PathMatcher; + +import java.io.IOException; + +/** + * XSS 过滤 jackson 反序列化器。 + * 在反序列化的过程中,会对字符串进行 XSS 过滤。 + * + * @author Hccake + */ +@Slf4j +@AllArgsConstructor +public class XssStringJsonDeserializer extends StringDeserializer { + + /** + * 属性 + */ + private final XssProperties properties; + /** + * 路径匹配器 + */ + private final PathMatcher pathMatcher; + + private final XssCleaner xssCleaner; + + @Override + public String deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { + // 1. 白名单 URL 的处理 + HttpServletRequest request = ServletUtils.getRequest(); + if (request != null) { + String uri = ServletUtils.getRequest().getRequestURI(); + if (properties.getExcludeUrls().stream().anyMatch(excludeUrl -> pathMatcher.match(excludeUrl, uri))) { + return p.getText(); + } + } + + // 2. 真正使用 xssCleaner 进行过滤 + if (p.hasToken(JsonToken.VALUE_STRING)) { + return xssCleaner.clean(p.getText()); + } + JsonToken t = p.currentToken(); + // [databind#381] + if (t == JsonToken.START_ARRAY) { + return _deserializeFromArray(p, ctxt); + } + // need to gracefully handle byte[] data, as base64 + if (t == JsonToken.VALUE_EMBEDDED_OBJECT) { + Object ob = p.getEmbeddedObject(); + if (ob == null) { + return null; + } + if (ob instanceof byte[]) { + return ctxt.getBase64Variant().encode((byte[]) ob, false); + } + // otherwise, try conversion using toString()... + return ob.toString(); + } + // 29-Jun-2020, tatu: New! "Scalar from Object" (mostly for XML) + if (t == JsonToken.START_OBJECT) { + return ctxt.extractScalarFromObject(p, this, _valueClass); + } + + if (t.isScalarValue()) { + String text = p.getValueAsString(); + return xssCleaner.clean(text); + } + return (String) ctxt.handleUnexpectedToken(_valueClass, p); + } +} + diff --git a/tashow-framework/tashow-framework-web/src/main/java/com/tashow/cloud/web/xss/package-info.java b/tashow-framework/tashow-framework-web/src/main/java/com/tashow/cloud/web/xss/package-info.java new file mode 100644 index 0000000..bdb71ae --- /dev/null +++ b/tashow-framework/tashow-framework-web/src/main/java/com/tashow/cloud/web/xss/package-info.java @@ -0,0 +1,6 @@ +/** + * 针对 XSS 的基础封装 + * + * XSS 说明:https://tech.meituan.com/2018/09/27/fe-security.html + */ +package com.tashow.cloud.web.xss; diff --git a/tashow-framework/tashow-framework-web/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/tashow-framework/tashow-framework-web/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports new file mode 100644 index 0000000..d93c2c4 --- /dev/null +++ b/tashow-framework/tashow-framework-web/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -0,0 +1,5 @@ +com.tashow.cloud.web.apilog.config.ApiLogAutoConfiguration +com.tashow.cloud.web.jackson.config.JacksonAutoConfiguration +com.tashow.cloud.web.web.config.WebAutoConfiguration +com.tashow.cloud.web.apilog.config.ApiLogRpcAutoConfiguration +com.tashow.cloud.web.banner.config.BannerAutoConfiguration diff --git a/tashow-framework/tashow-framework-web/src/main/resources/banner.txt b/tashow-framework/tashow-framework-web/src/main/resources/banner.txt new file mode 100644 index 0000000..5fd0506 --- /dev/null +++ b/tashow-framework/tashow-framework-web/src/main/resources/banner.txt @@ -0,0 +1,16 @@ +Application Version: ${tashow.info.version} +Spring Boot Version: ${spring-boot.version} + +.__ __. ______ .______ __ __ _______ +| \ | | / __ \ | _ \ | | | | / _____| +| \| | | | | | | |_) | | | | | | | __ +| . ` | | | | | | _ < | | | | | | |_ | +| |\ | | `--' | | |_) | | `--' | | |__| | +|__| \__| \______/ |______/ \______/ \______| + +███╗ ██╗ ██████╗ ██████╗ ██╗ ██╗ ██████╗ +████╗ ██║██╔═══██╗ ██╔══██╗██║ ██║██╔════╝ +██╔██╗ ██║██║ ██║ ██████╔╝██║ ██║██║ ███╗ +██║╚██╗██║██║ ██║ ██╔══██╗██║ ██║██║ ██║ +██║ ╚████║╚██████╔╝ ██████╔╝╚██████╔╝╚██████╔╝ +╚═╝ ╚═══╝ ╚═════╝ ╚═════╝ ╚═════╝ ╚═════╝ diff --git a/tashow-framework/tashow-framework-websocket/pom.xml b/tashow-framework/tashow-framework-websocket/pom.xml new file mode 100644 index 0000000..4757c29 --- /dev/null +++ b/tashow-framework/tashow-framework-websocket/pom.xml @@ -0,0 +1,62 @@ + + + + com.tashow.cloud + tashow-framework + ${revision} + + 4.0.0 + tashow-framework-websocket + jar + + ${project.artifactId} + WebSocket 框架,支持多节点的广播 + + + + + com.tashow.cloud + tashow-common + + + + + + com.tashow.cloud + tashow-framework-security + provided + + + + org.springframework.boot + spring-boot-starter-websocket + + + + + com.tashow.cloud + tashow-framework-mq + + + org.springframework.amqp + spring-rabbit + true + + + + + + com.tashow.cloud + tashow-framework-tenant + provided + + + + diff --git a/tashow-framework/tashow-framework-websocket/src/main/java/com/tashow/cloud/websocket/config/WebSocketAutoConfiguration.java b/tashow-framework/tashow-framework-websocket/src/main/java/com/tashow/cloud/websocket/config/WebSocketAutoConfiguration.java new file mode 100644 index 0000000..3180f49 --- /dev/null +++ b/tashow-framework/tashow-framework-websocket/src/main/java/com/tashow/cloud/websocket/config/WebSocketAutoConfiguration.java @@ -0,0 +1,117 @@ +package com.tashow.cloud.websocket.config; + +import com.tashow.cloud.websocket.core.handler.JsonWebSocketMessageHandler; +import com.tashow.cloud.websocket.core.listener.WebSocketMessageListener; +import com.tashow.cloud.websocket.core.security.LoginUserHandshakeInterceptor; +import com.tashow.cloud.websocket.core.security.WebSocketAuthorizeRequestsCustomizer; +import com.tashow.cloud.websocket.core.sender.local.LocalWebSocketMessageSender; +import com.tashow.cloud.websocket.core.sender.rabbitmq.RabbitMQWebSocketMessageConsumer; +import com.tashow.cloud.websocket.core.sender.rabbitmq.RabbitMQWebSocketMessageSender; +import com.tashow.cloud.websocket.core.session.WebSocketSessionHandlerDecorator; +import com.tashow.cloud.websocket.core.session.WebSocketSessionManager; +import com.tashow.cloud.websocket.core.session.WebSocketSessionManagerImpl; +import org.springframework.amqp.core.TopicExchange; +import org.springframework.amqp.rabbit.core.RabbitTemplate; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.socket.WebSocketHandler; +import org.springframework.web.socket.config.annotation.EnableWebSocket; +import org.springframework.web.socket.config.annotation.WebSocketConfigurer; +import org.springframework.web.socket.server.HandshakeInterceptor; + +import java.util.List; + +/** + * WebSocket 自动配置 + * + * @author xingyu4j + */ +@AutoConfiguration +@EnableWebSocket // 开启 websocket +@ConditionalOnProperty(prefix = "tashow.websocket", value = "enable", matchIfMissing = true) // 允许使用 yudao.websocket.enable=false 禁用 websocket +@EnableConfigurationProperties(WebSocketProperties.class) +public class WebSocketAutoConfiguration { + + @Bean + public WebSocketConfigurer webSocketConfigurer(HandshakeInterceptor[] handshakeInterceptors, + WebSocketHandler webSocketHandler, + WebSocketProperties webSocketProperties) { + return registry -> registry + // 添加 WebSocketHandler + .addHandler(webSocketHandler, webSocketProperties.getPath()) + .addInterceptors(handshakeInterceptors) + // 允许跨域,否则前端连接会直接断开 + .setAllowedOriginPatterns("*"); + } + + @Bean + public HandshakeInterceptor handshakeInterceptor() { + return new LoginUserHandshakeInterceptor(); + } + + @Bean + public WebSocketHandler webSocketHandler(WebSocketSessionManager sessionManager, + List> messageListeners) { + // 1. 创建 JsonWebSocketMessageHandler 对象,处理消息 + JsonWebSocketMessageHandler messageHandler = new JsonWebSocketMessageHandler(messageListeners); + // 2. 创建 WebSocketSessionHandlerDecorator 对象,处理连接 + return new WebSocketSessionHandlerDecorator(messageHandler, sessionManager); + } + + @Bean + public WebSocketSessionManager webSocketSessionManager() { + return new WebSocketSessionManagerImpl(); + } + + @Bean + public WebSocketAuthorizeRequestsCustomizer webSocketAuthorizeRequestsCustomizer(WebSocketProperties webSocketProperties) { + return new WebSocketAuthorizeRequestsCustomizer(webSocketProperties); + } + + // ==================== Sender 相关 ==================== + + @Configuration + @ConditionalOnProperty(prefix = "tashow.websocket", name = "sender-type", havingValue = "local") + public class LocalWebSocketMessageSenderConfiguration { + + @Bean + public LocalWebSocketMessageSender localWebSocketMessageSender(WebSocketSessionManager sessionManager) { + return new LocalWebSocketMessageSender(sessionManager); + } + + } + + @Configuration + @ConditionalOnProperty(prefix = "tashow.websocket", name = "sender-type", havingValue = "rabbitmq") + public class RabbitMQWebSocketMessageSenderConfiguration { + + @Bean + public RabbitMQWebSocketMessageSender rabbitMQWebSocketMessageSender( + WebSocketSessionManager sessionManager, RabbitTemplate rabbitTemplate, + TopicExchange websocketTopicExchange) { + return new RabbitMQWebSocketMessageSender(sessionManager, rabbitTemplate, websocketTopicExchange); + } + + @Bean + public RabbitMQWebSocketMessageConsumer rabbitMQWebSocketMessageConsumer( + RabbitMQWebSocketMessageSender rabbitMQWebSocketMessageSender) { + return new RabbitMQWebSocketMessageConsumer(rabbitMQWebSocketMessageSender); + } + + /** + * 创建 Topic Exchange + */ + @Bean + public TopicExchange websocketTopicExchange(@Value("${tashow.websocket.sender-rabbitmq.exchange}") String exchange) { + return new TopicExchange(exchange, + true, // durable: 是否持久化 + false); // exclusive: 是否排它 + } + + } + +} diff --git a/tashow-framework/tashow-framework-websocket/src/main/java/com/tashow/cloud/websocket/config/WebSocketProperties.java b/tashow-framework/tashow-framework-websocket/src/main/java/com/tashow/cloud/websocket/config/WebSocketProperties.java new file mode 100644 index 0000000..3f6ce34 --- /dev/null +++ b/tashow-framework/tashow-framework-websocket/src/main/java/com/tashow/cloud/websocket/config/WebSocketProperties.java @@ -0,0 +1,33 @@ +package com.tashow.cloud.websocket.config; + +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.validation.annotation.Validated; + +/** + * WebSocket 配置项 + * + * @author xingyu4j + */ +@ConfigurationProperties("tashow.websocket") +@Data +@Validated +public class WebSocketProperties { + + /** + * WebSocket 的连接路径 + */ + @NotEmpty(message = "WebSocket 的连接路径不能为空") + private String path = "/ws"; + + /** + * 消息发送器的类型 + * + * 可选值:local、redis、rocketmq、kafka、rabbitmq + */ + @NotNull(message = "WebSocket 的消息发送者不能为空") + private String senderType = "local"; + +} diff --git a/tashow-framework/tashow-framework-websocket/src/main/java/com/tashow/cloud/websocket/core/handler/JsonWebSocketMessageHandler.java b/tashow-framework/tashow-framework-websocket/src/main/java/com/tashow/cloud/websocket/core/handler/JsonWebSocketMessageHandler.java new file mode 100644 index 0000000..ff97818 --- /dev/null +++ b/tashow-framework/tashow-framework-websocket/src/main/java/com/tashow/cloud/websocket/core/handler/JsonWebSocketMessageHandler.java @@ -0,0 +1,83 @@ +package com.tashow.cloud.websocket.core.handler; + +import cn.hutool.core.util.StrUtil; +import cn.hutool.core.util.TypeUtil; +import com.tashow.cloud.common.util.json.JsonUtils; +import com.tashow.cloud.tenant.core.util.TenantUtils; +import com.tashow.cloud.websocket.core.listener.WebSocketMessageListener; +import com.tashow.cloud.websocket.core.message.JsonWebSocketMessage; +import com.tashow.cloud.websocket.core.util.WebSocketFrameworkUtils; +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.socket.TextMessage; +import org.springframework.web.socket.WebSocketHandler; +import org.springframework.web.socket.WebSocketSession; +import org.springframework.web.socket.handler.TextWebSocketHandler; + +import java.lang.reflect.Type; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.function.Consumer; + +/** + * JSON 格式 {@link WebSocketHandler} 实现类 + * + * 基于 {@link JsonWebSocketMessage#getType()} 消息类型,调度到对应的 {@link WebSocketMessageListener} 监听器。 + * + * @author 芋道源码 + */ +@Slf4j +public class JsonWebSocketMessageHandler extends TextWebSocketHandler { + + /** + * type 与 WebSocketMessageListener 的映射 + */ + private final Map> listeners = new HashMap<>(); + + @SuppressWarnings({"rawtypes", "unchecked"}) + public JsonWebSocketMessageHandler(List listenersList) { + listenersList.forEach((Consumer) + listener -> listeners.put(listener.getType(), listener)); + } + + @Override + protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception { + // 1.1 空消息,跳过 + if (message.getPayloadLength() == 0) { + return; + } + // 1.2 ping 心跳消息,直接返回 pong 消息。 + if (message.getPayloadLength() == 4 && Objects.equals(message.getPayload(), "ping")) { + session.sendMessage(new TextMessage("pong")); + return; + } + + // 2.1 解析消息 + try { + JsonWebSocketMessage jsonMessage = JsonUtils.parseObject(message.getPayload(), JsonWebSocketMessage.class); + if (jsonMessage == null) { + log.error("[handleTextMessage][session({}) message({}) 解析为空]", session.getId(), message.getPayload()); + return; + } + if (StrUtil.isEmpty(jsonMessage.getType())) { + log.error("[handleTextMessage][session({}) message({}) 类型为空]", session.getId(), message.getPayload()); + return; + } + // 2.2 获得对应的 WebSocketMessageListener + WebSocketMessageListener messageListener = listeners.get(jsonMessage.getType()); + if (messageListener == null) { + log.error("[handleTextMessage][session({}) message({}) 监听器为空]", session.getId(), message.getPayload()); + return; + } + // 2.3 处理消息 + Type type = TypeUtil.getTypeArgument(messageListener.getClass(), 0); + Object messageObj = JsonUtils.parseObject(jsonMessage.getContent(), type); + Long tenantId = WebSocketFrameworkUtils.getTenantId(session); + TenantUtils.execute(tenantId, () -> messageListener.onMessage(session, messageObj)); + } catch (Throwable ex) { + log.error("[handleTextMessage][session({}) message({}) 处理异常]", session.getId(), message.getPayload()); + } + } + +} diff --git a/tashow-framework/tashow-framework-websocket/src/main/java/com/tashow/cloud/websocket/core/listener/WebSocketMessageListener.java b/tashow-framework/tashow-framework-websocket/src/main/java/com/tashow/cloud/websocket/core/listener/WebSocketMessageListener.java new file mode 100644 index 0000000..b6453ba --- /dev/null +++ b/tashow-framework/tashow-framework-websocket/src/main/java/com/tashow/cloud/websocket/core/listener/WebSocketMessageListener.java @@ -0,0 +1,31 @@ +package com.tashow.cloud.websocket.core.listener; + +import com.tashow.cloud.websocket.core.message.JsonWebSocketMessage; +import org.springframework.web.socket.WebSocketSession; + +/** + * WebSocket 消息监听器接口 + * + * 目的:前端发送消息给后端后,处理对应 {@link #getType()} 类型的消息 + * + * @param 泛型,消息类型 + */ +public interface WebSocketMessageListener { + + /** + * 处理消息 + * + * @param session Session + * @param message 消息 + */ + void onMessage(WebSocketSession session, T message); + + /** + * 获得消息类型 + * + * @see JsonWebSocketMessage#getType() + * @return 消息类型 + */ + String getType(); + +} diff --git a/tashow-framework/tashow-framework-websocket/src/main/java/com/tashow/cloud/websocket/core/message/JsonWebSocketMessage.java b/tashow-framework/tashow-framework-websocket/src/main/java/com/tashow/cloud/websocket/core/message/JsonWebSocketMessage.java new file mode 100644 index 0000000..fada656 --- /dev/null +++ b/tashow-framework/tashow-framework-websocket/src/main/java/com/tashow/cloud/websocket/core/message/JsonWebSocketMessage.java @@ -0,0 +1,30 @@ +package com.tashow.cloud.websocket.core.message; + +import com.tashow.cloud.websocket.core.listener.WebSocketMessageListener; +import com.tashow.cloud.websocket.core.listener.WebSocketMessageListener; +import lombok.Data; + +import java.io.Serializable; + +/** + * JSON 格式的 WebSocket 消息帧 + * + * @author 芋道源码 + */ +@Data +public class JsonWebSocketMessage implements Serializable { + + /** + * 消息类型 + * + * 目的:用于分发到对应的 {@link WebSocketMessageListener} 实现类 + */ + private String type; + /** + * 消息内容 + * + * 要求 JSON 对象 + */ + private String content; + +} diff --git a/tashow-framework/tashow-framework-websocket/src/main/java/com/tashow/cloud/websocket/core/security/LoginUserHandshakeInterceptor.java b/tashow-framework/tashow-framework-websocket/src/main/java/com/tashow/cloud/websocket/core/security/LoginUserHandshakeInterceptor.java new file mode 100644 index 0000000..160ecda --- /dev/null +++ b/tashow-framework/tashow-framework-websocket/src/main/java/com/tashow/cloud/websocket/core/security/LoginUserHandshakeInterceptor.java @@ -0,0 +1,42 @@ +package com.tashow.cloud.websocket.core.security; + +import com.tashow.cloud.security.security.core.LoginUser; +import com.tashow.cloud.security.security.core.filter.TokenAuthenticationFilter; +import com.tashow.cloud.security.security.core.util.SecurityFrameworkUtils; +import com.tashow.cloud.websocket.core.util.WebSocketFrameworkUtils; +import org.springframework.http.server.ServerHttpRequest; +import org.springframework.http.server.ServerHttpResponse; +import org.springframework.web.socket.WebSocketHandler; +import org.springframework.web.socket.WebSocketSession; +import org.springframework.web.socket.server.HandshakeInterceptor; + +import java.util.Map; + +/** + * 登录用户的 {@link HandshakeInterceptor} 实现类 + * + * 流程如下: + * 1. 前端连接 websocket 时,会通过拼接 ?token={token} 到 ws:// 连接后,这样它可以被 {@link TokenAuthenticationFilter} 所认证通过 + * 2. {@link LoginUserHandshakeInterceptor} 负责把 {@link LoginUser} 添加到 {@link WebSocketSession} 中 + * + * @author 芋道源码 + */ +public class LoginUserHandshakeInterceptor implements HandshakeInterceptor { + + @Override + public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, + WebSocketHandler wsHandler, Map attributes) { + LoginUser loginUser = SecurityFrameworkUtils.getLoginUser(); + if (loginUser != null) { + WebSocketFrameworkUtils.setLoginUser(loginUser, attributes); + } + return true; + } + + @Override + public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response, + WebSocketHandler wsHandler, Exception exception) { + // do nothing + } + +} diff --git a/tashow-framework/tashow-framework-websocket/src/main/java/com/tashow/cloud/websocket/core/security/WebSocketAuthorizeRequestsCustomizer.java b/tashow-framework/tashow-framework-websocket/src/main/java/com/tashow/cloud/websocket/core/security/WebSocketAuthorizeRequestsCustomizer.java new file mode 100644 index 0000000..d74170a --- /dev/null +++ b/tashow-framework/tashow-framework-websocket/src/main/java/com/tashow/cloud/websocket/core/security/WebSocketAuthorizeRequestsCustomizer.java @@ -0,0 +1,24 @@ +package com.tashow.cloud.websocket.core.security; + +import com.tashow.cloud.security.security.config.AuthorizeRequestsCustomizer; +import com.tashow.cloud.websocket.config.WebSocketProperties; +import lombok.RequiredArgsConstructor; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configurers.AuthorizeHttpRequestsConfigurer; + +/** + * WebSocket 的权限自定义 + * + * @author 芋道源码 + */ +@RequiredArgsConstructor +public class WebSocketAuthorizeRequestsCustomizer extends AuthorizeRequestsCustomizer { + + private final WebSocketProperties webSocketProperties; + + @Override + public void customize(AuthorizeHttpRequestsConfigurer.AuthorizationManagerRequestMatcherRegistry registry) { + registry.requestMatchers(webSocketProperties.getPath()).permitAll(); + } + +} diff --git a/tashow-framework/tashow-framework-websocket/src/main/java/com/tashow/cloud/websocket/core/sender/AbstractWebSocketMessageSender.java b/tashow-framework/tashow-framework-websocket/src/main/java/com/tashow/cloud/websocket/core/sender/AbstractWebSocketMessageSender.java new file mode 100644 index 0000000..427ea13 --- /dev/null +++ b/tashow-framework/tashow-framework-websocket/src/main/java/com/tashow/cloud/websocket/core/sender/AbstractWebSocketMessageSender.java @@ -0,0 +1,106 @@ +package com.tashow.cloud.websocket.core.sender; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.util.StrUtil; +import com.tashow.cloud.common.util.json.JsonUtils; +import com.tashow.cloud.websocket.core.message.JsonWebSocketMessage; +import com.tashow.cloud.websocket.core.session.WebSocketSessionManager; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.socket.TextMessage; +import org.springframework.web.socket.WebSocketSession; + +import java.io.IOException; +import java.util.Collection; +import java.util.Collections; +import java.util.List; + +/** + * WebSocketMessageSender 实现类 + * + * @author 芋道源码 + */ +@Slf4j +@RequiredArgsConstructor +public abstract class AbstractWebSocketMessageSender implements WebSocketMessageSender { + + private final WebSocketSessionManager sessionManager; + + @Override + public void send(Integer userType, Long userId, String messageType, String messageContent) { + send(null, userType, userId, messageType, messageContent); + } + + @Override + public void send(Integer userType, String messageType, String messageContent) { + send(null, userType, null, messageType, messageContent); + } + + @Override + public void send(String sessionId, String messageType, String messageContent) { + send(sessionId, null, null, messageType, messageContent); + } + + /** + * 发送消息 + * + * @param sessionId Session 编号 + * @param userType 用户类型 + * @param userId 用户编号 + * @param messageType 消息类型 + * @param messageContent 消息内容 + */ + public void send(String sessionId, Integer userType, Long userId, String messageType, String messageContent) { + // 1. 获得 Session 列表 + List sessions = Collections.emptyList(); + if (StrUtil.isNotEmpty(sessionId)) { + WebSocketSession session = sessionManager.getSession(sessionId); + if (session != null) { + sessions = Collections.singletonList(session); + } + } else if (userType != null && userId != null) { + sessions = (List) sessionManager.getSessionList(userType, userId); + } else if (userType != null) { + sessions = (List) sessionManager.getSessionList(userType); + } + if (CollUtil.isEmpty(sessions)) { + if (log.isDebugEnabled()) { + log.debug("[send][sessionId({}) userType({}) userId({}) messageType({}) messageContent({}) 未匹配到会话]", + sessionId, userType, userId, messageType, messageContent); + } + } + // 2. 执行发送 + doSend(sessions, messageType, messageContent); + } + + /** + * 发送消息的具体实现 + * + * @param sessions Session 列表 + * @param messageType 消息类型 + * @param messageContent 消息内容 + */ + public void doSend(Collection sessions, String messageType, String messageContent) { + JsonWebSocketMessage message = new JsonWebSocketMessage().setType(messageType).setContent(messageContent); + String payload = JsonUtils.toJsonString(message); // 关键,使用 JSON 序列化 + sessions.forEach(session -> { + // 1. 各种校验,保证 Session 可以被发送 + if (session == null) { + log.error("[doSend][session 为空, message({})]", message); + return; + } + if (!session.isOpen()) { + log.error("[doSend][session({}) 已关闭, message({})]", session.getId(), message); + return; + } + // 2. 执行发送 + try { + session.sendMessage(new TextMessage(payload)); + log.info("[doSend][session({}) 发送消息成功,message({})]", session.getId(), message); + } catch (IOException ex) { + log.error("[doSend][session({}) 发送消息失败,message({})]", session.getId(), message, ex); + } + }); + } + +} diff --git a/tashow-framework/tashow-framework-websocket/src/main/java/com/tashow/cloud/websocket/core/sender/WebSocketMessageSender.java b/tashow-framework/tashow-framework-websocket/src/main/java/com/tashow/cloud/websocket/core/sender/WebSocketMessageSender.java new file mode 100644 index 0000000..b5a4a98 --- /dev/null +++ b/tashow-framework/tashow-framework-websocket/src/main/java/com/tashow/cloud/websocket/core/sender/WebSocketMessageSender.java @@ -0,0 +1,52 @@ +package com.tashow.cloud.websocket.core.sender; + +import com.tashow.cloud.common.util.json.JsonUtils; + +/** + * WebSocket 消息的发送器接口 + * + * @author 芋道源码 + */ +public interface WebSocketMessageSender { + + /** + * 发送消息给指定用户 + * + * @param userType 用户类型 + * @param userId 用户编号 + * @param messageType 消息类型 + * @param messageContent 消息内容,JSON 格式 + */ + void send(Integer userType, Long userId, String messageType, String messageContent); + + /** + * 发送消息给指定用户类型 + * + * @param userType 用户类型 + * @param messageType 消息类型 + * @param messageContent 消息内容,JSON 格式 + */ + void send(Integer userType, String messageType, String messageContent); + + /** + * 发送消息给指定 Session + * + * @param sessionId Session 编号 + * @param messageType 消息类型 + * @param messageContent 消息内容,JSON 格式 + */ + void send(String sessionId, String messageType, String messageContent); + + default void sendObject(Integer userType, Long userId, String messageType, Object messageContent) { + send(userType, userId, messageType, JsonUtils.toJsonString(messageContent)); + } + + default void sendObject(Integer userType, String messageType, Object messageContent) { + send(userType, messageType, JsonUtils.toJsonString(messageContent)); + } + + default void sendObject(String sessionId, String messageType, Object messageContent) { + send(sessionId, messageType, JsonUtils.toJsonString(messageContent)); + } + +} diff --git a/tashow-framework/tashow-framework-websocket/src/main/java/com/tashow/cloud/websocket/core/sender/local/LocalWebSocketMessageSender.java b/tashow-framework/tashow-framework-websocket/src/main/java/com/tashow/cloud/websocket/core/sender/local/LocalWebSocketMessageSender.java new file mode 100644 index 0000000..44d839e --- /dev/null +++ b/tashow-framework/tashow-framework-websocket/src/main/java/com/tashow/cloud/websocket/core/sender/local/LocalWebSocketMessageSender.java @@ -0,0 +1,23 @@ +package com.tashow.cloud.websocket.core.sender.local; + +import com.tashow.cloud.websocket.core.sender.AbstractWebSocketMessageSender; +import com.tashow.cloud.websocket.core.sender.WebSocketMessageSender; +import com.tashow.cloud.websocket.core.session.WebSocketSessionManager; +import com.tashow.cloud.websocket.core.sender.AbstractWebSocketMessageSender; +import com.tashow.cloud.websocket.core.sender.WebSocketMessageSender; +import com.tashow.cloud.websocket.core.session.WebSocketSessionManager; + +/** + * 本地的 {@link WebSocketMessageSender} 实现类 + * + * 注意:仅仅适合单机场景!!! + * + * @author 芋道源码 + */ +public class LocalWebSocketMessageSender extends AbstractWebSocketMessageSender { + + public LocalWebSocketMessageSender(WebSocketSessionManager sessionManager) { + super(sessionManager); + } + +} diff --git a/tashow-framework/tashow-framework-websocket/src/main/java/com/tashow/cloud/websocket/core/sender/rabbitmq/RabbitMQWebSocketMessage.java b/tashow-framework/tashow-framework-websocket/src/main/java/com/tashow/cloud/websocket/core/sender/rabbitmq/RabbitMQWebSocketMessage.java new file mode 100644 index 0000000..b1ee0cf --- /dev/null +++ b/tashow-framework/tashow-framework-websocket/src/main/java/com/tashow/cloud/websocket/core/sender/rabbitmq/RabbitMQWebSocketMessage.java @@ -0,0 +1,37 @@ +package com.tashow.cloud.websocket.core.sender.rabbitmq; + +import lombok.Data; + +import java.io.Serializable; + +/** + * RabbitMQ 广播 WebSocket 的消息 + * + * @author 芋道源码 + */ +@Data +public class RabbitMQWebSocketMessage implements Serializable { + + /** + * Session 编号 + */ + private String sessionId; + /** + * 用户类型 + */ + private Integer userType; + /** + * 用户编号 + */ + private Long userId; + + /** + * 消息类型 + */ + private String messageType; + /** + * 消息内容 + */ + private String messageContent; + +} diff --git a/tashow-framework/tashow-framework-websocket/src/main/java/com/tashow/cloud/websocket/core/sender/rabbitmq/RabbitMQWebSocketMessageConsumer.java b/tashow-framework/tashow-framework-websocket/src/main/java/com/tashow/cloud/websocket/core/sender/rabbitmq/RabbitMQWebSocketMessageConsumer.java new file mode 100644 index 0000000..3f30c80 --- /dev/null +++ b/tashow-framework/tashow-framework-websocket/src/main/java/com/tashow/cloud/websocket/core/sender/rabbitmq/RabbitMQWebSocketMessageConsumer.java @@ -0,0 +1,39 @@ +package com.tashow.cloud.websocket.core.sender.rabbitmq; + +import lombok.RequiredArgsConstructor; +import org.springframework.amqp.core.ExchangeTypes; +import org.springframework.amqp.rabbit.annotation.*; + +/** + * {@link RabbitMQWebSocketMessage} 广播消息的消费者,真正把消息发送出去 + * + * @author 芋道源码 + */ +@RabbitListener( + bindings = @QueueBinding( + value = @Queue( + // 在 Queue 的名字上,使用 UUID 生成其后缀。这样,启动的 Consumer 的 Queue 不同,以达到广播消费的目的 + name = "${tashow.websocket.sender-rabbitmq.queue}" + "-" + "#{T(java.util.UUID).randomUUID()}", + // Consumer 关闭时,该队列就可以被自动删除了 + autoDelete = "true" + ), + exchange = @Exchange( + name = "${tashow.websocket.sender-rabbitmq.exchange}", + type = ExchangeTypes.TOPIC, + declare = "false" + ) + ) +) +@RequiredArgsConstructor +public class RabbitMQWebSocketMessageConsumer { + + private final RabbitMQWebSocketMessageSender rabbitMQWebSocketMessageSender; + + @RabbitHandler + public void onMessage(RabbitMQWebSocketMessage message) { + rabbitMQWebSocketMessageSender.send(message.getSessionId(), + message.getUserType(), message.getUserId(), + message.getMessageType(), message.getMessageContent()); + } + +} diff --git a/tashow-framework/tashow-framework-websocket/src/main/java/com/tashow/cloud/websocket/core/sender/rabbitmq/RabbitMQWebSocketMessageSender.java b/tashow-framework/tashow-framework-websocket/src/main/java/com/tashow/cloud/websocket/core/sender/rabbitmq/RabbitMQWebSocketMessageSender.java new file mode 100644 index 0000000..fe76103 --- /dev/null +++ b/tashow-framework/tashow-framework-websocket/src/main/java/com/tashow/cloud/websocket/core/sender/rabbitmq/RabbitMQWebSocketMessageSender.java @@ -0,0 +1,65 @@ +package com.tashow.cloud.websocket.core.sender.rabbitmq; + +import com.tashow.cloud.websocket.core.sender.AbstractWebSocketMessageSender; +import com.tashow.cloud.websocket.core.sender.WebSocketMessageSender; +import com.tashow.cloud.websocket.core.session.WebSocketSessionManager; +import com.tashow.cloud.websocket.core.sender.AbstractWebSocketMessageSender; +import com.tashow.cloud.websocket.core.sender.WebSocketMessageSender; +import com.tashow.cloud.websocket.core.session.WebSocketSessionManager; +import lombok.extern.slf4j.Slf4j; +import org.springframework.amqp.core.TopicExchange; +import org.springframework.amqp.rabbit.core.RabbitTemplate; + +/** + * 基于 RabbitMQ 的 {@link WebSocketMessageSender} 实现类 + * + * @author 芋道源码 + */ +@Slf4j +public class RabbitMQWebSocketMessageSender extends AbstractWebSocketMessageSender { + + private final RabbitTemplate rabbitTemplate; + + private final TopicExchange topicExchange; + + public RabbitMQWebSocketMessageSender(WebSocketSessionManager sessionManager, + RabbitTemplate rabbitTemplate, + TopicExchange topicExchange) { + super(sessionManager); + this.rabbitTemplate = rabbitTemplate; + this.topicExchange = topicExchange; + } + + @Override + public void send(Integer userType, Long userId, String messageType, String messageContent) { + sendRabbitMQMessage(null, userId, userType, messageType, messageContent); + } + + @Override + public void send(Integer userType, String messageType, String messageContent) { + sendRabbitMQMessage(null, null, userType, messageType, messageContent); + } + + @Override + public void send(String sessionId, String messageType, String messageContent) { + sendRabbitMQMessage(sessionId, null, null, messageType, messageContent); + } + + /** + * 通过 RabbitMQ 广播消息 + * + * @param sessionId Session 编号 + * @param userId 用户编号 + * @param userType 用户类型 + * @param messageType 消息类型 + * @param messageContent 消息内容 + */ + private void sendRabbitMQMessage(String sessionId, Long userId, Integer userType, + String messageType, String messageContent) { + RabbitMQWebSocketMessage mqMessage = new RabbitMQWebSocketMessage() + .setSessionId(sessionId).setUserId(userId).setUserType(userType) + .setMessageType(messageType).setMessageContent(messageContent); + rabbitTemplate.convertAndSend(topicExchange.getName(), null, mqMessage); + } + +} diff --git a/tashow-framework/tashow-framework-websocket/src/main/java/com/tashow/cloud/websocket/core/session/WebSocketSessionHandlerDecorator.java b/tashow-framework/tashow-framework-websocket/src/main/java/com/tashow/cloud/websocket/core/session/WebSocketSessionHandlerDecorator.java new file mode 100644 index 0000000..1aa2e8b --- /dev/null +++ b/tashow-framework/tashow-framework-websocket/src/main/java/com/tashow/cloud/websocket/core/session/WebSocketSessionHandlerDecorator.java @@ -0,0 +1,49 @@ +package com.tashow.cloud.websocket.core.session; + +import org.springframework.web.socket.CloseStatus; +import org.springframework.web.socket.WebSocketHandler; +import org.springframework.web.socket.WebSocketSession; +import org.springframework.web.socket.handler.ConcurrentWebSocketSessionDecorator; +import org.springframework.web.socket.handler.WebSocketHandlerDecorator; + +/** + * {@link WebSocketHandler} 的装饰类,实现了以下功能: + * + * 1. {@link WebSocketSession} 连接或关闭时,使用 {@link #sessionManager} 进行管理 + * 2. 封装 {@link WebSocketSession} 支持并发操作 + * + * @author 芋道源码 + */ +public class WebSocketSessionHandlerDecorator extends WebSocketHandlerDecorator { + + /** + * 发送时间的限制,单位:毫秒 + */ + private static final Integer SEND_TIME_LIMIT = 1000 * 5; + /** + * 发送消息缓冲上线,单位:bytes + */ + private static final Integer BUFFER_SIZE_LIMIT = 1024 * 100; + + private final WebSocketSessionManager sessionManager; + + public WebSocketSessionHandlerDecorator(WebSocketHandler delegate, + WebSocketSessionManager sessionManager) { + super(delegate); + this.sessionManager = sessionManager; + } + + @Override + public void afterConnectionEstablished(WebSocketSession session) { + // 实现 session 支持并发,可参考 https://blog.csdn.net/abu935009066/article/details/131218149 + session = new ConcurrentWebSocketSessionDecorator(session, SEND_TIME_LIMIT, BUFFER_SIZE_LIMIT); + // 添加到 WebSocketSessionManager 中 + sessionManager.addSession(session); + } + + @Override + public void afterConnectionClosed(WebSocketSession session, CloseStatus closeStatus) { + sessionManager.removeSession(session); + } + +} diff --git a/tashow-framework/tashow-framework-websocket/src/main/java/com/tashow/cloud/websocket/core/session/WebSocketSessionManager.java b/tashow-framework/tashow-framework-websocket/src/main/java/com/tashow/cloud/websocket/core/session/WebSocketSessionManager.java new file mode 100644 index 0000000..4d14267 --- /dev/null +++ b/tashow-framework/tashow-framework-websocket/src/main/java/com/tashow/cloud/websocket/core/session/WebSocketSessionManager.java @@ -0,0 +1,53 @@ +package com.tashow.cloud.websocket.core.session; + +import org.springframework.web.socket.WebSocketSession; + +import java.util.Collection; + +/** + * {@link WebSocketSession} 管理器的接口 + * + * @author 芋道源码 + */ +public interface WebSocketSessionManager { + + /** + * 添加 Session + * + * @param session Session + */ + void addSession(WebSocketSession session); + + /** + * 移除 Session + * + * @param session Session + */ + void removeSession(WebSocketSession session); + + /** + * 获得指定编号的 Session + * + * @param id Session 编号 + * @return Session + */ + WebSocketSession getSession(String id); + + /** + * 获得指定用户类型的 Session 列表 + * + * @param userType 用户类型 + * @return Session 列表 + */ + Collection getSessionList(Integer userType); + + /** + * 获得指定用户编号的 Session 列表 + * + * @param userType 用户类型 + * @param userId 用户编号 + * @return Session 列表 + */ + Collection getSessionList(Integer userType, Long userId); + +} diff --git a/tashow-framework/tashow-framework-websocket/src/main/java/com/tashow/cloud/websocket/core/session/WebSocketSessionManagerImpl.java b/tashow-framework/tashow-framework-websocket/src/main/java/com/tashow/cloud/websocket/core/session/WebSocketSessionManagerImpl.java new file mode 100644 index 0000000..532f920 --- /dev/null +++ b/tashow-framework/tashow-framework-websocket/src/main/java/com/tashow/cloud/websocket/core/session/WebSocketSessionManagerImpl.java @@ -0,0 +1,125 @@ +package com.tashow.cloud.websocket.core.session; + +import cn.hutool.core.collection.CollUtil; +import com.tashow.cloud.security.security.core.LoginUser; +import com.tashow.cloud.tenant.core.context.TenantContextHolder; +import com.tashow.cloud.websocket.core.util.WebSocketFrameworkUtils; +import org.springframework.web.socket.WebSocketSession; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.LinkedList; +import java.util.List; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.CopyOnWriteArrayList; + +/** + * 默认的 {@link WebSocketSessionManager} 实现类 + * + * @author 芋道源码 + */ +public class WebSocketSessionManagerImpl implements WebSocketSessionManager { + + /** + * id 与 WebSocketSession 映射 + * + * key:Session 编号 + */ + private final ConcurrentMap idSessions = new ConcurrentHashMap<>(); + + /** + * user 与 WebSocketSession 映射 + * + * key1:用户类型 + * key2:用户编号 + */ + private final ConcurrentMap>> userSessions + = new ConcurrentHashMap<>(); + + @Override + public void addSession(WebSocketSession session) { + // 添加到 idSessions 中 + idSessions.put(session.getId(), session); + // 添加到 userSessions 中 + LoginUser user = WebSocketFrameworkUtils.getLoginUser(session); + if (user == null) { + return; + } + ConcurrentMap> userSessionsMap = userSessions.get(user.getUserType()); + if (userSessionsMap == null) { + userSessionsMap = new ConcurrentHashMap<>(); + if (userSessions.putIfAbsent(user.getUserType(), userSessionsMap) != null) { + userSessionsMap = userSessions.get(user.getUserType()); + } + } + CopyOnWriteArrayList sessions = userSessionsMap.get(user.getId()); + if (sessions == null) { + sessions = new CopyOnWriteArrayList<>(); + if (userSessionsMap.putIfAbsent(user.getId(), sessions) != null) { + sessions = userSessionsMap.get(user.getId()); + } + } + sessions.add(session); + } + + @Override + public void removeSession(WebSocketSession session) { + // 移除从 idSessions 中 + idSessions.remove(session.getId()); + // 移除从 idSessions 中 + LoginUser user = WebSocketFrameworkUtils.getLoginUser(session); + if (user == null) { + return; + } + ConcurrentMap> userSessionsMap = userSessions.get(user.getUserType()); + if (userSessionsMap == null) { + return; + } + CopyOnWriteArrayList sessions = userSessionsMap.get(user.getId()); + sessions.removeIf(session0 -> session0.getId().equals(session.getId())); + if (CollUtil.isEmpty(sessions)) { + userSessionsMap.remove(user.getId(), sessions); + } + } + + @Override + public WebSocketSession getSession(String id) { + return idSessions.get(id); + } + + @Override + public Collection getSessionList(Integer userType) { + ConcurrentMap> userSessionsMap = userSessions.get(userType); + if (CollUtil.isEmpty(userSessionsMap)) { + return new ArrayList<>(); + } + LinkedList result = new LinkedList<>(); // 避免扩容 + Long contextTenantId = TenantContextHolder.getTenantId(); + for (List sessions : userSessionsMap.values()) { + if (CollUtil.isEmpty(sessions)) { + continue; + } + // 特殊:如果租户不匹配,则直接排除 + if (contextTenantId != null) { + Long userTenantId = WebSocketFrameworkUtils.getTenantId(sessions.get(0)); + if (!contextTenantId.equals(userTenantId)) { + continue; + } + } + result.addAll(sessions); + } + return result; + } + + @Override + public Collection getSessionList(Integer userType, Long userId) { + ConcurrentMap> userSessionsMap = userSessions.get(userType); + if (CollUtil.isEmpty(userSessionsMap)) { + return new ArrayList<>(); + } + CopyOnWriteArrayList sessions = userSessionsMap.get(userId); + return CollUtil.isNotEmpty(sessions) ? new ArrayList<>(sessions) : new ArrayList<>(); + } + +} diff --git a/tashow-framework/tashow-framework-websocket/src/main/java/com/tashow/cloud/websocket/core/util/WebSocketFrameworkUtils.java b/tashow-framework/tashow-framework-websocket/src/main/java/com/tashow/cloud/websocket/core/util/WebSocketFrameworkUtils.java new file mode 100644 index 0000000..adab697 --- /dev/null +++ b/tashow-framework/tashow-framework-websocket/src/main/java/com/tashow/cloud/websocket/core/util/WebSocketFrameworkUtils.java @@ -0,0 +1,67 @@ +package com.tashow.cloud.websocket.core.util; + +import com.tashow.cloud.security.security.core.LoginUser; +import org.springframework.web.socket.WebSocketSession; + +import java.util.Map; + +/** + * 专属于 web 包的工具类 + * + * @author 芋道源码 + */ +public class WebSocketFrameworkUtils { + + public static final String ATTRIBUTE_LOGIN_USER = "LOGIN_USER"; + + /** + * 设置当前用户 + * + * @param loginUser 登录用户 + * @param attributes Session + */ + public static void setLoginUser(LoginUser loginUser, Map attributes) { + attributes.put(ATTRIBUTE_LOGIN_USER, loginUser); + } + + /** + * 获取当前用户 + * + * @return 当前用户 + */ + public static LoginUser getLoginUser(WebSocketSession session) { + return (LoginUser) session.getAttributes().get(ATTRIBUTE_LOGIN_USER); + } + + /** + * 获得当前用户的编号 + * + * @return 用户编号 + */ + public static Long getLoginUserId(WebSocketSession session) { + LoginUser loginUser = getLoginUser(session); + return loginUser != null ? loginUser.getId() : null; + } + + /** + * 获得当前用户的类型 + * + * @return 用户编号 + */ + public static Integer getLoginUserType(WebSocketSession session) { + LoginUser loginUser = getLoginUser(session); + return loginUser != null ? loginUser.getUserType() : null; + } + + /** + * 获得当前用户的租户编号 + * + * @param session Session + * @return 租户编号 + */ + public static Long getTenantId(WebSocketSession session) { + LoginUser loginUser = getLoginUser(session); + return loginUser != null ? loginUser.getTenantId() : null; + } + +} diff --git a/tashow-framework/tashow-framework-websocket/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/tashow-framework/tashow-framework-websocket/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports new file mode 100644 index 0000000..ce42960 --- /dev/null +++ b/tashow-framework/tashow-framework-websocket/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -0,0 +1 @@ +com.tashow.cloud.websocket.config.WebSocketAutoConfiguration diff --git a/tashow-gateway/Dockerfile b/tashow-gateway/Dockerfile new file mode 100644 index 0000000..46037b7 --- /dev/null +++ b/tashow-gateway/Dockerfile @@ -0,0 +1,19 @@ +## AdoptOpenJDK 停止发布 OpenJDK 二进制,而 Eclipse Temurin 是它的延伸,提供更好的稳定性 +## 感谢复旦核博士的建议!灰子哥,牛皮! +FROM eclipse-temurin:21-jre + +## 创建目录,并使用它作为工作目录 +RUN mkdir -p /yudao-gateway +WORKDIR /yudao-gateway +## 将后端项目的 Jar 文件,复制到镜像中 +COPY ./target/yudao-gateway.jar app.jar + +## 设置 TZ 时区 +## 设置 JAVA_OPTS 环境变量,可通过 docker run -e "JAVA_OPTS=" 进行覆盖 +ENV TZ=Asia/Shanghai JAVA_OPTS="-Xms512m -Xmx512m" + +## 暴露后端项目的 48080 端口 +EXPOSE 48080 + +## 启动后端项目 +CMD java ${JAVA_OPTS} -Djava.security.egd=file:/dev/./urandom -jar app.jar diff --git a/tashow-gateway/pom.xml b/tashow-gateway/pom.xml new file mode 100644 index 0000000..b7206a0 --- /dev/null +++ b/tashow-gateway/pom.xml @@ -0,0 +1,85 @@ + + + + com.tashow.cloud + tashow-platform + ${revision} + + 4.0.0 + tashow-gateway + jar + + ${project.artifactId} + API 服务网关,基于 Spring Cloud Gateway 实现 + + + + + com.tashow.cloud + tashow-system-api + ${revision} + + + + + org.springframework.cloud + spring-cloud-starter-gateway + + + + + org.springframework.cloud + spring-cloud-starter-loadbalancer + + + + + com.alibaba.cloud + spring-cloud-starter-alibaba-nacos-discovery + + + + + com.alibaba.cloud + spring-cloud-starter-alibaba-nacos-config + + + + + com.tashow.cloud + tashow-framework-monitor + + + + + com.google.guava + guava + + + + + + + + + ${project.artifactId} + + + + org.springframework.boot + spring-boot-maven-plugin + ${spring.boot.version} + + + + repackage + + + + + + + + diff --git a/tashow-gateway/src/main/java/com/tashow/cloud/gateway/GatewayServerApplication.java b/tashow-gateway/src/main/java/com/tashow/cloud/gateway/GatewayServerApplication.java new file mode 100644 index 0000000..2e1e588 --- /dev/null +++ b/tashow-gateway/src/main/java/com/tashow/cloud/gateway/GatewayServerApplication.java @@ -0,0 +1,14 @@ +package com.tashow.cloud.gateway; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class GatewayServerApplication { + + public static void main(String[] args) { + // 启动 Spring Boot 应用 + SpringApplication.run(GatewayServerApplication.class, args); + System.out.println("网关启动成功"); + } + +} diff --git a/tashow-gateway/src/main/java/com/tashow/cloud/gateway/filter/cors/CorsFilter.java b/tashow-gateway/src/main/java/com/tashow/cloud/gateway/filter/cors/CorsFilter.java new file mode 100644 index 0000000..c83e19f --- /dev/null +++ b/tashow-gateway/src/main/java/com/tashow/cloud/gateway/filter/cors/CorsFilter.java @@ -0,0 +1,48 @@ +package com.tashow.cloud.gateway.filter.cors; + +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.http.HttpStatus; +import org.springframework.http.server.reactive.ServerHttpRequest; +import org.springframework.http.server.reactive.ServerHttpResponse; +import org.springframework.stereotype.Component; +import org.springframework.web.cors.reactive.CorsUtils; +import org.springframework.web.server.ServerWebExchange; +import org.springframework.web.server.WebFilter; +import org.springframework.web.server.WebFilterChain; +import reactor.core.publisher.Mono; + +/** + * 跨域 Filter + * + * @author 芋道源码 + */ +@Component +public class CorsFilter implements WebFilter { + + private static final String ALL = "*"; + private static final String MAX_AGE = "3600L"; + + @Override + public Mono filter(ServerWebExchange exchange, WebFilterChain chain) { + // 非跨域请求,直接放行 + ServerHttpRequest request = exchange.getRequest(); + if (!CorsUtils.isCorsRequest(request)) { + return chain.filter(exchange); + } + + // 设置跨域响应头 + ServerHttpResponse response = exchange.getResponse(); + HttpHeaders headers = response.getHeaders(); + headers.add("Access-Control-Allow-Origin", ALL); + headers.add("Access-Control-Allow-Methods", ALL); + headers.add("Access-Control-Allow-Headers", ALL); + headers.add("Access-Control-Max-Age", MAX_AGE); + if (request.getMethod() == HttpMethod.OPTIONS) { + response.setStatusCode(HttpStatus.OK); + return Mono.empty(); + } + return chain.filter(exchange); + } + +} diff --git a/tashow-gateway/src/main/java/com/tashow/cloud/gateway/filter/cors/CorsResponseHeaderFilter.java b/tashow-gateway/src/main/java/com/tashow/cloud/gateway/filter/cors/CorsResponseHeaderFilter.java new file mode 100644 index 0000000..31519a2 --- /dev/null +++ b/tashow-gateway/src/main/java/com/tashow/cloud/gateway/filter/cors/CorsResponseHeaderFilter.java @@ -0,0 +1,45 @@ +package com.tashow.cloud.gateway.filter.cors; + +import org.springframework.cloud.gateway.filter.GatewayFilterChain; +import org.springframework.cloud.gateway.filter.GlobalFilter; +import org.springframework.cloud.gateway.filter.NettyWriteResponseFilter; +import org.springframework.core.Ordered; +import org.springframework.http.HttpHeaders; +import org.springframework.stereotype.Component; +import org.springframework.web.server.ServerWebExchange; +import reactor.core.publisher.Mono; + +import java.util.ArrayList; + +/** + * 解决 Spring Cloud Gateway 2.x 跨域时,出现重复 Origin 的 BUG + * + * 参考文档: + * + * @author 芋道源码 + */ +@Component +public class CorsResponseHeaderFilter implements GlobalFilter, Ordered { + + @Override + public int getOrder() { + // 指定此过滤器位于 NettyWriteResponseFilter 之后 + // 即待处理完响应体后接着处理响应头 + return NettyWriteResponseFilter.WRITE_RESPONSE_FILTER_ORDER + 1; + } + + @Override + public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) { + return chain.filter(exchange).then(Mono.defer(() -> { + exchange.getResponse().getHeaders().entrySet().stream() + .filter(kv -> (kv.getValue() != null && kv.getValue().size() > 1)) + .filter(kv -> (kv.getKey().equals(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN) + || kv.getKey().equals(HttpHeaders.ACCESS_CONTROL_ALLOW_CREDENTIALS))) + .forEach(kv -> kv.setValue(new ArrayList() {{ + add(kv.getValue().get(0)); + }})); + return chain.filter(exchange); + })); + } + +} diff --git a/tashow-gateway/src/main/java/com/tashow/cloud/gateway/filter/grey/GrayLoadBalancer.java b/tashow-gateway/src/main/java/com/tashow/cloud/gateway/filter/grey/GrayLoadBalancer.java new file mode 100644 index 0000000..71fc5fc --- /dev/null +++ b/tashow-gateway/src/main/java/com/tashow/cloud/gateway/filter/grey/GrayLoadBalancer.java @@ -0,0 +1,111 @@ +package com.tashow.cloud.gateway.filter.grey; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.util.StrUtil; +import com.tashow.cloud.common.util.collection.CollectionUtils; +import com.tashow.cloud.gateway.util.EnvUtils; +import com.alibaba.cloud.nacos.balancer.NacosBalancer; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.ObjectProvider; +import org.springframework.cloud.client.ServiceInstance; +import org.springframework.cloud.client.loadbalancer.*; +import org.springframework.cloud.loadbalancer.core.NoopServiceInstanceListSupplier; +import org.springframework.cloud.loadbalancer.core.ReactorServiceInstanceLoadBalancer; +import org.springframework.cloud.loadbalancer.core.ServiceInstanceListSupplier; +import org.springframework.http.HttpHeaders; +import reactor.core.publisher.Mono; + +import java.util.List; + +/** + * 灰度 {@link GrayLoadBalancer} 实现类 + * + * 根据请求的 header[version] 匹配,筛选满足 metadata[version] 相等的服务实例列表,然后随机 + 权重进行选择一个 + * 1. 假如请求的 header[version] 为空,则不进行筛选,所有服务实例都进行选择 + * 2. 如果 metadata[version] 都不相等,则不进行筛选,所有服务实例都进行选择 + * + * 注意,考虑到实现的简易,它的权重是使用 Nacos 的 nacos.weight,所以随机 + 权重也是基于 {@link NacosBalancer} 筛选。 + * 也就是说,如果你不使用 Nacos 作为注册中心,需要微调一下筛选的实现逻辑 + * + * @author 芋道源码 + */ +@RequiredArgsConstructor +@Slf4j +public class GrayLoadBalancer implements ReactorServiceInstanceLoadBalancer { + + private static final String VERSION = "version"; + + /** + * 用于获取 serviceId 对应的服务实例的列表 + */ + private final ObjectProvider serviceInstanceListSupplierProvider; + /** + * 需要获取的服务实例名 + * + * 暂时用于打印 logger 日志 + */ + private final String serviceId; + + @Override + public Mono> choose(Request request) { + // 获得 HttpHeaders 属性,实现从 header 中获取 version + HttpHeaders headers = ((RequestDataContext) request.getContext()).getClientRequest().getHeaders(); + // 选择实例 + ServiceInstanceListSupplier supplier = serviceInstanceListSupplierProvider.getIfAvailable(NoopServiceInstanceListSupplier::new); + return supplier.get(request).next().map(list -> getInstanceResponse(list, headers)); + } + + private Response getInstanceResponse(List instances, HttpHeaders headers) { + // 如果服务实例为空,则直接返回 + if (CollUtil.isEmpty(instances)) { + log.warn("[getInstanceResponse][serviceId({}) 服务实例列表为空]", serviceId); + return new EmptyResponse(); + } + + // 筛选满足 version 条件的实例列表 + String version = headers.getFirst(VERSION); + List chooseInstances; + if (StrUtil.isEmpty(version)) { + chooseInstances = instances; + } else { + chooseInstances = CollectionUtils.filterList(instances, instance -> version.equals(instance.getMetadata().get("version"))); + if (CollUtil.isEmpty(chooseInstances)) { + log.warn("[getInstanceResponse][serviceId({}) 没有满足版本({})的服务实例列表,直接使用所有服务实例列表]", serviceId, version); + chooseInstances = instances; + } + } + + // 基于 tag 过滤实例列表 + chooseInstances = filterTagServiceInstances(chooseInstances, headers); + + // 随机 + 权重获取实例列表 TODO 芋艿:目前直接使用 Nacos 提供的方法,如果替换注册中心,需要重新失败该方法 + return new DefaultResponse(NacosBalancer.getHostByRandomWeight3(chooseInstances)); + } + + /** + * 基于 tag 请求头,过滤匹配 tag 的服务实例列表 + * + * copy from EnvLoadBalancerClient + * + * @param instances 服务实例列表 + * @param headers 请求头 + * @return 服务实例列表 + */ + private List filterTagServiceInstances(List instances, HttpHeaders headers) { + // 情况一,没有 tag 时,直接返回 + String tag = EnvUtils.getTag(headers); + if (StrUtil.isEmpty(tag)) { + return instances; + } + + // 情况二,有 tag 时,使用 tag 匹配服务实例 + List chooseInstances = CollectionUtils.filterList(instances, instance -> tag.equals(EnvUtils.getTag(instance))); + if (CollUtil.isEmpty(chooseInstances)) { + log.warn("[filterTagServiceInstances][serviceId({}) 没有满足 tag({}) 的服务实例列表,直接使用所有服务实例列表]", serviceId, tag); + chooseInstances = instances; + } + return chooseInstances; + } + +} diff --git a/tashow-gateway/src/main/java/com/tashow/cloud/gateway/filter/grey/GrayReactiveLoadBalancerClientFilter.java b/tashow-gateway/src/main/java/com/tashow/cloud/gateway/filter/grey/GrayReactiveLoadBalancerClientFilter.java new file mode 100644 index 0000000..1c0c31f --- /dev/null +++ b/tashow-gateway/src/main/java/com/tashow/cloud/gateway/filter/grey/GrayReactiveLoadBalancerClientFilter.java @@ -0,0 +1,138 @@ +package com.tashow.cloud.gateway.filter.grey; + +import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.cloud.client.ServiceInstance; +import org.springframework.cloud.client.loadbalancer.*; +import org.springframework.cloud.gateway.config.GatewayLoadBalancerProperties; +import org.springframework.cloud.gateway.filter.GatewayFilterChain; +import org.springframework.cloud.gateway.filter.GlobalFilter; +import org.springframework.cloud.gateway.filter.ReactiveLoadBalancerClientFilter; +import org.springframework.cloud.gateway.support.DelegatingServiceInstance; +import org.springframework.cloud.gateway.support.NotFoundException; +import org.springframework.cloud.loadbalancer.core.ServiceInstanceListSupplier; +import org.springframework.cloud.loadbalancer.support.LoadBalancerClientFactory; +import org.springframework.core.Ordered; +import org.springframework.stereotype.Component; +import org.springframework.web.server.ServerWebExchange; +import reactor.core.publisher.Mono; + +import java.net.URI; +import java.util.Map; +import java.util.Set; + +import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.*; + +/** + * 支持灰度功能的 {@link ReactiveLoadBalancerClientFilter} 实现类 + * + * 由于 {@link ReactiveLoadBalancerClientFilter#choose(Request, String, Set)} 是 private 方法,无法进行重写。 + * 因此,这里只好 copy 它所有的代码,手动重写 choose 方法 + * + * 具体的使用与实现原理,可阅读如下两个文章: + * 1. https://www.jianshu.com/p/6db15bc0be8f + * 2. https://cloud.tencent.com/developer/article/1620795 + * + * @author 芋道源码 + */ +@Component +@AllArgsConstructor +@Slf4j +@SuppressWarnings({"JavadocReference", "rawtypes", "unchecked", "ConstantConditions"}) +public class GrayReactiveLoadBalancerClientFilter implements GlobalFilter, Ordered { + + private final LoadBalancerClientFactory clientFactory; + + private final GatewayLoadBalancerProperties properties; + + @Override + public int getOrder() { + return ReactiveLoadBalancerClientFilter.LOAD_BALANCER_CLIENT_FILTER_ORDER; + } + + @Override + public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) { + URI url = exchange.getAttribute(GATEWAY_REQUEST_URL_ATTR); + String schemePrefix = exchange.getAttribute(GATEWAY_SCHEME_PREFIX_ATTR); + // 修改 by 芋道源码:将 lb 替换成 grayLb,表示灰度负载均衡 + if (url == null || (!"grayLb".equals(url.getScheme()) && !"grayLb".equals(schemePrefix))) { + return chain.filter(exchange); + } + // preserve the original url + addOriginalRequestUrl(exchange, url); + + if (log.isTraceEnabled()) { + log.trace(ReactiveLoadBalancerClientFilter.class.getSimpleName() + " url before: " + url); + } + + URI requestUri = exchange.getAttribute(GATEWAY_REQUEST_URL_ATTR); + String serviceId = requestUri.getHost(); + Set supportedLifecycleProcessors = LoadBalancerLifecycleValidator + .getSupportedLifecycleProcessors(clientFactory.getInstances(serviceId, LoadBalancerLifecycle.class), + RequestDataContext.class, ResponseData.class, ServiceInstance.class); + DefaultRequest lbRequest = new DefaultRequest<>( + new RequestDataContext(new RequestData(exchange.getRequest()), getHint(serviceId))); + return choose(lbRequest, serviceId, supportedLifecycleProcessors).doOnNext(response -> { + + if (!response.hasServer()) { + supportedLifecycleProcessors.forEach(lifecycle -> lifecycle + .onComplete(new CompletionContext<>(CompletionContext.Status.DISCARD, lbRequest, response))); + throw NotFoundException.create(properties.isUse404(), "Unable to find instance for " + url.getHost()); + } + + ServiceInstance retrievedInstance = response.getServer(); + + URI uri = exchange.getRequest().getURI(); + + // if the `lb:` mechanism was used, use `` as the default, + // if the loadbalancer doesn't provide one. + String overrideScheme = retrievedInstance.isSecure() ? "https" : "http"; + if (schemePrefix != null) { + overrideScheme = url.getScheme(); + } + + DelegatingServiceInstance serviceInstance = new DelegatingServiceInstance(retrievedInstance, + overrideScheme); + + URI requestUrl = reconstructURI(serviceInstance, uri); + + if (log.isTraceEnabled()) { + log.trace("LoadBalancerClientFilter url chosen: " + requestUrl); + } + exchange.getAttributes().put(GATEWAY_REQUEST_URL_ATTR, requestUrl); + exchange.getAttributes().put(GATEWAY_LOADBALANCER_RESPONSE_ATTR, response); + supportedLifecycleProcessors.forEach(lifecycle -> lifecycle.onStartRequest(lbRequest, response)); + }).then(chain.filter(exchange)) + .doOnError(throwable -> supportedLifecycleProcessors.forEach(lifecycle -> lifecycle + .onComplete(new CompletionContext( + CompletionContext.Status.FAILED, throwable, lbRequest, + exchange.getAttribute(GATEWAY_LOADBALANCER_RESPONSE_ATTR))))) + .doOnSuccess(aVoid -> supportedLifecycleProcessors.forEach(lifecycle -> lifecycle + .onComplete(new CompletionContext( + CompletionContext.Status.SUCCESS, lbRequest, + exchange.getAttribute(GATEWAY_LOADBALANCER_RESPONSE_ATTR), + new ResponseData(exchange.getResponse(), new RequestData(exchange.getRequest())))))); + } + + protected URI reconstructURI(ServiceInstance serviceInstance, URI original) { + return LoadBalancerUriTools.reconstructURI(serviceInstance, original); + } + + private Mono> choose(Request lbRequest, String serviceId, + Set supportedLifecycleProcessors) { + // 修改 by 芋道源码:直接创建 GrayLoadBalancer 对象 + GrayLoadBalancer loadBalancer = new GrayLoadBalancer( + clientFactory.getLazyProvider(serviceId, ServiceInstanceListSupplier.class), serviceId); + supportedLifecycleProcessors.forEach(lifecycle -> lifecycle.onStart(lbRequest)); + return loadBalancer.choose(lbRequest); + } + + private String getHint(String serviceId) { + LoadBalancerProperties loadBalancerProperties = clientFactory.getProperties(serviceId); + Map hints = loadBalancerProperties.getHint(); + String defaultHint = hints.getOrDefault("default", "default"); + String hintPropertyValue = hints.get(serviceId); + return hintPropertyValue != null ? hintPropertyValue : defaultHint; + } + +} diff --git a/tashow-gateway/src/main/java/com/tashow/cloud/gateway/filter/logging/AccessLog.java b/tashow-gateway/src/main/java/com/tashow/cloud/gateway/filter/logging/AccessLog.java new file mode 100644 index 0000000..f2c5b6a --- /dev/null +++ b/tashow-gateway/src/main/java/com/tashow/cloud/gateway/filter/logging/AccessLog.java @@ -0,0 +1,92 @@ +package com.tashow.cloud.gateway.filter.logging; + +import lombok.Data; +import org.springframework.cloud.gateway.route.Route; +import org.springframework.http.HttpStatus; +import org.springframework.util.MultiValueMap; + +import java.time.LocalDateTime; + +/** + * 网关的访问日志 + */ +@Data +public class AccessLog { + + /** + * 链路追踪编号 + */ + private String traceId; + /** + * 用户编号 + */ + private Long userId; + /** + * 用户类型 + */ + private Integer userType; + /** + * 路由 + * + * 类似 ApiAccessLogCreateReqDTO 的 applicationName + */ + private Route route; + + /** + * 协议 + */ + private String schema; + /** + * 请求方法名 + */ + private String requestMethod; + /** + * 访问地址 + */ + private String requestUrl; + /** + * 查询参数 + */ + private MultiValueMap queryParams; + /** + * 请求体 + */ + private String requestBody; + /** + * 请求头 + */ + private MultiValueMap requestHeaders; + /** + * 用户 IP + */ + private String userIp; + + /** + * 响应体 + * + * 类似 ApiAccessLogCreateReqDTO 的 resultCode + resultMsg + */ + private String responseBody; + /** + * 响应头 + */ + private MultiValueMap responseHeaders; + /** + * 响应结果 + */ + private HttpStatus httpStatus; + + /** + * 开始请求时间 + */ + private LocalDateTime startTime; + /** + * 结束请求时间 + */ + private LocalDateTime endTime; + /** + * 执行时长,单位:毫秒 + */ + private Integer duration; + +} diff --git a/tashow-gateway/src/main/java/com/tashow/cloud/gateway/filter/logging/AccessLogFilter.java b/tashow-gateway/src/main/java/com/tashow/cloud/gateway/filter/logging/AccessLogFilter.java new file mode 100644 index 0000000..adc070a --- /dev/null +++ b/tashow-gateway/src/main/java/com/tashow/cloud/gateway/filter/logging/AccessLogFilter.java @@ -0,0 +1,263 @@ +package com.tashow.cloud.gateway.filter.logging; + +import cn.hutool.core.date.LocalDateTimeUtil; +import cn.hutool.core.map.MapUtil; +import cn.hutool.json.JSONUtil; +import com.tashow.cloud.common.util.json.JsonUtils; +import com.tashow.cloud.gateway.util.SecurityFrameworkUtils; +import com.tashow.cloud.gateway.util.WebFrameworkUtils; +import com.alibaba.nacos.common.utils.StringUtils; +import lombok.extern.slf4j.Slf4j; +import org.reactivestreams.Publisher; +import org.springframework.cloud.gateway.filter.GatewayFilterChain; +import org.springframework.cloud.gateway.filter.GlobalFilter; +import org.springframework.cloud.gateway.filter.factory.rewrite.CachedBodyOutputMessage; +import org.springframework.cloud.gateway.filter.factory.rewrite.ModifyRequestBodyGatewayFilterFactory; +import org.springframework.cloud.gateway.support.BodyInserterContext; +import org.springframework.cloud.gateway.support.ServerWebExchangeUtils; +import org.springframework.core.Ordered; +import org.springframework.core.io.buffer.DataBuffer; +import org.springframework.core.io.buffer.DataBufferFactory; +import org.springframework.core.io.buffer.DataBufferUtils; +import org.springframework.core.io.buffer.DefaultDataBufferFactory; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.http.ReactiveHttpOutputMessage; +import org.springframework.http.codec.CodecConfigurer; +import org.springframework.http.server.reactive.ServerHttpRequest; +import org.springframework.http.server.reactive.ServerHttpRequestDecorator; +import org.springframework.http.server.reactive.ServerHttpResponse; +import org.springframework.http.server.reactive.ServerHttpResponseDecorator; +import org.springframework.stereotype.Component; +import org.springframework.web.reactive.function.BodyInserter; +import org.springframework.web.reactive.function.BodyInserters; +import org.springframework.web.reactive.function.server.ServerRequest; +import org.springframework.web.server.ServerWebExchange; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; + +import jakarta.annotation.Resource; +import java.nio.charset.StandardCharsets; +import java.time.LocalDateTime; +import java.util.List; +import java.util.Map; + +import static cn.hutool.core.date.DatePattern.NORM_DATETIME_MS_FORMATTER; + +/** + * 网关的访问日志过滤器 + * + * 从功能上,它类似 yudao-spring-boot-starter-web 的 ApiAccessLogFilter 过滤器 + * + * TODO 芋艿:如果网关执行异常,不会记录访问日志,后续研究下 https://github.com/Silvmike/webflux-demo/blob/master/tests/src/test/java/ru/hardcoders/demo/webflux/web_handler/filters/logging + * + * @author 芋道源码 + */ +@Slf4j +@Component +public class AccessLogFilter implements GlobalFilter, Ordered { + + @Resource + private CodecConfigurer codecConfigurer; + + /** + * 打印日志 + * + * @param gatewayLog 网关日志 + */ + private void writeAccessLog(AccessLog gatewayLog) { + // 方式一:打印 Logger 后,通过 ELK 进行收集 + // log.info("[writeAccessLog][日志内容:{}]", JsonUtils.toJsonString(gatewayLog)); + + // 方式二:调用远程服务,记录到数据库中 + // TODO 芋艿:暂未实现 + + // 方式三:打印到控制台,方便排查错误 + Map values = MapUtil.newHashMap(15, true); // 手工拼接,保证排序;15 保证不用扩容 + values.put("userId", gatewayLog.getUserId()); + values.put("userType", gatewayLog.getUserType()); + values.put("routeId", gatewayLog.getRoute() != null ? gatewayLog.getRoute().getId() : null); + values.put("schema", gatewayLog.getSchema()); + values.put("requestUrl", gatewayLog.getRequestUrl()); + values.put("queryParams", gatewayLog.getQueryParams().toSingleValueMap()); + values.put("requestBody", JsonUtils.isJson(gatewayLog.getRequestBody()) ? // 保证 body 的展示好看 + JSONUtil.parse(gatewayLog.getRequestBody()) : gatewayLog.getRequestBody()); + values.put("requestHeaders", JsonUtils.toJsonString(gatewayLog.getRequestHeaders().toSingleValueMap())); + values.put("userIp", gatewayLog.getUserIp()); + values.put("responseBody", JsonUtils.isJson(gatewayLog.getResponseBody()) ? // 保证 body 的展示好看 + JSONUtil.parse(gatewayLog.getResponseBody()) : gatewayLog.getResponseBody()); + values.put("responseHeaders", gatewayLog.getResponseHeaders() != null ? + JsonUtils.toJsonString(gatewayLog.getResponseHeaders().toSingleValueMap()) : null); + values.put("httpStatus", gatewayLog.getHttpStatus()); + values.put("startTime", LocalDateTimeUtil.format(gatewayLog.getStartTime(), NORM_DATETIME_MS_FORMATTER)); + values.put("endTime", LocalDateTimeUtil.format(gatewayLog.getEndTime(), NORM_DATETIME_MS_FORMATTER)); + values.put("duration", gatewayLog.getDuration() != null ? gatewayLog.getDuration() + " ms" : null); + log.info("[writeAccessLog][网关日志:{}]", JsonUtils.toJsonPrettyString(values)); + } + + @Override + public int getOrder() { + return Ordered.HIGHEST_PRECEDENCE; + } + + @Override + public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) { + // 将 Request 中可以直接获取到的参数,设置到网关日志 + ServerHttpRequest request = exchange.getRequest(); + // TODO traceId + AccessLog gatewayLog = new AccessLog(); + gatewayLog.setRoute(WebFrameworkUtils.getGatewayRoute(exchange)); + gatewayLog.setSchema(request.getURI().getScheme()); + gatewayLog.setRequestMethod(request.getMethod().name()); + gatewayLog.setRequestUrl(request.getURI().getRawPath()); + gatewayLog.setQueryParams(request.getQueryParams()); + gatewayLog.setRequestHeaders(request.getHeaders()); + gatewayLog.setStartTime(LocalDateTime.now()); + gatewayLog.setUserIp(WebFrameworkUtils.getClientIP(exchange)); + + // 继续 filter 过滤 + MediaType mediaType = request.getHeaders().getContentType(); + if (MediaType.APPLICATION_FORM_URLENCODED.isCompatibleWith(mediaType) + || MediaType.APPLICATION_JSON.isCompatibleWith(mediaType)) { // 适合 JSON 和 Form 提交的请求 + return filterWithRequestBody(exchange, chain, gatewayLog); + } + return filterWithoutRequestBody(exchange, chain, gatewayLog); + } + + private Mono filterWithoutRequestBody(ServerWebExchange exchange, GatewayFilterChain chain, AccessLog accessLog) { + // 包装 Response,用于记录 Response Body + ServerHttpResponseDecorator decoratedResponse = recordResponseLog(exchange, accessLog); + return chain.filter(exchange.mutate().response(decoratedResponse).build()) + .then(Mono.fromRunnable(() -> writeAccessLog(accessLog))); // 打印日志 + } + + /** + * 参考 {@link ModifyRequestBodyGatewayFilterFactory} 实现 + * + * 差别主要在于使用 modifiedBody 来读取 Request Body 数据 + */ + private Mono filterWithRequestBody(ServerWebExchange exchange, GatewayFilterChain chain, AccessLog gatewayLog) { + // 设置 Request Body 读取时,设置到网关日志 + // 此处 codecConfigurer.getReaders() 的目的,是解决 spring.codec.max-in-memory-size 不生效 + ServerRequest serverRequest = ServerRequest.create(exchange, codecConfigurer.getReaders()); + Mono modifiedBody = serverRequest.bodyToMono(String.class).flatMap(body -> { + gatewayLog.setRequestBody(body); + return Mono.just(body); + }); + + // 创建 BodyInserter 对象 + BodyInserter, ReactiveHttpOutputMessage> bodyInserter = BodyInserters.fromPublisher(modifiedBody, String.class); + // 创建 CachedBodyOutputMessage 对象 + HttpHeaders headers = new HttpHeaders(); + headers.putAll(exchange.getRequest().getHeaders()); + // the new content type will be computed by bodyInserter + // and then set in the request decorator + headers.remove(HttpHeaders.CONTENT_LENGTH); // 移除 + CachedBodyOutputMessage outputMessage = new CachedBodyOutputMessage(exchange, headers); + // 通过 BodyInserter 将 Request Body 写入到 CachedBodyOutputMessage 中 + return bodyInserter.insert(outputMessage, new BodyInserterContext()).then(Mono.defer(() -> { + // 包装 Request,用于缓存 Request Body + ServerHttpRequest decoratedRequest = requestDecorate(exchange, headers, outputMessage); + // 包装 Response,用于记录 Response Body + ServerHttpResponseDecorator decoratedResponse = recordResponseLog(exchange, gatewayLog); + // 记录普通的 + return chain.filter(exchange.mutate().request(decoratedRequest).response(decoratedResponse).build()) + .then(Mono.fromRunnable(() -> writeAccessLog(gatewayLog))); // 打印日志 + + })); + } + + /** + * 记录响应日志 + * 通过 DataBufferFactory 解决响应体分段传输问题。 + */ + private ServerHttpResponseDecorator recordResponseLog(ServerWebExchange exchange, AccessLog gatewayLog) { + ServerHttpResponse response = exchange.getResponse(); + return new ServerHttpResponseDecorator(response) { + + @Override + public Mono writeWith(Publisher body) { + if (body instanceof Flux) { + DataBufferFactory bufferFactory = response.bufferFactory(); + // 计算执行时间 + gatewayLog.setEndTime(LocalDateTime.now()); + gatewayLog.setDuration((int) (LocalDateTimeUtil.between(gatewayLog.getStartTime(), + gatewayLog.getEndTime()).toMillis())); + // 设置其它字段 + gatewayLog.setUserId(SecurityFrameworkUtils.getLoginUserId(exchange)); + gatewayLog.setUserType(SecurityFrameworkUtils.getLoginUserType(exchange)); + gatewayLog.setResponseHeaders(response.getHeaders()); + gatewayLog.setHttpStatus((HttpStatus) response.getStatusCode()); + + // 获取响应类型,如果是 json 就打印 + String originalResponseContentType = exchange.getAttribute(ServerWebExchangeUtils.ORIGINAL_RESPONSE_CONTENT_TYPE_ATTR); + if (StringUtils.isNotBlank(originalResponseContentType) + && originalResponseContentType.contains("application/json")) { + Flux fluxBody = Flux.from(body); + return super.writeWith(fluxBody.buffer().map(dataBuffers -> { + // 设置 response body 到网关日志 + byte[] content = readContent(dataBuffers); + String responseResult = new String(content, StandardCharsets.UTF_8); + gatewayLog.setResponseBody(responseResult); + + // 响应 + return bufferFactory.wrap(content); + })); + } + } + // if body is not a flux. never got there. + return super.writeWith(body); + } + }; + } + + // ========== 参考 ModifyRequestBodyGatewayFilterFactory 中的方法 ========== + + /** + * 请求装饰器,支持重新计算 headers、body 缓存 + * + * @param exchange 请求 + * @param headers 请求头 + * @param outputMessage body 缓存 + * @return 请求装饰器 + */ + private ServerHttpRequestDecorator requestDecorate(ServerWebExchange exchange, HttpHeaders headers, CachedBodyOutputMessage outputMessage) { + return new ServerHttpRequestDecorator(exchange.getRequest()) { + + @Override + public HttpHeaders getHeaders() { + long contentLength = headers.getContentLength(); + HttpHeaders httpHeaders = new HttpHeaders(); + httpHeaders.putAll(super.getHeaders()); + if (contentLength > 0) { + httpHeaders.setContentLength(contentLength); + } else { + // TODO: this causes a 'HTTP/1.1 411 Length Required' // on + // httpbin.org + httpHeaders.set(HttpHeaders.TRANSFER_ENCODING, "chunked"); + } + return httpHeaders; + } + + @Override + public Flux getBody() { + return outputMessage.getBody(); + } + }; + } + + // ========== 参考 ModifyResponseBodyGatewayFilterFactory 中的方法 ========== + + private byte[] readContent(List dataBuffers) { + // 合并多个流集合,解决返回体分段传输 + DataBufferFactory dataBufferFactory = new DefaultDataBufferFactory(); + DataBuffer join = dataBufferFactory.join(dataBuffers); + byte[] content = new byte[join.readableByteCount()]; + join.read(content); + // 释放掉内存 + DataBufferUtils.release(join); + return content; + } + +} diff --git a/tashow-gateway/src/main/java/com/tashow/cloud/gateway/filter/security/LoginUser.java b/tashow-gateway/src/main/java/com/tashow/cloud/gateway/filter/security/LoginUser.java new file mode 100644 index 0000000..0247913 --- /dev/null +++ b/tashow-gateway/src/main/java/com/tashow/cloud/gateway/filter/security/LoginUser.java @@ -0,0 +1,44 @@ +package com.tashow.cloud.gateway.filter.security; + +import lombok.Data; + +import java.time.LocalDateTime; +import java.util.List; +import java.util.Map; + +/** + * 登录用户信息 + * + * copy from yudao-spring-boot-starter-security 的 LoginUser 类 + * + * @author 芋道源码 + */ +@Data +public class LoginUser { + + /** + * 用户编号 + */ + private Long id; + /** + * 用户类型 + */ + private Integer userType; + /** + * 额外的用户信息 + */ + private Map info; + /** + * 租户编号 + */ + private Long tenantId; + /** + * 授权范围 + */ + private List scopes; + /** + * 过期时间 + */ + private LocalDateTime expiresTime; + +} diff --git a/tashow-gateway/src/main/java/com/tashow/cloud/gateway/filter/security/TokenAuthenticationFilter.java b/tashow-gateway/src/main/java/com/tashow/cloud/gateway/filter/security/TokenAuthenticationFilter.java new file mode 100644 index 0000000..f5a3699 --- /dev/null +++ b/tashow-gateway/src/main/java/com/tashow/cloud/gateway/filter/security/TokenAuthenticationFilter.java @@ -0,0 +1,168 @@ +package com.tashow.cloud.gateway.filter.security; + +import cn.hutool.core.util.StrUtil; +import com.fasterxml.jackson.core.type.TypeReference; +import com.google.common.cache.CacheLoader; +import com.google.common.cache.LoadingCache; +import com.tashow.cloud.common.core.KeyValue; +import com.tashow.cloud.common.pojo.CommonResult; +import com.tashow.cloud.common.util.date.LocalDateTimeUtils; +import com.tashow.cloud.common.util.json.JsonUtils; +import com.tashow.cloud.gateway.util.SecurityFrameworkUtils; +import com.tashow.cloud.gateway.util.WebFrameworkUtils; +import com.tashow.cloud.systemapi.api.oauth2.OAuth2TokenApi; +import com.tashow.cloud.systemapi.api.oauth2.dto.OAuth2AccessTokenCheckRespDTO; +import org.springframework.cloud.client.loadbalancer.reactive.ReactorLoadBalancerExchangeFilterFunction; +import org.springframework.cloud.gateway.filter.GatewayFilterChain; +import org.springframework.cloud.gateway.filter.GlobalFilter; +import org.springframework.core.Ordered; +import org.springframework.http.HttpStatus; +import org.springframework.stereotype.Component; +import org.springframework.web.reactive.function.client.WebClient; +import org.springframework.web.server.ServerWebExchange; +import reactor.core.publisher.Mono; + +import java.time.Duration; +import java.util.Objects; +import java.util.function.Function; + +import static com.tashow.cloud.common.util.cache.CacheUtils.buildAsyncReloadingCache; + + +/** + * Token 过滤器,验证 token 的有效性 + * 1. 验证通过时,将 userId、userType、tenantId 通过 Header 转发给服务 + * 2. 验证不通过,还是会转发给服务。因为,接口是否需要登录的校验,还是交给服务自身处理 + * + * @author 芋道源码 + */ +@Component +public class TokenAuthenticationFilter implements GlobalFilter, Ordered { + + /** + * CommonResult 对应的 TypeReference 结果,用于解析 checkToken 的结果 + */ + private static final TypeReference> CHECK_RESULT_TYPE_REFERENCE + = new TypeReference>() {}; + + /** + * 空的 LoginUser 的结果 + * + * 用于解决如下问题: + * 1. {@link #getLoginUser(ServerWebExchange, String)} 返回 Mono.empty() 时,会导致后续的 flatMap 无法进行处理的问题。 + * 2. {@link #buildUser(String)} 时,如果 Token 已经过期,返回 LOGIN_USER_EMPTY 对象,避免缓存无法刷新 + */ + private static final LoginUser LOGIN_USER_EMPTY = new LoginUser(); + + private final WebClient webClient; + + /** + * 登录用户的本地缓存 + * + * key1:多租户的编号 + * key2:访问令牌 + */ + private final LoadingCache, LoginUser> loginUserCache = buildAsyncReloadingCache(Duration.ofMinutes(1), + new CacheLoader, LoginUser>() { + + @Override + public LoginUser load(KeyValue token) { + String body = checkAccessToken(token.getKey(), token.getValue()).block(); + return buildUser(body); + } + + }); + + public TokenAuthenticationFilter(ReactorLoadBalancerExchangeFilterFunction lbFunction) { + // Q:为什么不使用 OAuth2TokenApi 进行调用? + // A1:Spring Cloud OpenFeign 官方未内置 Reactive 的支持 https://docs.spring.io/spring-cloud-openfeign/docs/current/reference/html/#reactive-support + // A2:校验 Token 的 API 需要使用到 header[tenant-id] 传递租户编号,暂时不想编写 RequestInterceptor 实现 + // 因此,这里采用 WebClient,通过 lbFunction 实现负载均衡 + this.webClient = WebClient.builder().filter(lbFunction).build(); + } + + @Override + public Mono filter(final ServerWebExchange exchange, GatewayFilterChain chain) { + // 移除 login-user 的请求头,避免伪造模拟 + SecurityFrameworkUtils.removeLoginUser(exchange); + + // 情况一,如果没有 Token 令牌,则直接继续 filter + String token = SecurityFrameworkUtils.obtainAuthorization(exchange); + if (StrUtil.isEmpty(token)) { + return chain.filter(exchange); + } + + // 情况二,如果有 Token 令牌,则解析对应 userId、userType、tenantId 等字段,并通过 通过 Header 转发给服务 + // 重要说明:defaultIfEmpty 作用,保证 Mono.empty() 情况,可以继续执行 `flatMap 的 chain.filter(exchange)` 逻辑,避免返回给前端空的 Response!! + return getLoginUser(exchange, token).defaultIfEmpty(LOGIN_USER_EMPTY).flatMap(user -> { + // 1. 无用户,直接 filter 继续请求 + if (user == LOGIN_USER_EMPTY || // 下面 expiresTime 的判断,为了解决 token 实际已经过期的情况 + user.getExpiresTime() == null || LocalDateTimeUtils.beforeNow(user.getExpiresTime())) { + return chain.filter(exchange); + } + + // 2.1 有用户,则设置登录用户 + SecurityFrameworkUtils.setLoginUser(exchange, user); + // 2.2 将 user 并设置到 login-user 的请求头,使用 json 存储值 + ServerWebExchange newExchange = exchange.mutate() + .request(builder -> SecurityFrameworkUtils.setLoginUserHeader(builder, user)).build(); + return chain.filter(newExchange); + }); + } + + private Mono getLoginUser(ServerWebExchange exchange, String token) { + // 从缓存中,获取 LoginUser + Long tenantId = WebFrameworkUtils.getTenantId(exchange); + KeyValue cacheKey = new KeyValue().setKey(tenantId).setValue(token); + LoginUser localUser = loginUserCache.getIfPresent(cacheKey); + if (localUser != null) { + return Mono.just(localUser); + } + + // 缓存不存在,则请求远程服务 + return checkAccessToken(tenantId, token).flatMap((Function>) body -> { + LoginUser remoteUser = buildUser(body); + if (remoteUser != null) { + // 非空,则进行缓存 + loginUserCache.put(cacheKey, remoteUser); + return Mono.just(remoteUser); + } + return Mono.empty(); + }); + } + + private Mono checkAccessToken(Long tenantId, String token) { + return webClient.get() + .uri(OAuth2TokenApi.URL_CHECK, uriBuilder -> uriBuilder.queryParam("accessToken", token).build()) + .headers(httpHeaders -> WebFrameworkUtils.setTenantIdHeader(tenantId, httpHeaders)) // 设置租户的 Header + .retrieve().bodyToMono(String.class); + } + + private LoginUser buildUser(String body) { + // 处理结果,结果不正确 + CommonResult result = JsonUtils.parseObject(body, CHECK_RESULT_TYPE_REFERENCE); + if (result == null) { + return null; + } + if (result.isError()) { + // 特殊情况:令牌已经过期(code = 401),需要返回 LOGIN_USER_EMPTY,避免 Token 一直因为缓存,被误判为有效 + if (Objects.equals(result.getCode(), HttpStatus.UNAUTHORIZED.value())) { + return LOGIN_USER_EMPTY; + } + return null; + } + + // 创建登录用户 + OAuth2AccessTokenCheckRespDTO tokenInfo = result.getData(); + return new LoginUser().setId(tokenInfo.getUserId()).setUserType(tokenInfo.getUserType()) + .setInfo(tokenInfo.getUserInfo()) // 额外的用户信息 + .setTenantId(tokenInfo.getTenantId()).setScopes(tokenInfo.getScopes()) + .setExpiresTime(tokenInfo.getExpiresTime()); + } + + @Override + public int getOrder() { + return -100; // 和 Spring Security Filter 的顺序对齐 + } + +} diff --git a/tashow-gateway/src/main/java/com/tashow/cloud/gateway/handler/GlobalExceptionHandler.java b/tashow-gateway/src/main/java/com/tashow/cloud/gateway/handler/GlobalExceptionHandler.java new file mode 100644 index 0000000..34d04ab --- /dev/null +++ b/tashow-gateway/src/main/java/com/tashow/cloud/gateway/handler/GlobalExceptionHandler.java @@ -0,0 +1,75 @@ +package com.tashow.cloud.gateway.handler; + +import com.tashow.cloud.common.pojo.CommonResult; +import com.tashow.cloud.gateway.util.WebFrameworkUtils; +import lombok.extern.slf4j.Slf4j; +import org.springframework.boot.web.reactive.error.ErrorWebExceptionHandler; +import org.springframework.core.annotation.Order; +import org.springframework.http.server.reactive.ServerHttpRequest; +import org.springframework.http.server.reactive.ServerHttpResponse; +import org.springframework.stereotype.Component; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.server.ResponseStatusException; +import org.springframework.web.server.ServerWebExchange; +import reactor.core.publisher.Mono; + +import static com.tashow.cloud.common.exception.enums.GlobalErrorCodeConstants.INTERNAL_SERVER_ERROR; + + +/** + * Gateway 的全局异常处理器,将 Exception 翻译成 CommonResult + 对应的异常编号 + * + * 在功能上,和 yudao-spring-boot-starter-web 的 GlobalExceptionHandler 类是一致的 + * + * @author 芋道源码 + */ +@Component +@Order(-1) // 保证优先级高于默认的 Spring Cloud Gateway 的 ErrorWebExceptionHandler 实现 +@Slf4j +public class GlobalExceptionHandler implements ErrorWebExceptionHandler { + + @Override + public Mono handle(ServerWebExchange exchange, Throwable ex) { + // 已经 commit,则直接返回异常 + ServerHttpResponse response = exchange.getResponse(); + if (response.isCommitted()) { + return Mono.error(ex); + } + + // 转换成 CommonResult + CommonResult result; + if (ex instanceof ResponseStatusException) { + result = responseStatusExceptionHandler(exchange, (ResponseStatusException) ex); + } else { + result = defaultExceptionHandler(exchange, ex); + } + + // 返回给前端 + return WebFrameworkUtils.writeJSON(exchange, result); + } + + /** + * 处理 Spring Cloud Gateway 默认抛出的 ResponseStatusException 异常 + */ + private CommonResult responseStatusExceptionHandler(ServerWebExchange exchange, + ResponseStatusException ex) { + // TODO 芋艿:这里要精细化翻译,默认返回用户是看不懂的 + ServerHttpRequest request = exchange.getRequest(); + log.error("[responseStatusExceptionHandler][uri({}/{}) 发生异常]", request.getURI(), request.getMethod(), ex); + return CommonResult.error(ex.getStatusCode().value(), ex.getReason()); + } + + /** + * 处理系统异常,兜底处理所有的一切 + */ + @ExceptionHandler(value = Exception.class) + public CommonResult defaultExceptionHandler(ServerWebExchange exchange, + Throwable ex) { + ServerHttpRequest request = exchange.getRequest(); + log.error("[defaultExceptionHandler][uri({}/{}) 发生异常]", request.getURI(), request.getMethod(), ex); + // TODO 芋艿:是否要插入异常日志呢? + // 返回 ERROR CommonResult + return CommonResult.error(INTERNAL_SERVER_ERROR.getCode(), INTERNAL_SERVER_ERROR.getMsg()); + } + +} diff --git a/tashow-gateway/src/main/java/com/tashow/cloud/gateway/jackson/JacksonAutoConfiguration.java b/tashow-gateway/src/main/java/com/tashow/cloud/gateway/jackson/JacksonAutoConfiguration.java new file mode 100644 index 0000000..0daa016 --- /dev/null +++ b/tashow-gateway/src/main/java/com/tashow/cloud/gateway/jackson/JacksonAutoConfiguration.java @@ -0,0 +1,51 @@ +package com.tashow.cloud.gateway.jackson; + +import cn.hutool.core.collection.CollUtil; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.module.SimpleModule; +import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer; +import com.fasterxml.jackson.datatype.jsr310.deser.LocalTimeDeserializer; +import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer; +import com.fasterxml.jackson.datatype.jsr310.ser.LocalTimeSerializer; +import com.tashow.cloud.common.util.json.JsonUtils; +import com.tashow.cloud.common.util.json.databind.NumberSerializer; +import com.tashow.cloud.common.util.json.databind.TimestampLocalDateTimeDeserializer; +import com.tashow.cloud.common.util.json.databind.TimestampLocalDateTimeSerializer; +import lombok.extern.slf4j.Slf4j; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.util.List; + +@Configuration +@Slf4j +public class JacksonAutoConfiguration { + + @Bean + public JsonUtils jsonUtils(List objectMappers) { + // 1.1 创建 SimpleModule 对象 + SimpleModule simpleModule = new SimpleModule(); + simpleModule + // 新增 Long 类型序列化规则,数值超过 2^53-1,在 JS 会出现精度丢失问题,因此 Long 自动序列化为字符串类型 + .addSerializer(Long.class, NumberSerializer.INSTANCE) + .addSerializer(Long.TYPE, NumberSerializer.INSTANCE) + .addSerializer(LocalDate.class, LocalDateSerializer.INSTANCE) + .addDeserializer(LocalDate.class, LocalDateDeserializer.INSTANCE) + .addSerializer(LocalTime.class, LocalTimeSerializer.INSTANCE) + .addDeserializer(LocalTime.class, LocalTimeDeserializer.INSTANCE) + // 新增 LocalDateTime 序列化、反序列化规则,使用 Long 时间戳 + .addSerializer(LocalDateTime.class, TimestampLocalDateTimeSerializer.INSTANCE) + .addDeserializer(LocalDateTime.class, TimestampLocalDateTimeDeserializer.INSTANCE); + // 1.2 注册到 objectMapper + objectMappers.forEach(objectMapper -> objectMapper.registerModule(simpleModule)); + + // 2. 设置 objectMapper 到 JsonUtils + JsonUtils.init(CollUtil.getFirst(objectMappers)); + log.info("[init][初始化 JsonUtils 成功]"); + return new JsonUtils(); + } + +} diff --git a/tashow-gateway/src/main/java/com/tashow/cloud/gateway/route/dynamic/package-info.java b/tashow-gateway/src/main/java/com/tashow/cloud/gateway/route/dynamic/package-info.java new file mode 100644 index 0000000..2c8f4c2 --- /dev/null +++ b/tashow-gateway/src/main/java/com/tashow/cloud/gateway/route/dynamic/package-info.java @@ -0,0 +1,10 @@ +/** + * 在 Nacos 配置发生变化时,Spring Cloud Alibaba Nacos Config 内置的监听器,会监听到配置刷新,最终触发 Gateway 的路由信息刷新。 + * + * 参见 https://www.iocoder.cn/Spring-Cloud/Spring-Cloud-Gateway/?yudao 博客的「6. 基于配置中心 Nacos 实现动态路由」小节 + * + * 使用方式:在 Nacos 修改 DataId 为 gateway-server.yaml 的配置,修改 spring.cloud.gateway.routes 配置项 + * + * @author 芋道源码 + */ +package com.tashow.cloud.gateway.route.dynamic; diff --git a/tashow-gateway/src/main/java/com/tashow/cloud/gateway/route/package-info.java b/tashow-gateway/src/main/java/com/tashow/cloud/gateway/route/package-info.java new file mode 100644 index 0000000..34db8d6 --- /dev/null +++ b/tashow-gateway/src/main/java/com/tashow/cloud/gateway/route/package-info.java @@ -0,0 +1,4 @@ +/** + * 占位符 + */ +package com.tashow.cloud.gateway.route; diff --git a/tashow-gateway/src/main/java/com/tashow/cloud/gateway/util/BannerApplicationRunner.java b/tashow-gateway/src/main/java/com/tashow/cloud/gateway/util/BannerApplicationRunner.java new file mode 100644 index 0000000..0a794ea --- /dev/null +++ b/tashow-gateway/src/main/java/com/tashow/cloud/gateway/util/BannerApplicationRunner.java @@ -0,0 +1,52 @@ +package com.tashow.cloud.gateway.util; + +import cn.hutool.core.thread.ThreadUtil; +import lombok.extern.slf4j.Slf4j; +import org.springframework.boot.ApplicationArguments; +import org.springframework.boot.ApplicationRunner; +import org.springframework.stereotype.Component; + +import java.util.concurrent.TimeUnit; + +/** + * 项目启动成功后,提供文档相关的地址 + * + * @author 芋道源码 + */ +@Component +@Slf4j +public class BannerApplicationRunner implements ApplicationRunner { + + @Override + public void run(ApplicationArguments args) { + ThreadUtil.execute(() -> { + ThreadUtil.sleep(1, TimeUnit.SECONDS); // 延迟 1 秒,保证输出到结尾 + log.info("\n----------------------------------------------------------\n\t" + + "项目启动成功!\n\t" + + "接口文档: \t{} \n\t" + + "----------------------------------------------------------", + "https://cloud.iocoder.cn/api-doc/" + ); + +// // 数据报表 +// System.out.println("[报表模块 yudao-module-report 教程][参考 https://cloud.iocoder.cn/report/ 开启]"); +// // 工作流 +// System.out.println("[工作流模块 yudao-module-bpm 教程][参考 https://cloud.iocoder.cn/bpm/ 开启]"); +// // 商城系统 +// System.out.println("[商城系统 yudao-module-mall 教程][参考 https://cloud.iocoder.cn/mall/build/ 开启]"); +// // ERP 系统 +// System.out.println("[ERP 系统 yudao-module-erp - 教程][参考 https://cloud.iocoder.cn/erp/build/ 开启]"); +// // CRM 系统 +// System.out.println("[CRM 系统 yudao-module-crm - 教程][参考 https://cloud.iocoder.cn/crm/build/ 开启]"); +// // 微信公众号 +// System.out.println("[微信公众号 yudao-module-mp 教程][参考 https://cloud.iocoder.cn/mp/build/ 开启]"); +// // 支付平台 +// System.out.println("[支付系统 yudao-module-pay - 教程][参考 https://doc.iocoder.cn/pay/build/ 开启]"); +// // AI 大模型 +// System.out.println("[AI 大模型 yudao-module-ai - 教程][参考 https://cloud.iocoder.cn/ai/build/ 开启]"); +// // IOT 物联网 +// System.out.println("[IOT 物联网 yudao-module-iot - 教程][参考 https://doc.iocoder.cn/iot/build/ 开启]"); + }); + } + +} diff --git a/tashow-gateway/src/main/java/com/tashow/cloud/gateway/util/EnvUtils.java b/tashow-gateway/src/main/java/com/tashow/cloud/gateway/util/EnvUtils.java new file mode 100644 index 0000000..6a48147 --- /dev/null +++ b/tashow-gateway/src/main/java/com/tashow/cloud/gateway/util/EnvUtils.java @@ -0,0 +1,39 @@ +package com.tashow.cloud.gateway.util; + +import cn.hutool.core.net.NetUtil; +import cn.hutool.core.util.IdUtil; +import cn.hutool.core.util.StrUtil; +import org.springframework.cloud.client.ServiceInstance; +import org.springframework.http.HttpHeaders; + +import java.util.Objects; + +/** + * 环境 Utils + * + * copy from yudao-spring-boot-starter-env 的 EnvUtils 类 + * + * @author 芋道源码 + */ +public class EnvUtils { + + private static final String HEADER_TAG = "tag"; + + public static final String HOST_NAME_VALUE = "${HOSTNAME}"; + + public static String getTag(HttpHeaders headers) { + String tag = headers.getFirst(HEADER_TAG); + // 如果请求的是 "${HOSTNAME}",则解析成对应的本地主机名 + // 目的:特殊逻辑,解决 IDEA Rest Client 不支持环境变量的读取,所以就服务器来做 + return Objects.equals(tag, HOST_NAME_VALUE) ? getHostName() : tag; + } + + public static String getTag(ServiceInstance instance) { + return instance.getMetadata().get(HEADER_TAG); + } + + public static String getHostName() { + return StrUtil.blankToDefault(NetUtil.getLocalHostName(), IdUtil.fastSimpleUUID()); + } + +} diff --git a/tashow-gateway/src/main/java/com/tashow/cloud/gateway/util/SecurityFrameworkUtils.java b/tashow-gateway/src/main/java/com/tashow/cloud/gateway/util/SecurityFrameworkUtils.java new file mode 100644 index 0000000..c311674 --- /dev/null +++ b/tashow-gateway/src/main/java/com/tashow/cloud/gateway/util/SecurityFrameworkUtils.java @@ -0,0 +1,118 @@ +package com.tashow.cloud.gateway.util; + +import cn.hutool.core.map.MapUtil; +import com.tashow.cloud.common.util.json.JsonUtils; +import com.tashow.cloud.gateway.filter.security.LoginUser; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.server.reactive.ServerHttpRequest; +import org.springframework.util.StringUtils; +import org.springframework.web.server.ServerWebExchange; + +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; + +/** + * 安全服务工具类 + * + * copy from yudao-spring-boot-starter-security 的 SecurityFrameworkUtils 类 + * + * @author 芋道源码 + */ +@Slf4j +public class SecurityFrameworkUtils { + + private static final String AUTHORIZATION_HEADER = "Authorization"; + + private static final String AUTHORIZATION_BEARER = "Bearer"; + + private static final String LOGIN_USER_HEADER = "login-user"; + + private static final String LOGIN_USER_ID_ATTR = "login-user-id"; + private static final String LOGIN_USER_TYPE_ATTR = "login-user-type"; + + private SecurityFrameworkUtils() {} + + /** + * 从请求中,获得认证 Token + * + * @param exchange 请求 + * @return 认证 Token + */ + public static String obtainAuthorization(ServerWebExchange exchange) { + String authorization = exchange.getRequest().getHeaders().getFirst(AUTHORIZATION_HEADER); + if (!StringUtils.hasText(authorization)) { + return null; + } + int index = authorization.indexOf(AUTHORIZATION_BEARER + " "); + if (index == -1) { // 未找到 + return null; + } + return authorization.substring(index + 7).trim(); + } + + /** + * 设置登录用户 + * + * @param exchange 请求 + * @param user 用户 + */ + public static void setLoginUser(ServerWebExchange exchange, LoginUser user) { + exchange.getAttributes().put(LOGIN_USER_ID_ATTR, user.getId()); + exchange.getAttributes().put(LOGIN_USER_TYPE_ATTR, user.getUserType()); + } + + /** + * 移除请求头的用户 + * + * @param exchange 请求 + * @return 请求 + */ + public static ServerWebExchange removeLoginUser(ServerWebExchange exchange) { + // 如果不包含,直接返回 + if (!exchange.getRequest().getHeaders().containsKey(LOGIN_USER_HEADER)) { + return exchange; + } + // 如果包含,则移除。参考 RemoveRequestHeaderGatewayFilterFactory 实现 + ServerHttpRequest request = exchange.getRequest().mutate() + .headers(httpHeaders -> httpHeaders.remove(LOGIN_USER_HEADER)).build(); + return exchange.mutate().request(request).build(); + } + + /** + * 获得登录用户的编号 + * + * @param exchange 请求 + * @return 用户编号 + */ + public static Long getLoginUserId(ServerWebExchange exchange) { + return MapUtil.getLong(exchange.getAttributes(), LOGIN_USER_ID_ATTR); + } + + /** + * 获得登录用户的类型 + * + * @param exchange 请求 + * @return 用户类型 + */ + public static Integer getLoginUserType(ServerWebExchange exchange) { + return MapUtil.getInt(exchange.getAttributes(), LOGIN_USER_TYPE_ATTR); + } + + /** + * 将 user 并设置到 login-user 的请求头,使用 json 存储值 + * + * @param builder 请求 + * @param user 用户 + */ + public static void setLoginUserHeader(ServerHttpRequest.Builder builder, LoginUser user) { + try { + String userStr = JsonUtils.toJsonString(user); + userStr = URLEncoder.encode(userStr, StandardCharsets.UTF_8); // 编码,避免中文乱码 + builder.header(LOGIN_USER_HEADER, userStr); + } catch (Exception ex) { + log.error("[setLoginUserHeader][序列化 user({}) 发生异常]", user, ex); + throw ex; + } + } + +} diff --git a/tashow-gateway/src/main/java/com/tashow/cloud/gateway/util/WebFrameworkUtils.java b/tashow-gateway/src/main/java/com/tashow/cloud/gateway/util/WebFrameworkUtils.java new file mode 100644 index 0000000..fa83c3a --- /dev/null +++ b/tashow-gateway/src/main/java/com/tashow/cloud/gateway/util/WebFrameworkUtils.java @@ -0,0 +1,116 @@ +package com.tashow.cloud.gateway.util; + +import cn.hutool.core.net.NetUtil; +import cn.hutool.core.util.ArrayUtil; +import cn.hutool.core.util.NumberUtil; +import cn.hutool.extra.servlet.ServletUtil; +import com.tashow.cloud.common.util.json.JsonUtils; +import lombok.extern.slf4j.Slf4j; +import org.springframework.cloud.gateway.route.Route; +import org.springframework.cloud.gateway.support.ServerWebExchangeUtils; +import org.springframework.core.io.buffer.DataBufferFactory; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; +import org.springframework.http.server.reactive.ServerHttpRequest; +import org.springframework.http.server.reactive.ServerHttpResponse; +import org.springframework.web.server.ServerWebExchange; +import reactor.core.publisher.Mono; + +/** + * Web 工具类 + * + * copy from yudao-spring-boot-starter-web 的 WebFrameworkUtils 类 + * + * @author 芋道源码 + */ +@Slf4j +public class WebFrameworkUtils { + + private static final String HEADER_TENANT_ID = "tenant-id"; + + private WebFrameworkUtils() {} + + /** + * 将 Gateway 请求中的 header,设置到 HttpHeaders 中 + * + * @param tenantId 租户编号 + * @param httpHeaders WebClient 的请求 + */ + public static void setTenantIdHeader(Long tenantId, HttpHeaders httpHeaders) { + if (tenantId == null) { + return; + } + httpHeaders.set(HEADER_TENANT_ID, String.valueOf(tenantId)); + } + + public static Long getTenantId(ServerWebExchange exchange) { + String tenantId = exchange.getRequest().getHeaders().getFirst(HEADER_TENANT_ID); + return NumberUtil.isNumber(tenantId) ? Long.valueOf(tenantId) : null; + } + + /** + * 返回 JSON 字符串 + * + * @param exchange 响应 + * @param object 对象,会序列化成 JSON 字符串 + */ + @SuppressWarnings("deprecation") // 必须使用 APPLICATION_JSON_UTF8_VALUE,否则会乱码 + public static Mono writeJSON(ServerWebExchange exchange, Object object) { + // 设置 header + ServerHttpResponse response = exchange.getResponse(); + response.getHeaders().setContentType(MediaType.APPLICATION_JSON_UTF8); + // 设置 body + return response.writeWith(Mono.fromSupplier(() -> { + DataBufferFactory bufferFactory = response.bufferFactory(); + try { + return bufferFactory.wrap(JsonUtils.toJsonByte(object)); + } catch (Exception ex) { + ServerHttpRequest request = exchange.getRequest(); + log.error("[writeJSON][uri({}/{}) 发生异常]", request.getURI(), request.getMethod(), ex); + return bufferFactory.wrap(new byte[0]); + } + })); + } + + /** + * 获得客户端 IP + * + * 参考 {@link ServletUtil} 的 getClientIP 方法 + * + * @param exchange 请求 + * @param otherHeaderNames 其它 header 名字的数组 + * @return 客户端 IP + */ + public static String getClientIP(ServerWebExchange exchange, String... otherHeaderNames) { + String[] headers = { "X-Forwarded-For", "X-Real-IP", "Proxy-Client-IP", "WL-Proxy-Client-IP", "HTTP_CLIENT_IP", "HTTP_X_FORWARDED_FOR" }; + if (ArrayUtil.isNotEmpty(otherHeaderNames)) { + headers = ArrayUtil.addAll(headers, otherHeaderNames); + } + // 方式一,通过 header 获取 + String ip; + for (String header : headers) { + ip = exchange.getRequest().getHeaders().getFirst(header); + if (!NetUtil.isUnknown(ip)) { + return NetUtil.getMultistageReverseProxyIp(ip); + } + } + + // 方式二,通过 remoteAddress 获取 + if (exchange.getRequest().getRemoteAddress() == null) { + return null; + } + ip = exchange.getRequest().getRemoteAddress().getHostString(); + return NetUtil.getMultistageReverseProxyIp(ip); + } + + /** + * 获得请求匹配的 Route 路由 + * + * @param exchange 请求 + * @return 路由 + */ + public static Route getGatewayRoute(ServerWebExchange exchange) { + return exchange.getAttribute(ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR); + } + +} diff --git a/tashow-gateway/src/main/resources/application-local.yaml b/tashow-gateway/src/main/resources/application-local.yaml new file mode 100644 index 0000000..8756703 --- /dev/null +++ b/tashow-gateway/src/main/resources/application-local.yaml @@ -0,0 +1,19 @@ +--- #################### 注册中心 + 配置中心相关配置 #################### + +spring: + cloud: + nacos: + server-addr: 43.139.42.137:8848 # Nacos 服务器地址 + username: nacos # Nacos 账号 + password: nacos # Nacos 密码 + discovery: # 【配置中心】配置项 + namespace: 16bd40df-7cc7-4c2c-82c2-6186ade7bb08 # 命名空间。这里使用 dev 开发环境 + group: DEFAULT_GROUP # 使用的 Nacos 配置分组,默认为 DEFAULT_GROUP + config: # 【注册中心】配置项 + namespace: 16bd40df-7cc7-4c2c-82c2-6186ade7bb08 # 命名空间。这里使用 dev 开发环境 + group: DEFAULT_GROUP # 使用的 Nacos 配置分组,默认为 DEFAULT_GROUP + +# 日志文件配置 +logging: + level: + org.springframework.context.support.PostProcessorRegistrationDelegate: ERROR # TODO 芋艿:先禁用,Spring Boot 3.X 存在部分错误的 WARN 提示 diff --git a/tashow-gateway/src/main/resources/application.yaml b/tashow-gateway/src/main/resources/application.yaml new file mode 100644 index 0000000..ab8de4e --- /dev/null +++ b/tashow-gateway/src/main/resources/application.yaml @@ -0,0 +1,15 @@ +server: + port: 48080 +spring: + application: + name: gateway-server + profiles: + active: local + main: + allow-circular-references: true # 允许循环依赖,因为项目是三层架构,无法避免这个情况。 + config: + import: + - optional:classpath:application-${spring.profiles.active}.yaml # 加载【本地】配置 + - optional:nacos:application.yaml # 加载【Nacos】的配置 + - optional:nacos:${spring.application.name}-${spring.profiles.active}.yaml # 加载【Nacos】的配置 + diff --git a/tashow-gateway/src/main/resources/banner.txt b/tashow-gateway/src/main/resources/banner.txt new file mode 100644 index 0000000..5fd0506 --- /dev/null +++ b/tashow-gateway/src/main/resources/banner.txt @@ -0,0 +1,16 @@ +Application Version: ${tashow.info.version} +Spring Boot Version: ${spring-boot.version} + +.__ __. ______ .______ __ __ _______ +| \ | | / __ \ | _ \ | | | | / _____| +| \| | | | | | | |_) | | | | | | | __ +| . ` | | | | | | _ < | | | | | | |_ | +| |\ | | `--' | | |_) | | `--' | | |__| | +|__| \__| \______/ |______/ \______/ \______| + +███╗ ██╗ ██████╗ ██████╗ ██╗ ██╗ ██████╗ +████╗ ██║██╔═══██╗ ██╔══██╗██║ ██║██╔════╝ +██╔██╗ ██║██║ ██║ ██████╔╝██║ ██║██║ ███╗ +██║╚██╗██║██║ ██║ ██╔══██╗██║ ██║██║ ██║ +██║ ╚████║╚██████╔╝ ██████╔╝╚██████╔╝╚██████╔╝ +╚═╝ ╚═══╝ ╚═════╝ ╚═════╝ ╚═════╝ ╚═════╝ diff --git a/tashow-gateway/src/main/resources/logback-spring.xml b/tashow-gateway/src/main/resources/logback-spring.xml new file mode 100644 index 0000000..16f0c0f --- /dev/null +++ b/tashow-gateway/src/main/resources/logback-spring.xml @@ -0,0 +1,76 @@ + + + + + + + + + +       + + + ${PATTERN_DEFAULT} + + + + + + + + + + ${PATTERN_DEFAULT} + + + + ${LOG_FILE} + + + ${LOGBACK_ROLLINGPOLICY_FILE_NAME_PATTERN:-${LOG_FILE}.%d{yyyy-MM-dd}.%i.gz} + + ${LOGBACK_ROLLINGPOLICY_CLEAN_HISTORY_ON_START:-false} + + ${LOGBACK_ROLLINGPOLICY_MAX_FILE_SIZE:-10MB} + + ${LOGBACK_ROLLINGPOLICY_TOTAL_SIZE_CAP:-0} + + ${LOGBACK_ROLLINGPOLICY_MAX_HISTORY:-30} + + + + + + 0 + + 256 + + + + + + + + ${PATTERN_DEFAULT} + + + + + + + + + + + + + + + + + + + + + + diff --git a/tashow-gateway/src/main/resources/static/favicon.ico b/tashow-gateway/src/main/resources/static/favicon.ico new file mode 100644 index 0000000..30053fa Binary files /dev/null and b/tashow-gateway/src/main/resources/static/favicon.ico differ diff --git a/tashow-module/pom.xml b/tashow-module/pom.xml new file mode 100644 index 0000000..ffdb6c5 --- /dev/null +++ b/tashow-module/pom.xml @@ -0,0 +1,20 @@ + + 4.0.0 + + com.tashow.cloud + tashow-platform + ${revision} + + + tashow-module + pom + + + tashow-module-system + tashow-module-infra + tashow-module-app + tashow-module-product + + + diff --git a/tashow-module/tashow-module-app/pom.xml b/tashow-module/tashow-module-app/pom.xml new file mode 100644 index 0000000..4de4e69 --- /dev/null +++ b/tashow-module/tashow-module-app/pom.xml @@ -0,0 +1,79 @@ + + 4.0.0 + + com.tashow.cloud + tashow-module + ${revision} + + + tashow-module-app + jar + + + + + com.alibaba.cloud + spring-cloud-starter-alibaba-nacos-discovery + + + + + com.alibaba.cloud + spring-cloud-starter-alibaba-nacos-config + + + + + com.tashow.cloud + tashow-framework-rpc + + + com.tashow.cloud + tashow-data-mybatis + + + com.tashow.cloud + tashow-framework-web + + + com.tashow.cloud + tashow-framework-env + + + com.tashow.cloud + tashow-infra-api + + + + com.tashow.cloud + tashow-framework-websocket + + + com.tashow.cloud + tashow-data-redis + + + com.tashow.cloud + tashow-framework-security + + + org.springframework.boot + spring-boot-starter-actuator + + + com.tashow.cloud + tashow-feishu-sdk + 1.0.0 + compile + + + org.springframework.amqp + spring-rabbit + + + com.tashow.cloud + tashow-data-redis + + + diff --git a/tashow-module/tashow-module-app/src/main/java/com/tashow/cloud/app/AppServerApplication.java b/tashow-module/tashow-module-app/src/main/java/com/tashow/cloud/app/AppServerApplication.java new file mode 100644 index 0000000..5099474 --- /dev/null +++ b/tashow-module/tashow-module-app/src/main/java/com/tashow/cloud/app/AppServerApplication.java @@ -0,0 +1,19 @@ +package com.tashow.cloud.app; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.scheduling.annotation.EnableScheduling; + +/** + * 应用服务启动类 + */ +@SpringBootApplication +@EnableScheduling +@ComponentScan(basePackages = {"com.tashow.cloud.app", "com.tashow.cloud.sdk.feishu"}) +public class AppServerApplication { + + public static void main(String[] args) { + SpringApplication.run(AppServerApplication.class, args); + } +} diff --git a/tashow-module/tashow-module-app/src/main/java/com/tashow/cloud/app/config/AppConfig.java b/tashow-module/tashow-module-app/src/main/java/com/tashow/cloud/app/config/AppConfig.java new file mode 100644 index 0000000..8684b88 --- /dev/null +++ b/tashow-module/tashow-module-app/src/main/java/com/tashow/cloud/app/config/AppConfig.java @@ -0,0 +1,19 @@ +package com.tashow.cloud.app.config; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * 应用配置类 + */ +@Configuration +public class AppConfig { + + /** + * 提供ObjectMapper bean用于JSON处理 + */ + @Bean + public ObjectMapper objectMapper() { + return new ObjectMapper(); + } +} \ No newline at end of file diff --git a/tashow-module/tashow-module-app/src/main/java/com/tashow/cloud/app/controller/FeishuController.java b/tashow-module/tashow-module-app/src/main/java/com/tashow/cloud/app/controller/FeishuController.java new file mode 100644 index 0000000..7ef438e --- /dev/null +++ b/tashow-module/tashow-module-app/src/main/java/com/tashow/cloud/app/controller/FeishuController.java @@ -0,0 +1,64 @@ +package com.tashow.cloud.app.controller; + +import cn.hutool.json.JSONObject; +import com.lark.oapi.core.utils.Decryptor; +import com.tashow.cloud.app.service.feishu.FeiShuCardDataService; +import com.tashow.cloud.sdk.feishu.client.FeiShuAlertClient; +import com.tashow.cloud.sdk.feishu.config.LarkConfig; +import jakarta.annotation.security.PermitAll; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.Map; + +@RestController +public class FeishuController { + private static final Logger log = LoggerFactory.getLogger(FeishuController.class); + private static final String ACTION_COMPLETE_ALARM = "complete_alarm"; + private final FeiShuAlertClient feiShuAlertClient; + private final FeiShuCardDataService feiShuCardDataService; + private final LarkConfig larkConfig; + + @Autowired + public FeishuController(FeiShuAlertClient feiShuAlertClient, FeiShuCardDataService feiShuCardDataService, LarkConfig larkConfig) { + this.feiShuAlertClient = feiShuAlertClient; + this.feiShuCardDataService = feiShuCardDataService; + this.larkConfig = larkConfig; + } + + @RequestMapping("/card") + @PermitAll + public String card(@RequestBody JSONObject data) { + try { + if (data.containsKey("app_id") && data.containsKey("action")) { + JSONObject action = data.getJSONObject("action"); + JSONObject value = action.getJSONObject("value"); + if (value != null && ACTION_COMPLETE_ALARM.equals(value.getStr("action"))) { + String messageId = data.getStr("open_message_id"); + Map templateData = feiShuCardDataService.getCardData(messageId); + templateData.put("open_id", data.getStr("open_id")); + templateData.put("complete_time", LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"))); + JSONObject fromValue = action.getJSONObject("form_value"); + templateData.put("notes", fromValue.getStr("notes_input")); + return feiShuAlertClient.buildCardWithData(larkConfig.getSuccessCards(), templateData); + } + } + if (data.containsKey("encrypt")) { + Decryptor decryptor = new Decryptor(larkConfig.getEncryptKey()); + return decryptor.decrypt(data.getStr("encrypt")); + } + return "{}"; + } catch (Exception e) { + log.error("卡片处理异常", e); + return "{\"code\":1,\"msg\":\"处理异常: " + e.getMessage() + "\"}"; + } + } + + +} diff --git a/tashow-module/tashow-module-app/src/main/java/com/tashow/cloud/app/controller/LoginController.java b/tashow-module/tashow-module-app/src/main/java/com/tashow/cloud/app/controller/LoginController.java new file mode 100644 index 0000000..6635956 --- /dev/null +++ b/tashow-module/tashow-module-app/src/main/java/com/tashow/cloud/app/controller/LoginController.java @@ -0,0 +1,5 @@ +package com.tashow.cloud.app.controller; + + +public class LoginController { +} diff --git a/tashow-module/tashow-module-app/src/main/java/com/tashow/cloud/app/controller/TestController.java b/tashow-module/tashow-module-app/src/main/java/com/tashow/cloud/app/controller/TestController.java new file mode 100644 index 0000000..017e700 --- /dev/null +++ b/tashow-module/tashow-module-app/src/main/java/com/tashow/cloud/app/controller/TestController.java @@ -0,0 +1,25 @@ +package com.tashow.cloud.app.controller; +import com.tashow.cloud.app.mapper.BuriedPointMapper; +import com.tashow.cloud.app.mq.producer.buriedPoint.BuriedPointProducer; +import jakarta.annotation.security.PermitAll; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.*; +import java.util.HashMap; +import java.util.Map; + +/** + * 测试控制器 + */ +@RestController +@RequiredArgsConstructor +public class TestController { + /** + * 基础测试接口 + */ + @GetMapping("/test") + @PermitAll + public String test() { + return "test"; + } + +} diff --git a/tashow-module/tashow-module-app/src/main/java/com/tashow/cloud/app/dal/dataobject/package-info.java b/tashow-module/tashow-module-app/src/main/java/com/tashow/cloud/app/dal/dataobject/package-info.java new file mode 100644 index 0000000..fffab69 --- /dev/null +++ b/tashow-module/tashow-module-app/src/main/java/com/tashow/cloud/app/dal/dataobject/package-info.java @@ -0,0 +1,2 @@ +package com.tashow.cloud.app.dal.dataobject; +// 数据库对象 \ No newline at end of file diff --git a/tashow-module/tashow-module-app/src/main/java/com/tashow/cloud/app/dal/dto/package-info.java b/tashow-module/tashow-module-app/src/main/java/com/tashow/cloud/app/dal/dto/package-info.java new file mode 100644 index 0000000..0cc6509 --- /dev/null +++ b/tashow-module/tashow-module-app/src/main/java/com/tashow/cloud/app/dal/dto/package-info.java @@ -0,0 +1,2 @@ +package com.tashow.cloud.app.dal.dto; +// 视图层与业务层传输对象 \ No newline at end of file diff --git a/tashow-module/tashow-module-app/src/main/java/com/tashow/cloud/app/dal/package-info.java b/tashow-module/tashow-module-app/src/main/java/com/tashow/cloud/app/dal/package-info.java new file mode 100644 index 0000000..8f128a7 --- /dev/null +++ b/tashow-module/tashow-module-app/src/main/java/com/tashow/cloud/app/dal/package-info.java @@ -0,0 +1 @@ +package com.tashow.cloud.app.dal; \ No newline at end of file diff --git a/tashow-module/tashow-module-app/src/main/java/com/tashow/cloud/app/dal/vo/package-info.java b/tashow-module/tashow-module-app/src/main/java/com/tashow/cloud/app/dal/vo/package-info.java new file mode 100644 index 0000000..94b57c5 --- /dev/null +++ b/tashow-module/tashow-module-app/src/main/java/com/tashow/cloud/app/dal/vo/package-info.java @@ -0,0 +1,2 @@ +package com.tashow.cloud.app.dal.vo; +// 视图参数接收 \ No newline at end of file diff --git a/tashow-module/tashow-module-app/src/main/java/com/tashow/cloud/app/interceptor/BuriedPointInterceptor.java b/tashow-module/tashow-module-app/src/main/java/com/tashow/cloud/app/interceptor/BuriedPointInterceptor.java new file mode 100644 index 0000000..e82ea33 --- /dev/null +++ b/tashow-module/tashow-module-app/src/main/java/com/tashow/cloud/app/interceptor/BuriedPointInterceptor.java @@ -0,0 +1,46 @@ +package com.tashow.cloud.app.interceptor; + +import cn.hutool.core.util.IdUtil; +import com.tashow.cloud.app.mq.message.BuriedMessages; +import com.tashow.cloud.app.mq.producer.buriedPoint.BuriedPointProducer; +import com.tashow.cloud.common.util.json.JsonUtils; +import com.tashow.cloud.common.util.servlet.ServletUtils; +import com.tashow.cloud.common.util.spring.SpringUtils; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.util.StopWatch; +import org.springframework.web.method.HandlerMethod; +import org.springframework.web.servlet.HandlerInterceptor; + +import java.net.InetAddress; +import java.net.UnknownHostException; + +/** + * 后端静默埋点拦截器 + * 用于收集API请求信息并异步发送到消息队列 + */ +@Slf4j +@RequiredArgsConstructor +public class BuriedPointInterceptor implements HandlerInterceptor { + + private final BuriedPointProducer buriedPointProducer; + + + + @Override + public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) { + if (!(handler instanceof HandlerMethod handlerMethod)) { + return true; + } + BuriedMessages message = new BuriedMessages( + request, + handlerMethod + ); + buriedPointProducer.asyncSendMessage(message); + return true; + } + + +} \ No newline at end of file diff --git a/tashow-module/tashow-module-app/src/main/java/com/tashow/cloud/app/mapper/BuriedPointFailRecordMapper.java b/tashow-module/tashow-module-app/src/main/java/com/tashow/cloud/app/mapper/BuriedPointFailRecordMapper.java new file mode 100644 index 0000000..d285056 --- /dev/null +++ b/tashow-module/tashow-module-app/src/main/java/com/tashow/cloud/app/mapper/BuriedPointFailRecordMapper.java @@ -0,0 +1,13 @@ +package com.tashow.cloud.app.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.tashow.cloud.app.model.MqMessageRecord; +import org.apache.ibatis.annotations.Mapper; + +/** + * 埋点消息发送记录Mapper接口 + */ +@Mapper +public interface BuriedPointFailRecordMapper extends BaseMapper { + +} \ No newline at end of file diff --git a/tashow-module/tashow-module-app/src/main/java/com/tashow/cloud/app/mapper/BuriedPointMapper.java b/tashow-module/tashow-module-app/src/main/java/com/tashow/cloud/app/mapper/BuriedPointMapper.java new file mode 100644 index 0000000..1aa5918 --- /dev/null +++ b/tashow-module/tashow-module-app/src/main/java/com/tashow/cloud/app/mapper/BuriedPointMapper.java @@ -0,0 +1,14 @@ +package com.tashow.cloud.app.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.tashow.cloud.app.model.BuriedPoint; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; +import org.apache.ibatis.annotations.Select; + +@Mapper +public interface BuriedPointMapper extends BaseMapper { + + @Select("SELECT * FROM app_burying WHERE event_id = #{eventId} LIMIT 1") + BuriedPoint selectByEventId(@Param("eventId") Integer eventId); +} \ No newline at end of file diff --git a/tashow-module/tashow-module-app/src/main/java/com/tashow/cloud/app/model/BuriedPoint.java b/tashow-module/tashow-module-app/src/main/java/com/tashow/cloud/app/model/BuriedPoint.java new file mode 100644 index 0000000..29208d1 --- /dev/null +++ b/tashow-module/tashow-module-app/src/main/java/com/tashow/cloud/app/model/BuriedPoint.java @@ -0,0 +1,128 @@ +package com.tashow.cloud.app.model; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import com.tashow.cloud.app.mq.message.BuriedMessages; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.Accessors; +import java.util.Date; + +/** + * 埋点数据实体类 + */ +@Data +@NoArgsConstructor +@Accessors(chain = true) +@TableName(value = "app_burying") +public class BuriedPoint { + /** + * 主键ID + */ + @TableId(value = "id", type = IdType.AUTO) + private Integer id; + /** + * 事件唯一ID + */ + @TableField(value = "event_id") + private Integer eventId; + + /** + * 事件时间戳 + */ + @TableField(value = "event_time") + private Long eventTime; + + /** + * 服务名称 + */ + @TableField(value = "service") + private String service; + + /** + * 方法/接口 + */ + @TableField(value = "method") + private String method; + + /** + * 用户标识 + */ + @TableField(value = "user_id") + private String userId; + + /** + * 会话标识 + */ + @TableField(value = "session_id") + private String sessionId; + + /** + * 客户端IP + */ + @TableField(value = "client_ip") + private String clientIp; + + /** + * 服务器IP + */ + @TableField(value = "server_ip") + private String serverIp; + + /** + * 事件类型 + */ + @TableField(value = "event_type") + private String eventType; + + /** + * 页面路径/功能模块 + */ + @TableField(value = "page_path") + private String pagePath; + + /** + * 元素标识 + */ + @TableField(value = "element_id") + private String elementId; + + /** + * 操作时长(毫秒) + */ + @TableField(value = "duration") + private Long duration; + + /** + * 创建时间 + */ + @TableField(value = "create_time") + private Date createTime; + + + @TableField(value = "update_time") + private Date updateTime; + + @TableField(value = "status") + private Integer status; + + + public BuriedPoint(BuriedMessages message) { + this.eventId = message.getId(); + this.eventTime = System.currentTimeMillis(); + this.userId = message.getUserId(); + this.eventType = message.getEventType(); + this.service = message.getService(); + this.method = message.getMethod(); + this.sessionId = message.getSessionId(); + this.clientIp = message.getClientIp(); + this.serverIp = message.getServerIp(); + this.status = message.getStatusCode(); + this.pagePath = message.getPagePath(); + this.elementId = message.getElementId(); + this.createTime = new Date(); + this.updateTime = new Date(); + } +} + diff --git a/tashow-module/tashow-module-app/src/main/java/com/tashow/cloud/app/model/MqMessageRecord.java b/tashow-module/tashow-module-app/src/main/java/com/tashow/cloud/app/model/MqMessageRecord.java new file mode 100644 index 0000000..9b9d899 --- /dev/null +++ b/tashow-module/tashow-module-app/src/main/java/com/tashow/cloud/app/model/MqMessageRecord.java @@ -0,0 +1,65 @@ +package com.tashow.cloud.app.model; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; + +import java.util.Date; + +/** + * 埋点消息发送失败记录实体类 + */ +@Data +@TableName("mq_message_record") +public class MqMessageRecord { + /** + * 状态常量定义 + */ + public static final int STATUS_UNPROCESSED = 10; // 未处理 + public static final int STATUS_SUCCESS = 20; // 处理成功 + public static final int STATUS_FAILED = 30; // 发送失败 + @TableId + private Integer id; + + + /** + * 交换机名称 + */ + private String exchange; + + /** + * 路由键 + */ + private String routingKey; + + /** + * 失败原因 + */ + private String cause; + + /** + * 消息内容 + */ + private String messageContent; + + /** + * 重试次数 + */ + private Integer retryCount; + + /** + * 状态:0-未处理,1-处理中,2-处理成功,3-处理失败 + */ + private Integer status; + + /** + * 创建时间 + */ + private Date createTime; + + /** + * 更新时间 + */ + private Date updateTime; +} \ No newline at end of file diff --git a/tashow-module/tashow-module-app/src/main/java/com/tashow/cloud/app/mq/config/BuriedPointConfiguration.java b/tashow-module/tashow-module-app/src/main/java/com/tashow/cloud/app/mq/config/BuriedPointConfiguration.java new file mode 100644 index 0000000..0dd5876 --- /dev/null +++ b/tashow-module/tashow-module-app/src/main/java/com/tashow/cloud/app/mq/config/BuriedPointConfiguration.java @@ -0,0 +1,78 @@ +package com.tashow.cloud.app.mq.config; + +import com.tashow.cloud.app.interceptor.BuriedPointInterceptor; +import com.tashow.cloud.app.mq.message.BuriedMessages; +import com.tashow.cloud.app.mq.producer.buriedPoint.BuriedPointProducer; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.amqp.core.*; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.servlet.config.annotation.InterceptorRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +/** + * 埋点功能配置类 + */ +@Slf4j +@Configuration +@RequiredArgsConstructor +public class BuriedPointConfiguration implements WebMvcConfigurer { + + private final BuriedPointProducer buriedPointProducer; + + /** + * 创建埋点队列 + */ + @Bean + public Queue buriedPointQueue() { + return new Queue(BuriedMessages.QUEUE, true, false, false); + } + + /** + * 创建埋点交换机 + */ + @Bean + public DirectExchange buriedPointExchange() { + return new DirectExchange(BuriedMessages.EXCHANGE, true, false); + } + + /** + * 创建埋点绑定关系 + */ + @Bean + public Binding buriedPointBinding() { + return BindingBuilder.bind(buriedPointQueue()) + .to(buriedPointExchange()) + .with(BuriedMessages.ROUTING_KEY); + } + + /** + * 创建埋点拦截器 + */ + @Bean + public BuriedPointInterceptor buriedPointInterceptor() { + return new BuriedPointInterceptor(buriedPointProducer); + } + + /** + * 注册埋点拦截器 + */ + @Override + public void addInterceptors(InterceptorRegistry registry) { + // 注册拦截器,拦截所有请求 + registry.addInterceptor(buriedPointInterceptor()) + // 可以根据需要添加或排除特定路径 + .addPathPatterns("/**") + // 排除静态资源、Swagger等路径 + .excludePathPatterns( + "/swagger-ui/**", + "/swagger-resources/**", + "/v3/api-docs/**", + "/webjars/**", + "/static/**", + "/card", + "/error" + ); + } +} \ No newline at end of file diff --git a/tashow-module/tashow-module-app/src/main/java/com/tashow/cloud/app/mq/consumer/buriedPoint/BuriedPointConsumer.java b/tashow-module/tashow-module-app/src/main/java/com/tashow/cloud/app/mq/consumer/buriedPoint/BuriedPointConsumer.java new file mode 100644 index 0000000..9dafcdb --- /dev/null +++ b/tashow-module/tashow-module-app/src/main/java/com/tashow/cloud/app/mq/consumer/buriedPoint/BuriedPointConsumer.java @@ -0,0 +1,69 @@ +package com.tashow.cloud.app.mq.consumer.buriedPoint; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.tashow.cloud.app.mapper.BuriedPointMapper; +import com.tashow.cloud.app.mapper.BuriedPointFailRecordMapper; +import com.tashow.cloud.app.model.BuriedPoint; +import com.tashow.cloud.app.model.MqMessageRecord; +import com.tashow.cloud.app.mq.message.BuriedMessages; +import com.tashow.cloud.app.service.feishu.BuriedPointMonitorService; +import com.rabbitmq.client.Channel; +import com.tashow.cloud.mq.rabbitmq.consumer.AbstractRabbitMQConsumer; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.amqp.rabbit.annotation.RabbitHandler; +import org.springframework.amqp.rabbit.annotation.RabbitListener; +import org.springframework.amqp.support.AmqpHeaders; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.messaging.handler.annotation.Header; +import org.springframework.stereotype.Component; +import java.util.Date; +import org.springframework.dao.DuplicateKeyException; +/** + * 埋点消息消费者 + */ +@Component +@RabbitListener(queues = BuriedMessages.QUEUE) +@Slf4j +@RequiredArgsConstructor +public class BuriedPointConsumer extends AbstractRabbitMQConsumer { + private final BuriedPointMapper buriedPointMapper; + private final BuriedPointMonitorService buriedPointMonitorService; + + @Value("${spring.application.name:tashow-app}") + private String applicationName; + + @RabbitHandler + public void handleMessage(BuriedMessages message, Channel channel, @Header(AmqpHeaders.DELIVERY_TAG) long deliveryTag) { + onMessage(message, channel, deliveryTag); + } + + /** + * 处理埋点消息 + * @param message 消息对象 + * @return + */ + @Override + public boolean processMessage(BuriedMessages message) { + try { + BuriedPoint existingPoint = buriedPointMapper.selectByEventId(message.getId()); + if (existingPoint != null) { + existingPoint.setStatus(message.getStatusCode()); + existingPoint.setUpdateTime(new Date()); + return buriedPointMapper.updateById(existingPoint) > 0; + } + BuriedPoint buriedPoint = new BuriedPoint(message); + buriedPoint.setService(applicationName); + buriedPointMapper.insert(buriedPoint); + + if(buriedPoint.getStatus() == BuriedMessages.STATUS_ERROR){ + buriedPointMonitorService.checkFailRecordsAndAlert("埋点数据处理异常"); + } + return true; + } catch (DuplicateKeyException e) { + return true; + } catch (Exception e) { + log.error("[埋点消费者] 保存数据失败", e); + throw e; + } + } +} diff --git a/tashow-module/tashow-module-app/src/main/java/com/tashow/cloud/app/mq/handler/BuriedPointFailRecordHandler.java b/tashow-module/tashow-module-app/src/main/java/com/tashow/cloud/app/mq/handler/BuriedPointFailRecordHandler.java new file mode 100644 index 0000000..0649573 --- /dev/null +++ b/tashow-module/tashow-module-app/src/main/java/com/tashow/cloud/app/mq/handler/BuriedPointFailRecordHandler.java @@ -0,0 +1,100 @@ +package com.tashow.cloud.app.mq.handler; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.tashow.cloud.app.mapper.BuriedPointFailRecordMapper; +import com.tashow.cloud.app.model.MqMessageRecord; +import com.tashow.cloud.app.service.feishu.BuriedPointMonitorService; +import com.tashow.cloud.mq.handler.FailRecordHandler; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; +import java.util.Date; +/** + * MQ消息记录处理器 + */ +@Slf4j +@Component +@RequiredArgsConstructor +public class BuriedPointFailRecordHandler implements FailRecordHandler { + private final BuriedPointFailRecordMapper buriedPointFailRecordMapper; + private final BuriedPointMonitorService buriedPointMonitorService; + /** + * 保存消息记录= + */ + @Override + public void saveMessageRecord(Integer id, String exchange, String routingKey, String cause, String messageContent, int status) { + try { + MqMessageRecord existingRecord = findExistingRecord(id); + if (existingRecord != null) { + existingRecord.setRetryCount(existingRecord.getRetryCount() + 1); + existingRecord.setMessageContent(messageContent); + existingRecord.setStatus(status); + existingRecord.setCause(cause); + existingRecord.setUpdateTime(new Date()); + buriedPointFailRecordMapper.updateById(existingRecord); + } else { + MqMessageRecord record = new MqMessageRecord(); + record.setId(id); + record.setExchange(exchange); + record.setRoutingKey(routingKey); + record.setCause(cause); + record.setMessageContent(messageContent); + record.setRetryCount(0); + record.setStatus(status); + record.setCreateTime(new Date()); + record.setUpdateTime(new Date()); + buriedPointFailRecordMapper.insert(record); + if (status == MqMessageRecord.STATUS_FAILED) { + buriedPointMonitorService.checkFailRecordsAndAlert(cause); + } + } + } catch (Exception e) { + log.error("[MQ消息处理器] 保存消息记录异常", e); + } + } + + /** + * 更新消息状态 + */ + @Override + public void updateMessageStatus(Integer id) { + try { + MqMessageRecord record = findExistingRecord(id); + if (record != null) { + record.setStatus(MqMessageRecord.STATUS_SUCCESS); + record.setUpdateTime(new Date()); + buriedPointFailRecordMapper.updateById(record); + } + } catch (Exception e) { + log.error("[MQ消息处理器] 更新消息状态异常: {}", id, e); + } + } + + /** + * 更新消息状态并设置失败原因 + */ + @Override + public void updateMessageStatusWithCause(Integer id, String cause) { + try { + MqMessageRecord record = findExistingRecord(id); + if (record != null) { + record.setStatus(MqMessageRecord.STATUS_FAILED); + record.setCause(cause); + record.setUpdateTime(new Date()); + buriedPointFailRecordMapper.updateById(record); + buriedPointMonitorService.checkFailRecordsAndAlert(cause); + } + } catch (Exception e) { + log.error("[MQ消息处理器] 更新消息状态和原因异常: {}", id, e); + } + } + + /** + * 查找已存在的失败记录 + */ + private MqMessageRecord findExistingRecord(Integer id) { + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + queryWrapper.eq(MqMessageRecord::getId, id); + return buriedPointFailRecordMapper.selectOne(queryWrapper); + } + +} \ No newline at end of file diff --git a/tashow-module/tashow-module-app/src/main/java/com/tashow/cloud/app/mq/message/BuriedMessages.java b/tashow-module/tashow-module-app/src/main/java/com/tashow/cloud/app/mq/message/BuriedMessages.java new file mode 100644 index 0000000..900d572 --- /dev/null +++ b/tashow-module/tashow-module-app/src/main/java/com/tashow/cloud/app/mq/message/BuriedMessages.java @@ -0,0 +1,142 @@ +package com.tashow.cloud.app.mq.message; + +import cn.hutool.core.util.IdUtil; +import com.tashow.cloud.common.util.json.JsonUtils; +import com.tashow.cloud.common.util.servlet.ServletUtils; +import com.tashow.cloud.common.util.spring.SpringUtils; +import com.tashow.cloud.mq.core.BaseMqMessage; +import jakarta.servlet.http.HttpServletRequest; +import lombok.Data; +import org.springframework.web.method.HandlerMethod; + +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.util.Date; + +import static com.tashow.cloud.web.apilog.core.interceptor.ApiAccessLogInterceptor.ATTRIBUTE_HANDLER_METHOD; + +/** + * 埋点消息 + */ +@Data +public class BuriedMessages extends BaseMqMessage { + private static final String ATTRIBUTE_REQUEST_ID = "BuriedPoint.RequestId"; + + + /** + * 交换机名称 + */ + public static final String EXCHANGE = "tashow.buried.point.exchange"; + + /** + * 队列名称 + */ + public static final String QUEUE = "tashow.buried.point.queue"; + + /** + * 路由键 + */ + public static final String ROUTING_KEY = "tashow.buried.point.routing.key"; + + /** + * 消息状态:处理中 + */ + public static final int STATUS_PROCESSING = 10; + + /** + * 消息状态:成功 + */ + public static final int STATUS_SUCCESS = 20; + + /** + * 消息状态:失败 + */ + public static final int STATUS_ERROR = 30; + + /** + * 事件时间 + */ + private Date eventTime; + + /** + * 用户ID + */ + private String userId; + + /** + * 事件类型 + */ + private String eventType; + + /** + * 方法名称 + */ + private String method; + + /** + * 会话ID + */ + private String sessionId; + + /** + * 客户端IP + */ + private String clientIp; + + /** + * 服务端IP + */ + private String serverIp; + + /** + * 页面路径 + */ + private String pagePath; + + /** + * 元素ID + */ + private String elementId; + + /** + * 服务名称 + */ + private String service; + + public BuriedMessages() { + } + + /** + * 从请求创建埋点消息 + * + * @param request HTTP请求 + * @param handlerMethod 处理方法 + */ + public BuriedMessages(HttpServletRequest request, HandlerMethod handlerMethod) { + try { + int requestId = (int)(Math.abs(IdUtil.getSnowflakeNextId()) % Integer.MAX_VALUE); + this.setId(requestId); + this.eventTime = new Date(); + this.service = SpringUtils.getApplicationName(); + this.method = request.getMethod() + " " + request.getRequestURI() + + JsonUtils.toJsonString(request.getParameterMap()); + Object userId = request.getSession().getAttribute("USER_ID"); + this.userId = userId != null ? userId.toString() : "anonymous"; + this.sessionId = request.getSession().getId(); + this.clientIp = ServletUtils.getClientIP(request); + try { + this.serverIp = InetAddress.getLocalHost().getHostAddress(); + } catch (UnknownHostException e) { + this.serverIp = "unknown"; + } + String controllerName = handlerMethod.getBeanType().getSimpleName(); + String actionName = handlerMethod.getMethod().getName(); + this.pagePath = controllerName + "#" + actionName; + this.eventType = "API_REQUEST_START"; + this.setStatusCode(STATUS_PROCESSING); + request.setAttribute(ATTRIBUTE_REQUEST_ID, this.getId()); + } catch (Exception e) { + throw new RuntimeException("创建埋点消息失败", e); + } + } +} diff --git a/tashow-module/tashow-module-app/src/main/java/com/tashow/cloud/app/mq/producer/buriedPoint/BuriedPointProducer.java b/tashow-module/tashow-module-app/src/main/java/com/tashow/cloud/app/mq/producer/buriedPoint/BuriedPointProducer.java new file mode 100644 index 0000000..5daa8b0 --- /dev/null +++ b/tashow-module/tashow-module-app/src/main/java/com/tashow/cloud/app/mq/producer/buriedPoint/BuriedPointProducer.java @@ -0,0 +1,31 @@ +package com.tashow.cloud.app.mq.producer.buriedPoint; + +import com.tashow.cloud.app.mq.message.BuriedMessages; +import com.tashow.cloud.common.util.json.JsonUtils; +import com.tashow.cloud.mq.rabbitmq.producer.AbstractRabbitMQProducer; +import org.springframework.amqp.core.ReturnedMessage; +import org.springframework.stereotype.Component; + +/** + * 埋点消息生产者 + */ +@Component +public class BuriedPointProducer extends AbstractRabbitMQProducer { + + @Override + public void returnedMessage(ReturnedMessage returned) { + + } + + @Override + public String getExchange() { + return BuriedMessages.EXCHANGE; + } + + @Override + public String getRoutingKey() { + return BuriedMessages.ROUTING_KEY; + } + + +} \ No newline at end of file diff --git a/tashow-module/tashow-module-app/src/main/java/com/tashow/cloud/app/security/config/SecurityConfiguration.java b/tashow-module/tashow-module-app/src/main/java/com/tashow/cloud/app/security/config/SecurityConfiguration.java new file mode 100644 index 0000000..dfa7532 --- /dev/null +++ b/tashow-module/tashow-module-app/src/main/java/com/tashow/cloud/app/security/config/SecurityConfiguration.java @@ -0,0 +1,44 @@ +package com.tashow.cloud.app.security.config; + +import com.tashow.cloud.infraapi.enums.ApiConstants; +import com.tashow.cloud.security.security.config.AuthorizeRequestsCustomizer; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configurers.AuthorizeHttpRequestsConfigurer; + +/** + * Infra 模块的 Security 配置 + */ +@Configuration(proxyBeanMethods = false, value = "infraSecurityConfiguration") +public class SecurityConfiguration { + + @Value("${spring.boot.admin.context-path:''}") + private String adminSeverContextPath; + + @Bean("infraAuthorizeRequestsCustomizer") + public AuthorizeRequestsCustomizer authorizeRequestsCustomizer() { + return new AuthorizeRequestsCustomizer() { + @Override + public void customize(AuthorizeHttpRequestsConfigurer.AuthorizationManagerRequestMatcherRegistry registry) { + // Spring Boot Actuator 的安全配置 + registry.requestMatchers("/actuator").permitAll() + .requestMatchers("/actuator/**").permitAll(); + // Druid 监控 + registry.requestMatchers("/druid/**").permitAll(); + // Spring Boot Admin Server 的安全配置 + registry.requestMatchers(adminSeverContextPath).permitAll() + .requestMatchers(adminSeverContextPath + "/**").permitAll(); + // 文件读取 + registry.requestMatchers(buildAdminApi("/infra/file/*/get/**")).permitAll(); + + // TODO 芋艿:这个每个项目都需要重复配置,得捉摸有没通用的方案 + // RPC 服务的安全配置 + registry.requestMatchers(ApiConstants.PREFIX + "/**").permitAll(); + } + + }; + } + +} diff --git a/tashow-module/tashow-module-app/src/main/java/com/tashow/cloud/app/security/core/package-info.java b/tashow-module/tashow-module-app/src/main/java/com/tashow/cloud/app/security/core/package-info.java new file mode 100644 index 0000000..93a4969 --- /dev/null +++ b/tashow-module/tashow-module-app/src/main/java/com/tashow/cloud/app/security/core/package-info.java @@ -0,0 +1,4 @@ +/** + * 占位 + */ +package com.tashow.cloud.app.security.core; diff --git a/tashow-module/tashow-module-app/src/main/java/com/tashow/cloud/app/service/feishu/BuriedPointMonitorService.java b/tashow-module/tashow-module-app/src/main/java/com/tashow/cloud/app/service/feishu/BuriedPointMonitorService.java new file mode 100644 index 0000000..aacad9e --- /dev/null +++ b/tashow-module/tashow-module-app/src/main/java/com/tashow/cloud/app/service/feishu/BuriedPointMonitorService.java @@ -0,0 +1,241 @@ +package com.tashow.cloud.app.service.feishu; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.tashow.cloud.app.mapper.BuriedPointFailRecordMapper; +import com.tashow.cloud.app.mapper.BuriedPointMapper; +import com.tashow.cloud.app.model.BuriedPoint; +import com.tashow.cloud.app.model.MqMessageRecord; +import com.tashow.cloud.app.mq.message.BuriedMessages; +import com.tashow.cloud.sdk.feishu.client.FeiShuAlertClient; +import com.tashow.cloud.sdk.feishu.config.LarkConfig; +import com.tashow.cloud.sdk.feishu.util.ChartImageGenerator; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import java.text.SimpleDateFormat; +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; + +/** + * 埋点监控服务 + */ +@Slf4j +@Service +@RequiredArgsConstructor +public class BuriedPointMonitorService { + private static final int ALERT_THRESHOLD = 3; + private static final int MONITORING_HOURS = 12; + private final Map alertCache = new ConcurrentHashMap<>(); + + private final BuriedPointFailRecordMapper buriedPointFailRecordMapper; + private final BuriedPointMapper buriedPointMapper; + private final FeiShuAlertClient feiShuAlertClient; + private final FeiShuCardDataService feiShuCardDataService; + private final LarkConfig larkConfig; + + /** + * 检查失败记录并发送告警 + */ + public boolean checkFailRecordsAndAlert(String cause) { + try { + Date now = new Date(); + Date hoursAgo = getDateHoursAgo(now, MONITORING_HOURS); + boolean sentAlert = false; + List timeRanges = getHourRanges(hoursAgo, now); + long mqFailCount = countFailures(buriedPointFailRecordMapper, MqMessageRecord.class, hoursAgo, now); + long buriedFailCount = countFailures(buriedPointMapper, BuriedPoint.class, hoursAgo, now); + if (mqFailCount > ALERT_THRESHOLD||buriedFailCount > ALERT_THRESHOLD) { + if (!hasRecentlySentAlert(cause)) { + sendAlert(mqFailCount, cause, getMqStats(timeRanges)); + alertCache.put(cause, System.currentTimeMillis()); + sentAlert = true; + } + } + + return sentAlert; + } catch (Exception e) { + log.error("[埋点监控] 检查失败记录异常", e); + return false; + } + } + + /** + * 检查是否最近已发送过相同类型的告警 + */ + private boolean hasRecentlySentAlert(String alertType) { + Long lastSentTime = alertCache.get(alertType); + if (lastSentTime == null) { + return false; + } + + long hourInMillis = MONITORING_HOURS * 60 * 60 * 1000L; + return (System.currentTimeMillis() - lastSentTime) < hourInMillis; + } + + + /** + * 获取消息队列统计数据 + */ + private List getMqStats(List timeRanges) { + Map successData = batchQueryMqStatus(timeRanges, MqMessageRecord.STATUS_SUCCESS); + Map failedData = batchQueryMqFailures(timeRanges); + + SimpleDateFormat timeFormat = new SimpleDateFormat("HH:00"); + return timeRanges.stream() + .map(range -> new ChartImageGenerator.MonitoringDataPoint( + timeFormat.format(range[0]), + successData.getOrDefault(range[0], 0), + failedData.getOrDefault(range[0], 0) + )) + .toList(); + } + + /** + * 获取埋点表统计数据 + */ + private List getBuriedStats(List timeRanges) { + // 批量查询每个时间区间的数据 + Map successData = batchQueryBuriedStatus(timeRanges, BuriedMessages.STATUS_SUCCESS); + Map failedData = batchQueryBuriedStatus(timeRanges, BuriedMessages.STATUS_ERROR); + SimpleDateFormat timeFormat = new SimpleDateFormat("HH:00"); + return timeRanges.stream() + .map(range -> new ChartImageGenerator.MonitoringDataPoint( + timeFormat.format(range[0]), + successData.getOrDefault(range[0], 0), + failedData.getOrDefault(range[0], 0) + )) + .toList(); + } + + /** + * 查询MQ状态数据 + */ + private Map batchQueryMqStatus(List timeRanges, int status) { + Map result = new HashMap<>(); + for (Date[] range : timeRanges) { + LambdaQueryWrapper query = new LambdaQueryWrapper<>(); + query.ge(MqMessageRecord::getCreateTime, range[0]) + .lt(MqMessageRecord::getCreateTime, range[1]) + .eq(MqMessageRecord::getStatus, status); + result.put(range[0], buriedPointFailRecordMapper.selectCount(query).intValue()); + } + return result; + } + + /** + * 批量查询MQ失败数据 + */ + private Map batchQueryMqFailures(List timeRanges) { + Map result = new HashMap<>(); + for (Date[] range : timeRanges) { + LambdaQueryWrapper query = new LambdaQueryWrapper<>(); + query.ge(MqMessageRecord::getCreateTime, range[0]) + .lt(MqMessageRecord::getCreateTime, range[1]) + .in(MqMessageRecord::getStatus, Arrays.asList( + MqMessageRecord.STATUS_UNPROCESSED, MqMessageRecord.STATUS_FAILED)); + result.put(range[0], buriedPointFailRecordMapper.selectCount(query).intValue()); + } + return result; + } + + /** + * 批量查询埋点状态数据 + */ + private Map batchQueryBuriedStatus(List timeRanges, int status) { + Map result = new HashMap<>(); + for (Date[] range : timeRanges) { + LambdaQueryWrapper query = new LambdaQueryWrapper<>(); + query.ge(BuriedPoint::getCreateTime, range[0]) + .lt(BuriedPoint::getCreateTime, range[1]) + .eq(BuriedPoint::getStatus, status); + result.put(range[0], buriedPointMapper.selectCount(query).intValue()); + } + return result; + } + + /** + * 计算失败数量 + */ + private long countFailures(Object mapper, Class entityClass, Date startDate, Date endDate) { + try { + if (entityClass == BuriedPoint.class) { + LambdaQueryWrapper query = new LambdaQueryWrapper<>(); + query.ge(BuriedPoint::getCreateTime, startDate) + .le(BuriedPoint::getCreateTime, endDate) + .eq(BuriedPoint::getStatus, BuriedMessages.STATUS_ERROR); + return ((BuriedPointMapper)mapper).selectCount(query); + } else { + LambdaQueryWrapper query = new LambdaQueryWrapper<>(); + query.ge(MqMessageRecord::getCreateTime, startDate) + .le(MqMessageRecord::getCreateTime, endDate) + .eq(MqMessageRecord::getStatus, MqMessageRecord.STATUS_FAILED); + return ((BuriedPointFailRecordMapper)mapper).selectCount(query); + } + } catch (Exception e) { + return 0; + } + } + + /** + * 发送告警 + */ + private void sendAlert(long failCount, String alertMsg, List data) { + try { + String imageKey = feiShuAlertClient.uploadImage(data, alertMsg); + String title = alertMsg.split(":")[0].trim(); + + Map templateData = Map.of( + "alert_title", title, + "image_key", Map.of("img_key", imageKey), + "current_time", new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()), + "fail_count", failCount + ); + + String messageId = feiShuAlertClient.sendCardMessage( + larkConfig.getChatId(), + larkConfig.getExceptionCards(), + new HashMap<>(templateData) + ); + feiShuCardDataService.saveCardData(messageId, templateData); + } catch (Exception e) { + log.error("[埋点监控] 发送告警失败: {}", e.getMessage()); + } + } + + /** + * 获取小时范围列表 + */ + private List getHourRanges(Date startDate, Date endDate) { + List ranges = new ArrayList<>(); + Calendar cal = Calendar.getInstance(); + + cal.setTime(endDate); + cal.set(Calendar.MINUTE, 0); + cal.set(Calendar.SECOND, 0); + cal.set(Calendar.MILLISECOND, 0); + Date endHour = cal.getTime(); + + cal.add(Calendar.HOUR_OF_DAY, -(MONITORING_HOURS - 1)); + Date startHour = startDate.after(cal.getTime()) ? startDate : cal.getTime(); + + cal.setTime(startHour); + while (!cal.getTime().after(endHour)) { + Date hourStart = cal.getTime(); + cal.add(Calendar.HOUR_OF_DAY, 1); + Date hourEnd = cal.getTime().after(endDate) ? endDate : cal.getTime(); + ranges.add(new Date[]{hourStart, hourEnd}); + + if (hourEnd.equals(endDate)) break; + } + return ranges; + } + + /** + * 获取指定时间前N小时的时间 + */ + private Date getDateHoursAgo(Date date, int hours) { + Calendar calendar = Calendar.getInstance(); + calendar.setTime(date); + calendar.add(Calendar.HOUR_OF_DAY, -hours); + return calendar.getTime(); + } +} \ No newline at end of file diff --git a/tashow-module/tashow-module-app/src/main/java/com/tashow/cloud/app/service/feishu/FeiShuCardDataService.java b/tashow-module/tashow-module-app/src/main/java/com/tashow/cloud/app/service/feishu/FeiShuCardDataService.java new file mode 100644 index 0000000..4c238cd --- /dev/null +++ b/tashow-module/tashow-module-app/src/main/java/com/tashow/cloud/app/service/feishu/FeiShuCardDataService.java @@ -0,0 +1,73 @@ +package com.tashow.cloud.app.service.feishu; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.redis.core.StringRedisTemplate; +import org.springframework.stereotype.Service; + +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.TimeUnit; + +/** + * 飞书卡片数据处理服务 + */ +@Service +public class FeiShuCardDataService { + + private static final Logger log = LoggerFactory.getLogger(FeiShuCardDataService.class); + private static final String REDIS_KEY_PREFIX = "feishu:card:"; + private static final int CARD_EXPIRATION_DAYS = 30; + + private final StringRedisTemplate stringRedisTemplate; + private final ObjectMapper objectMapper; + + @Autowired + public FeiShuCardDataService(StringRedisTemplate stringRedisTemplate, ObjectMapper objectMapper) { + this.stringRedisTemplate = stringRedisTemplate; + this.objectMapper = objectMapper; + } + + /** + * 保存卡片数据到Redis + */ + public boolean saveCardData(String messageId, Map data) { + if (messageId == null || data == null) return false; + + try { + String jsonData = objectMapper.writeValueAsString(data); + stringRedisTemplate.opsForValue().set( + REDIS_KEY_PREFIX + messageId, + jsonData, + CARD_EXPIRATION_DAYS, + TimeUnit.DAYS + ); + return true; + } catch (JsonProcessingException e) { + log.error("保存卡片数据失败: {}", e.getMessage()); + return false; + } + } + + /** + * 从Redis获取卡片数据 + */ + public Map getCardData(String messageId) { + try { + String redisKey = REDIS_KEY_PREFIX + messageId; + String jsonData = stringRedisTemplate.opsForValue().get(redisKey); + + if (jsonData == null) return new HashMap<>(); + + @SuppressWarnings("unchecked") + Map result = objectMapper.readValue(jsonData, Map.class); + return result; + } catch (Exception e) { + log.error("获取卡片数据失败: {}", e.getMessage()); + return new HashMap<>(); + } + } +} \ No newline at end of file diff --git a/tashow-module/tashow-module-app/src/main/java/com/tashow/cloud/app/service/impl/BuriedPointFailRecordService.java b/tashow-module/tashow-module-app/src/main/java/com/tashow/cloud/app/service/impl/BuriedPointFailRecordService.java new file mode 100644 index 0000000..aa92783 --- /dev/null +++ b/tashow-module/tashow-module-app/src/main/java/com/tashow/cloud/app/service/impl/BuriedPointFailRecordService.java @@ -0,0 +1,53 @@ +package com.tashow.cloud.app.service.impl; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.tashow.cloud.app.mapper.BuriedPointFailRecordMapper; +import com.tashow.cloud.app.model.MqMessageRecord; +import com.tashow.cloud.app.mq.message.BuriedMessages; +import com.tashow.cloud.app.mq.producer.buriedPoint.BuriedPointProducer; +import com.tashow.cloud.common.util.json.JsonUtils; +import com.tashow.cloud.mq.retry.MessageRetryService; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +import java.util.Date; +import java.util.List; + +/** + * 埋点失败记录服务 + */ +@Slf4j +@Service +@RequiredArgsConstructor +public class BuriedPointFailRecordService implements MessageRetryService { + + private final BuriedPointFailRecordMapper buriedPointFailRecordMapper; + private final BuriedPointProducer buriedPointProducer; + + /** + * 获取未处理的失败记录 + */ + @Override + public List getUnprocessedRecords() { + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + queryWrapper.eq(MqMessageRecord::getStatus, MqMessageRecord.STATUS_FAILED) + .orderByAsc(MqMessageRecord::getCreateTime); + return buriedPointFailRecordMapper.selectList(queryWrapper); + } + + /** + * 重试失败消息 + */ + @Override + public void retryFailedMessage(Integer recordId) { + try { + Long id = Long.valueOf(recordId); + MqMessageRecord record = buriedPointFailRecordMapper.selectById(id); + BuriedMessages message = JsonUtils.parseObject(record.getMessageContent(), BuriedMessages.class); + buriedPointProducer.asyncSendMessage(message); + } catch (Exception e) { + log.error("[埋点重试] 重试失败", e); + } + } +} \ No newline at end of file diff --git a/tashow-module/tashow-module-app/src/main/java/com/tashow/cloud/app/task/BuriedPointRetryTask.java b/tashow-module/tashow-module-app/src/main/java/com/tashow/cloud/app/task/BuriedPointRetryTask.java new file mode 100644 index 0000000..aeccd90 --- /dev/null +++ b/tashow-module/tashow-module-app/src/main/java/com/tashow/cloud/app/task/BuriedPointRetryTask.java @@ -0,0 +1,41 @@ +package com.tashow.cloud.app.task; +import com.tashow.cloud.app.model.MqMessageRecord; +import com.tashow.cloud.app.service.impl.BuriedPointFailRecordService; +import com.tashow.cloud.mq.retry.AbstractMessageRetryTask; +import com.tashow.cloud.mq.retry.MessageRetryService; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Component; + +/** + * 埋点消息重试定时任务 + */ +@Slf4j +@Component +@RequiredArgsConstructor +public class BuriedPointRetryTask extends AbstractMessageRetryTask { + + private final BuriedPointFailRecordService buriedPointFailRecordService; + + /** + * 定时重试失败消息 + * 每天凌晨执行一次 + */ + @Scheduled(cron = "0 0 0 * * ?") + // @Scheduled(cron = "0/10 * * * * ?") + public void execute() { + retryFailedMessages(); + } + + @Override + protected MessageRetryService getMessageRetryService() { + return buriedPointFailRecordService; + } + @Override + protected Integer getRecordId(MqMessageRecord record) { + return record.getId(); + } + + +} \ No newline at end of file diff --git a/tashow-module/tashow-module-app/src/main/resources/application-local.yaml b/tashow-module/tashow-module-app/src/main/resources/application-local.yaml new file mode 100644 index 0000000..79777cb --- /dev/null +++ b/tashow-module/tashow-module-app/src/main/resources/application-local.yaml @@ -0,0 +1,16 @@ +--- #################### 注册中心 + 配置中心相关配置 #################### + +spring: + cloud: + nacos: + server-addr: 43.139.42.137:8848 # Nacos 服务器地址 + username: nacos # Nacos 账号 + password: nacos # Nacos 密码 + discovery: # 【配置中心】配置项 + namespace: 5c8b8fe6-9a89-4ae3-975e-ef3bf560ff82 # 命名空间。这里使用 dev 开发环境 + group: DEFAULT_GROUP # 使用的 Nacos 配置分组,默认为 DEFAULT_GROUP + metadata: + version: 1.0.0 # 服务实例的版本号,可用于灰度发布 + config: # 【注册中心】配置项 + namespace: 5c8b8fe6-9a89-4ae3-975e-ef3bf560ff82 # 命名空间。这里使用 dev 开发环境 + group: DEFAULT_GROUP # 使用的 Nacos 配置分组,默认为 DEFAULT_GROUP diff --git a/tashow-module/tashow-module-app/src/main/resources/application.yaml b/tashow-module/tashow-module-app/src/main/resources/application.yaml new file mode 100644 index 0000000..e9de0d5 --- /dev/null +++ b/tashow-module/tashow-module-app/src/main/resources/application.yaml @@ -0,0 +1,12 @@ +server: + port: 48083 +spring: + application: + name: app-server + profiles: + active: local + config: + import: + - optional:classpath:application-${spring.profiles.active}.yaml # 加载【本地】配置 + - optional:nacos:application.yaml # 加载【Nacos】的配置 + - optional:nacos:${spring.application.name}-${spring.profiles.active}.yaml # 加载【Nacos】的配置 diff --git a/tashow-module/tashow-module-infra/Dockerfile b/tashow-module/tashow-module-infra/Dockerfile new file mode 100644 index 0000000..f412161 --- /dev/null +++ b/tashow-module/tashow-module-infra/Dockerfile @@ -0,0 +1,19 @@ +## AdoptOpenJDK 停止发布 OpenJDK 二进制,而 Eclipse Temurin 是它的延伸,提供更好的稳定性 +## 感谢复旦核博士的建议!灰子哥,牛皮! +FROM eclipse-temurin:17-jre + +## 创建目录,并使用它作为工作目录 +RUN mkdir -p /home/java-work/system-infra +WORKDIR /home/java-work/system-infra +## 将后端项目的 Jar 文件,复制到镜像中 +COPY ./target/tashow-module-infra.jar app.jar + +## 设置 TZ 时区 +## 设置 JAVA_OPTS 环境变量,可通过 docker run -e "JAVA_OPTS=" 进行覆盖 +ENV TZ=Asia/Shanghai JAVA_OPTS="-Xms512m -Xmx512m" + +## 暴露后端项目的 48080 端口 +EXPOSE 48082 + +## 启动后端项目 +CMD java ${JAVA_OPTS} -Djava.security.egd=file:/dev/./urandom -jar app.jar diff --git a/tashow-module/tashow-module-infra/pom.xml b/tashow-module/tashow-module-infra/pom.xml new file mode 100644 index 0000000..b6b9120 --- /dev/null +++ b/tashow-module/tashow-module-infra/pom.xml @@ -0,0 +1,179 @@ + + + 4.0.0 + + com.tashow.cloud + tashow-module + ${revision} + + tashow-module-infra + jar + + ${project.artifactId} + + infra 模块,主要提供两块能力: + 1. 我们放基础设施的运维与管理,支撑上层的通用与核心业务。 例如说:定时任务的管理、服务器的信息等等 + 2. 研发工具,提升研发效率与质量。 例如说:代码生成器、接口文档等等 + + + + + + com.tashow.cloud + tashow-framework-env + + + + + com.tashow.cloud + tashow-system-api + ${revision} + + + com.tashow.cloud + tashow-infra-api + ${revision} + + + + + com.alibaba.otter + canal.client + 1.1.0 + + + + + + com.tashow.cloud + tashow-framework-tenant + + + + + com.tashow.cloud + tashow-framework-security + + + + com.tashow.cloud + tashow-framework-websocket + + + + + com.tashow.cloud + tashow-data-mybatis + + + com.baomidou + mybatis-plus-generator + + + + com.tashow.cloud + tashow-data-redis + + + + + + + com.tashow.cloud + tashow-framework-rpc + + + + + com.alibaba.cloud + spring-cloud-starter-alibaba-nacos-discovery + + + + + com.alibaba.cloud + spring-cloud-starter-alibaba-nacos-config + + + + + com.tashow.cloud + tashow-framework-job + + + + + com.tashow.cloud + tashow-framework-mq + + + + + com.tashow.cloud + tashow-data-excel + + + + org.apache.velocity + velocity-engine-core + + + + + com.tashow.cloud + tashow-framework-monitor + + + + de.codecentric + spring-boot-admin-starter-server + + + + + commons-net + commons-net + + + com.jcraft + jsch + + + com.amazonaws + aws-java-sdk-s3 + + + + org.apache.tika + tika-core + + + + + + ${project.artifactId} + + + + org.springframework.boot + spring-boot-maven-plugin + ${spring.boot.version} + + + + repackage + + + + + + + + diff --git a/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/InfraServerApplication.java b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/InfraServerApplication.java new file mode 100644 index 0000000..73eea08 --- /dev/null +++ b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/InfraServerApplication.java @@ -0,0 +1,17 @@ +package com.tashow.cloud.infra; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +/** + * 项目的启动类 + * @author 芋道源码 + */ +@SpringBootApplication +public class InfraServerApplication { + + public static void main(String[] args) { + SpringApplication.run(InfraServerApplication.class, args); + } + +} diff --git a/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/api/config/ConfigApiImpl.java b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/api/config/ConfigApiImpl.java new file mode 100644 index 0000000..82f7268 --- /dev/null +++ b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/api/config/ConfigApiImpl.java @@ -0,0 +1,27 @@ +package com.tashow.cloud.infra.api.config; + +import com.tashow.cloud.common.pojo.CommonResult; +import com.tashow.cloud.infra.service.config.ConfigService; +import com.tashow.cloud.infraapi.api.config.ConfigApi; +import com.tashow.cloud.infra.dal.dataobject.config.ConfigDO; +import jakarta.annotation.Resource; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.RestController; + +import static com.tashow.cloud.common.pojo.CommonResult.success; + + +@RestController // 提供 RESTful API 接口,给 Feign 调用 +@Validated +public class ConfigApiImpl implements ConfigApi { + + @Resource + private ConfigService configService; + + @Override + public CommonResult getConfigValueByKey(String key) { + ConfigDO config = configService.getConfigByKey(key); + return success(config != null ? config.getValue() : null); + } + +} diff --git a/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/api/file/FileApiImpl.java b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/api/file/FileApiImpl.java new file mode 100644 index 0000000..cb57d40 --- /dev/null +++ b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/api/file/FileApiImpl.java @@ -0,0 +1,28 @@ +package com.tashow.cloud.infra.api.file; + +import com.tashow.cloud.common.pojo.CommonResult; +import com.tashow.cloud.infra.service.file.FileService; +import com.tashow.cloud.infraapi.api.file.FileApi; +import com.tashow.cloud.infraapi.api.file.dto.FileCreateReqDTO; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.RestController; + +import jakarta.annotation.Resource; + +import static com.tashow.cloud.common.pojo.CommonResult.success; + + +@RestController // 提供 RESTful API 接口,给 Feign 调用 +@Validated +public class FileApiImpl implements FileApi { + + @Resource + private FileService fileService; + + @Override + public CommonResult createFile(FileCreateReqDTO createReqDTO) { + return success(fileService.createFile(createReqDTO.getName(), createReqDTO.getPath(), + createReqDTO.getContent())); + } + +} diff --git a/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/api/logger/ApiAccessLogApiImpl.java b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/api/logger/ApiAccessLogApiImpl.java new file mode 100644 index 0000000..00effcd --- /dev/null +++ b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/api/logger/ApiAccessLogApiImpl.java @@ -0,0 +1,28 @@ +package com.tashow.cloud.infra.api.logger; + +import com.tashow.cloud.common.pojo.CommonResult; +import com.tashow.cloud.infra.service.logger.ApiAccessLogService; +import com.tashow.cloud.infraapi.api.logger.ApiAccessLogApi; +import com.tashow.cloud.infraapi.api.logger.dto.ApiAccessLogCreateReqDTO; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.RestController; + +import jakarta.annotation.Resource; + +import static com.tashow.cloud.common.pojo.CommonResult.success; + + +@RestController // 提供 RESTful API 接口,给 Feign 调用 +@Validated +public class ApiAccessLogApiImpl implements ApiAccessLogApi { + + @Resource + private ApiAccessLogService apiAccessLogService; + + @Override + public CommonResult createApiAccessLog(ApiAccessLogCreateReqDTO createDTO) { + apiAccessLogService.createApiAccessLog(createDTO); + return success(true); + } + +} diff --git a/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/api/logger/ApiErrorLogApiImpl.java b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/api/logger/ApiErrorLogApiImpl.java new file mode 100644 index 0000000..24d6e56 --- /dev/null +++ b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/api/logger/ApiErrorLogApiImpl.java @@ -0,0 +1,27 @@ +package com.tashow.cloud.infra.api.logger; + + +import com.tashow.cloud.common.pojo.CommonResult; +import com.tashow.cloud.infra.service.logger.ApiErrorLogService; +import com.tashow.cloud.infraapi.api.logger.ApiErrorLogApi; +import com.tashow.cloud.infraapi.api.logger.dto.ApiErrorLogCreateReqDTO; +import jakarta.annotation.Resource; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.RestController; + +import static com.tashow.cloud.common.pojo.CommonResult.success; + +@RestController // 提供 RESTful API 接口,给 Feign 调用 +@Validated +public class ApiErrorLogApiImpl implements ApiErrorLogApi { + + @Resource + private ApiErrorLogService apiErrorLogService; + + @Override + public CommonResult createApiErrorLog(ApiErrorLogCreateReqDTO createDTO) { + apiErrorLogService.createApiErrorLog(createDTO); + return success(true); + } + +} diff --git a/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/api/package-info.java b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/api/package-info.java new file mode 100644 index 0000000..b389e58 --- /dev/null +++ b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/api/package-info.java @@ -0,0 +1 @@ +package com.tashow.cloud.infra.api; diff --git a/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/api/websocket/WebSocketSenderApiImpl.java b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/api/websocket/WebSocketSenderApiImpl.java new file mode 100644 index 0000000..334748a --- /dev/null +++ b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/api/websocket/WebSocketSenderApiImpl.java @@ -0,0 +1,36 @@ +package com.tashow.cloud.infra.api.websocket; + + +import com.tashow.cloud.common.pojo.CommonResult; +import com.tashow.cloud.infraapi.api.websocket.WebSocketSenderApi; +import com.tashow.cloud.infraapi.api.websocket.dto.WebSocketSendReqDTO; +import com.tashow.cloud.websocket.core.sender.WebSocketMessageSender; +import jakarta.annotation.Resource; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.RestController; + +import static com.tashow.cloud.common.pojo.CommonResult.success; + +@RestController // 提供 RESTful API 接口,给 Feign 调用 +@Validated +public class WebSocketSenderApiImpl implements WebSocketSenderApi { +// + @Resource + private WebSocketMessageSender webSocketMessageSender; + + @Override + public CommonResult send(WebSocketSendReqDTO message) { +// if (StrUtil.isNotEmpty(message.getSessionId())) { +// webSocketMessageSender.send(message.getSessionId(), +// message.getMessageType(), message.getMessageContent()); +// } else if (message.getUserType() != null && message.getUserId() != null) { +// webSocketMessageSender.send(message.getUserType(), message.getUserId(), +// message.getMessageType(), message.getMessageContent()); +// } else if (message.getUserType() != null) { +// webSocketMessageSender.send(message.getUserType(), +// message.getMessageType(), message.getMessageContent()); +// } + return success(true); + } + +} diff --git a/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/controller/admin/codegen/CodegenController.java b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/controller/admin/codegen/CodegenController.java new file mode 100644 index 0000000..df46f03 --- /dev/null +++ b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/controller/admin/codegen/CodegenController.java @@ -0,0 +1,138 @@ +package com.tashow.cloud.infra.controller.admin.codegen; + +import static com.tashow.cloud.common.pojo.CommonResult.success; +import static com.tashow.cloud.infra.framework.file.core.utils.FileTypeUtils.writeAttachment; +import static com.tashow.cloud.security.security.core.util.SecurityFrameworkUtils.getLoginUserId; + +import cn.hutool.core.io.IoUtil; +import cn.hutool.core.util.ZipUtil; +import com.tashow.cloud.common.pojo.CommonResult; +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.common.util.object.BeanUtils; +import com.tashow.cloud.infra.controller.admin.codegen.vo.CodegenCreateListReqVO; +import com.tashow.cloud.infra.controller.admin.codegen.vo.CodegenDetailRespVO; +import com.tashow.cloud.infra.controller.admin.codegen.vo.CodegenPreviewRespVO; +import com.tashow.cloud.infra.controller.admin.codegen.vo.CodegenUpdateReqVO; +import com.tashow.cloud.infra.controller.admin.codegen.vo.table.CodegenTablePageReqVO; +import com.tashow.cloud.infra.controller.admin.codegen.vo.table.CodegenTableRespVO; +import com.tashow.cloud.infra.controller.admin.codegen.vo.table.DatabaseTableRespVO; +import com.tashow.cloud.infra.convert.codegen.CodegenConvert; +import com.tashow.cloud.infra.dal.dataobject.codegen.CodegenColumnDO; +import com.tashow.cloud.infra.dal.dataobject.codegen.CodegenTableDO; +import com.tashow.cloud.infra.service.codegen.CodegenService; +import jakarta.annotation.Resource; +import jakarta.servlet.http.HttpServletResponse; +import jakarta.validation.Valid; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.List; +import java.util.Map; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +/** 管理后台 - 代码生成器 */ +@RestController +@RequestMapping("/infra/codegen") +@Validated +public class CodegenController { + + @Resource private CodegenService codegenService; + + /** 获得数据库自带的表定义列表", description = "会过滤掉已经导入 Codegen 的表 */ + @GetMapping("/db/table/list") + @PreAuthorize("@ss.hasPermission('infra:codegen:query')") + public CommonResult> getDatabaseTableList( + @RequestParam(value = "dataSourceConfigId") Long dataSourceConfigId, + @RequestParam(value = "name", required = false) String name, + @RequestParam(value = "comment", required = false) String comment) { + return success(codegenService.getDatabaseTableList(dataSourceConfigId, name, comment)); + } + + /** 获得表定义列表 */ + @GetMapping("/table/list") + @PreAuthorize("@ss.hasPermission('infra:codegen:query')") + public CommonResult> getCodegenTableList( + @RequestParam(value = "dataSourceConfigId") Long dataSourceConfigId) { + List list = codegenService.getCodegenTableList(dataSourceConfigId); + return success(BeanUtils.toBean(list, CodegenTableRespVO.class)); + } + + /** 获得表定义分页 */ + @GetMapping("/table/page") + @PreAuthorize("@ss.hasPermission('infra:codegen:query')") + public CommonResult> getCodegenTablePage( + @Valid CodegenTablePageReqVO pageReqVO) { + PageResult pageResult = codegenService.getCodegenTablePage(pageReqVO); + return success(BeanUtils.toBean(pageResult, CodegenTableRespVO.class)); + } + + /** 获得表和字段的明细 */ + @GetMapping("/detail") + @PreAuthorize("@ss.hasPermission('infra:codegen:query')") + public CommonResult getCodegenDetail(@RequestParam("tableId") Long tableId) { + CodegenTableDO table = codegenService.getCodegenTable(tableId); + List columns = codegenService.getCodegenColumnListByTableId(tableId); + // 拼装返回 + return success(CodegenConvert.INSTANCE.convert(table, columns)); + } + + /** 基于数据库的表结构,创建代码生成器的表和字段定义 */ + @PostMapping("/create-list") + @PreAuthorize("@ss.hasPermission('infra:codegen:create')") + public CommonResult> createCodegenList( + @Valid @RequestBody CodegenCreateListReqVO reqVO) { + return success(codegenService.createCodegenList(getLoginUserId(), reqVO)); + } + + /** 更新数据库的表和字段定义 */ + @PutMapping("/update") + @PreAuthorize("@ss.hasPermission('infra:codegen:update')") + public CommonResult updateCodegen(@Valid @RequestBody CodegenUpdateReqVO updateReqVO) { + codegenService.updateCodegen(updateReqVO); + return success(true); + } + + /** 基于数据库的表结构,同步数据库的表和字段定义 */ + @PutMapping("/sync-from-db") + @PreAuthorize("@ss.hasPermission('infra:codegen:update')") + public CommonResult syncCodegenFromDB(@RequestParam("tableId") Long tableId) { + codegenService.syncCodegenFromDB(tableId); + return success(true); + } + + /** 删除数据库的表和字段定义 */ + @DeleteMapping("/delete") + @PreAuthorize("@ss.hasPermission('infra:codegen:delete')") + public CommonResult deleteCodegen(@RequestParam("tableId") Long tableId) { + codegenService.deleteCodegen(tableId); + return success(true); + } + + /** 预览生成代码 */ + @GetMapping("/preview") + @PreAuthorize("@ss.hasPermission('infra:codegen:preview')") + public CommonResult> previewCodegen( + @RequestParam("tableId") Long tableId) { + Map codes = codegenService.generationCodes(tableId); + return success(CodegenConvert.INSTANCE.convert(codes)); + } + + /** 下载生成代码 */ + @GetMapping("/download") + @PreAuthorize("@ss.hasPermission('infra:codegen:download')") + public void downloadCodegen(@RequestParam("tableId") Long tableId, HttpServletResponse response) + throws IOException { + // 生成代码 + Map codes = codegenService.generationCodes(tableId); + // 构建 zip 包 + String[] paths = codes.keySet().toArray(new String[0]); + ByteArrayInputStream[] ins = + codes.values().stream().map(IoUtil::toUtf8Stream).toArray(ByteArrayInputStream[]::new); + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + ZipUtil.zip(outputStream, paths, ins); + // 输出 + writeAttachment(response, "codegen.zip", outputStream.toByteArray()); + } +} diff --git a/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/controller/admin/codegen/vo/CodegenCreateListReqVO.java b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/controller/admin/codegen/vo/CodegenCreateListReqVO.java new file mode 100644 index 0000000..3503131 --- /dev/null +++ b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/controller/admin/codegen/vo/CodegenCreateListReqVO.java @@ -0,0 +1,18 @@ +package com.tashow.cloud.infra.controller.admin.codegen.vo; + +import jakarta.validation.constraints.NotNull; +import java.util.List; +import lombok.Data; + +/** 管理后台 - 基于数据库的表结构,创建代码生成器的表和字段定义 Request VO */ +@Data +public class CodegenCreateListReqVO { + + /** 数据源配置的编号" */ + @NotNull(message = "数据源配置的编号不能为空") + private Long dataSourceConfigId; + + /** 表名数组", example = "[1, 2, 3] */ + @NotNull(message = "表名数组不能为空") + private List tableNames; +} diff --git a/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/controller/admin/codegen/vo/CodegenDetailRespVO.java b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/controller/admin/codegen/vo/CodegenDetailRespVO.java new file mode 100644 index 0000000..e2fde11 --- /dev/null +++ b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/controller/admin/codegen/vo/CodegenDetailRespVO.java @@ -0,0 +1,17 @@ +package com.tashow.cloud.infra.controller.admin.codegen.vo; + +import com.tashow.cloud.infra.controller.admin.codegen.vo.column.CodegenColumnRespVO; +import com.tashow.cloud.infra.controller.admin.codegen.vo.table.CodegenTableRespVO; +import java.util.List; +import lombok.Data; + +/** 管理后台 - 代码生成表和字段的明细 Response VO */ +@Data +public class CodegenDetailRespVO { + + /** 表定义 */ + private CodegenTableRespVO table; + + /** 字段定义 */ + private List columns; +} diff --git a/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/controller/admin/codegen/vo/CodegenPreviewRespVO.java b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/controller/admin/codegen/vo/CodegenPreviewRespVO.java new file mode 100644 index 0000000..ca11ce1 --- /dev/null +++ b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/controller/admin/codegen/vo/CodegenPreviewRespVO.java @@ -0,0 +1,14 @@ +package com.tashow.cloud.infra.controller.admin.codegen.vo; + +import lombok.Data; + +/** 管理后台 - 代码生成预览 Response VO,注意,每个文件都是一个该对象 */ +@Data +public class CodegenPreviewRespVO { + + /** 文件路径" */ + private String filePath; + + /** 代码" */ + private String code; +} diff --git a/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/controller/admin/codegen/vo/CodegenUpdateReqVO.java b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/controller/admin/codegen/vo/CodegenUpdateReqVO.java new file mode 100644 index 0000000..4b66bdf --- /dev/null +++ b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/controller/admin/codegen/vo/CodegenUpdateReqVO.java @@ -0,0 +1,21 @@ +package com.tashow.cloud.infra.controller.admin.codegen.vo; + +import com.tashow.cloud.infra.controller.admin.codegen.vo.column.CodegenColumnSaveReqVO; +import com.tashow.cloud.infra.controller.admin.codegen.vo.table.CodegenTableSaveReqVO; +import jakarta.validation.Valid; +import jakarta.validation.constraints.NotNull; +import java.util.List; +import lombok.Data; + +/** 管理后台 - 代码生成表和字段的修改 Request VO */ +@Data +public class CodegenUpdateReqVO { + + @Valid // 校验内嵌的字段 + @NotNull(message = "表定义不能为空") + private CodegenTableSaveReqVO table; + + @Valid // 校验内嵌的字段 + @NotNull(message = "字段定义不能为空") + private List columns; +} diff --git a/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/controller/admin/codegen/vo/column/CodegenColumnRespVO.java b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/controller/admin/codegen/vo/column/CodegenColumnRespVO.java new file mode 100644 index 0000000..79faa70 --- /dev/null +++ b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/controller/admin/codegen/vo/column/CodegenColumnRespVO.java @@ -0,0 +1,66 @@ +package com.tashow.cloud.infra.controller.admin.codegen.vo.column; + +import java.time.LocalDateTime; +import lombok.Data; + +/** 管理后台 - 代码生成字段定义 Response VO */ +@Data +public class CodegenColumnRespVO { + + /** 编号" */ + private Long id; + + /** 表编号" */ + private Long tableId; + + /** 字段名" */ + private String columnName; + + /** 字段类型" */ + private String dataType; + + /** 字段描述", example = "年龄 */ + private String columnComment; + + /** 是否允许为空" */ + private Boolean nullable; + + /** 是否主键" */ + private Boolean primaryKey; + + /** 排序" */ + private Integer ordinalPosition; + + /** Java 属性类型" */ + private String javaType; + + /** Java 属性名" */ + private String javaField; + + /** 字典类型 */ + private String dictType; + + /** 数据示例 */ + private String example; + + /** 是否为 Create 创建操作的字段" */ + private Boolean createOperation; + + /** 是否为 Update 更新操作的字段" */ + private Boolean updateOperation; + + /** 是否为 List 查询操作的字段" */ + private Boolean listOperation; + + /** List 查询操作的条件类型,参见 CodegenColumnListConditionEnum 枚举" */ + private String listOperationCondition; + + /** 是否为 List 查询操作的返回字段" */ + private Boolean listOperationResult; + + /** 显示类型" */ + private String htmlType; + + /** 创建时间 */ + private LocalDateTime createTime; +} diff --git a/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/controller/admin/codegen/vo/column/CodegenColumnSaveReqVO.java b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/controller/admin/codegen/vo/column/CodegenColumnSaveReqVO.java new file mode 100644 index 0000000..3189fbe --- /dev/null +++ b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/controller/admin/codegen/vo/column/CodegenColumnSaveReqVO.java @@ -0,0 +1,78 @@ +package com.tashow.cloud.infra.controller.admin.codegen.vo.column; + +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +/** 管理后台 - 代码生成字段定义创建/修改 Request VO */ +@Data +public class CodegenColumnSaveReqVO { + + /** 编号" */ + private Long id; + + /** 表编号" */ + @NotNull(message = "表编号不能为空") + private Long tableId; + + /** 字段名" */ + @NotNull(message = "字段名不能为空") + private String columnName; + + /** 字段类型" */ + @NotNull(message = "字段类型不能为空") + private String dataType; + + /** 字段描述", example = "年龄 */ + @NotNull(message = "字段描述不能为空") + private String columnComment; + + /** 是否允许为空" */ + @NotNull(message = "是否允许为空不能为空") + private Boolean nullable; + + /** 是否主键" */ + @NotNull(message = "是否主键不能为空") + private Boolean primaryKey; + + /** 排序" */ + @NotNull(message = "排序不能为空") + private Integer ordinalPosition; + + /** Java 属性类型" */ + @NotNull(message = "Java 属性类型不能为空") + private String javaType; + + /** Java 属性名" */ + @NotNull(message = "Java 属性名不能为空") + private String javaField; + + /** 字典类型 */ + private String dictType; + + /** 数据示例 */ + private String example; + + /** 是否为 Create 创建操作的字段" */ + @NotNull(message = "是否为 Create 创建操作的字段不能为空") + private Boolean createOperation; + + /** 是否为 Update 更新操作的字段" */ + @NotNull(message = "是否为 Update 更新操作的字段不能为空") + private Boolean updateOperation; + + /** 是否为 List 查询操作的字段" */ + @NotNull(message = "是否为 List 查询操作的字段不能为空") + private Boolean listOperation; + + /** List 查询操作的条件类型,参见 CodegenColumnListConditionEnum 枚举" */ + @NotNull(message = "List 查询操作的条件类型不能为空") + private String listOperationCondition; + + /** 是否为 List 查询操作的返回字段" */ + @NotNull(message = "是否为 List 查询操作的返回字段不能为空") + private Boolean listOperationResult; + + /** 显示类型" */ + @NotNull(message = "显示类型不能为空") + private String htmlType; +} diff --git a/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/controller/admin/codegen/vo/table/CodegenTablePageReqVO.java b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/controller/admin/codegen/vo/table/CodegenTablePageReqVO.java new file mode 100644 index 0000000..b580f1a --- /dev/null +++ b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/controller/admin/codegen/vo/table/CodegenTablePageReqVO.java @@ -0,0 +1,30 @@ +package com.tashow.cloud.infra.controller.admin.codegen.vo.table; + +import static com.tashow.cloud.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND; + +import com.tashow.cloud.common.pojo.PageParam; +import java.time.LocalDateTime; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; +import org.springframework.format.annotation.DateTimeFormat; + +/** 管理后台 - 表定义分页 Request VO */ +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class CodegenTablePageReqVO extends PageParam { + + /** 表名称,模糊匹配 */ + private String tableName; + + /** 表描述,模糊匹配 */ + private String tableComment; + + /** 实体,模糊匹配 */ + private String className; + + /** 创建时间 */ + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) + private LocalDateTime[] createTime; +} diff --git a/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/controller/admin/codegen/vo/table/CodegenTableRespVO.java b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/controller/admin/codegen/vo/table/CodegenTableRespVO.java new file mode 100644 index 0000000..670410e --- /dev/null +++ b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/controller/admin/codegen/vo/table/CodegenTableRespVO.java @@ -0,0 +1,72 @@ +package com.tashow.cloud.infra.controller.admin.codegen.vo.table; + +import java.time.LocalDateTime; +import lombok.Data; + +/** 管理后台 - 代码生成表定义 Response VO */ +@Data +public class CodegenTableRespVO { + + /** 编号" */ + private Long id; + + /** 生成场景,参见 CodegenSceneEnum 枚举" */ + private Integer scene; + + /** 表名称" */ + private String tableName; + + /** 表描述", example = "芋道 */ + private String tableComment; + + /** 备注 */ + private String remark; + + /** 模块名" */ + private String moduleName; + + /** 业务名" */ + private String businessName; + + /** 类名称" */ + private String className; + + /** 类描述", example = "代码生成器的表定义 */ + private String classComment; + + /** 作者", example = "芋道源码 */ + private String author; + + /** 模板类型,参见 CodegenTemplateTypeEnum 枚举" */ + private Integer templateType; + + /** 前端类型,参见 CodegenFrontTypeEnum 枚举" */ + private Integer frontType; + + /** 父菜单编号 */ + private Long parentMenuId; + + /** 主表的编号 */ + private Long masterTableId; + + /** 子表关联主表的字段编号 */ + private Long subJoinColumnId; + + /** 主表与子表是否一对多 */ + private Boolean subJoinMany; + + /** 树表的父字段编号 */ + private Long treeParentColumnId; + + /** 树表的名字字段编号 */ + private Long treeNameColumnId; + + /** 主键编号" */ + private Integer dataSourceConfigId; + + /** 创建时间 */ + private LocalDateTime createTime; + + /** 更新时间 */ + private LocalDateTime updateTime; +} diff --git a/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/controller/admin/codegen/vo/table/CodegenTableSaveReqVO.java b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/controller/admin/codegen/vo/table/CodegenTableSaveReqVO.java new file mode 100644 index 0000000..e6d46cb --- /dev/null +++ b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/controller/admin/codegen/vo/table/CodegenTableSaveReqVO.java @@ -0,0 +1,100 @@ +package com.tashow.cloud.infra.controller.admin.codegen.vo.table; + +import cn.hutool.core.util.ObjectUtil; +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.tashow.cloud.infra.enums.codegen.CodegenSceneEnum; +import com.tashow.cloud.infra.enums.codegen.CodegenTemplateTypeEnum; +import jakarta.validation.constraints.AssertTrue; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +/** 管理后台 - 代码生成表定义创建/修改 Response VO */ +@Data +public class CodegenTableSaveReqVO { + + /** 编号" */ + private Long id; + + /** 生成场景,参见 CodegenSceneEnum 枚举" */ + @NotNull(message = "导入类型不能为空") + private Integer scene; + + /** 表名称" */ + @NotNull(message = "表名称不能为空") + private String tableName; + + /** 表描述", example = "芋道 */ + @NotNull(message = "表描述不能为空") + private String tableComment; + + /** 备注 */ + private String remark; + + /** 模块名" */ + @NotNull(message = "模块名不能为空") + private String moduleName; + + /** 业务名" */ + @NotNull(message = "业务名不能为空") + private String businessName; + + /** 类名称" */ + @NotNull(message = "类名称不能为空") + private String className; + + /** 类描述", example = "代码生成器的表定义 */ + @NotNull(message = "类描述不能为空") + private String classComment; + + /** 作者", example = "芋道源码 */ + @NotNull(message = "作者不能为空") + private String author; + + /** 模板类型,参见 CodegenTemplateTypeEnum 枚举" */ + @NotNull(message = "模板类型不能为空") + private Integer templateType; + + /** 前端类型,参见 CodegenFrontTypeEnum 枚举" */ + @NotNull(message = "前端类型不能为空") + private Integer frontType; + + /** 父菜单编号 */ + private Long parentMenuId; + + /** 主表的编号 */ + private Long masterTableId; + + /** 子表关联主表的字段编号 */ + private Long subJoinColumnId; + + /** 主表与子表是否一对多 */ + private Boolean subJoinMany; + + /** 树表的父字段编号 */ + private Long treeParentColumnId; + + /** 树表的名字字段编号 */ + private Long treeNameColumnId; + + @AssertTrue(message = "上级菜单不能为空,请前往 [修改生成配置 -> 生成信息] 界面,设置“上级菜单”字段") + @JsonIgnore + public boolean isParentMenuIdValid() { + // 生成场景为管理后台时,必须设置上级菜单,不然生成的菜单 SQL 是无父级菜单的 + return ObjectUtil.notEqual(getScene(), CodegenSceneEnum.ADMIN.getScene()) + || getParentMenuId() != null; + } + + @AssertTrue(message = "关联的父表信息不全") + @JsonIgnore + public boolean isSubValid() { + return ObjectUtil.notEqual(getTemplateType(), CodegenTemplateTypeEnum.SUB) + || (ObjectUtil.isAllNotEmpty(masterTableId, subJoinColumnId, subJoinMany)); + } + + @AssertTrue(message = "关联的树表信息不全") + @JsonIgnore + public boolean isTreeValid() { + return ObjectUtil.notEqual(templateType, CodegenTemplateTypeEnum.TREE) + || (ObjectUtil.isAllNotEmpty(treeParentColumnId, treeNameColumnId)); + } +} diff --git a/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/controller/admin/codegen/vo/table/DatabaseTableRespVO.java b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/controller/admin/codegen/vo/table/DatabaseTableRespVO.java new file mode 100644 index 0000000..3dbabc1 --- /dev/null +++ b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/controller/admin/codegen/vo/table/DatabaseTableRespVO.java @@ -0,0 +1,14 @@ +package com.tashow.cloud.infra.controller.admin.codegen.vo.table; + +import lombok.Data; + +/** 管理后台 - 数据库的表定义 Response VO */ +@Data +public class DatabaseTableRespVO { + + /** 表名称" */ + private String name; + + /** 表描述", example = "芋道源码 */ + private String comment; +} diff --git a/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/controller/admin/config/ConfigController.java b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/controller/admin/config/ConfigController.java new file mode 100644 index 0000000..48d778d --- /dev/null +++ b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/controller/admin/config/ConfigController.java @@ -0,0 +1,99 @@ +package com.tashow.cloud.infra.controller.admin.config; + +import static com.tashow.cloud.common.exception.util.ServiceExceptionUtil.exception; +import static com.tashow.cloud.common.pojo.CommonResult.success; +import static com.tashow.cloud.web.apilog.core.enums.OperateTypeEnum.EXPORT; + +import com.tashow.cloud.common.pojo.CommonResult; +import com.tashow.cloud.common.pojo.PageParam; +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.excel.excel.core.util.ExcelUtils; +import com.tashow.cloud.infra.controller.admin.config.vo.ConfigPageReqVO; +import com.tashow.cloud.infra.controller.admin.config.vo.ConfigRespVO; +import com.tashow.cloud.infra.controller.admin.config.vo.ConfigSaveReqVO; +import com.tashow.cloud.infra.convert.config.ConfigConvert; +import com.tashow.cloud.infra.dal.dataobject.config.ConfigDO; +import com.tashow.cloud.infra.service.config.ConfigService; +import com.tashow.cloud.infraapi.enums.ErrorCodeConstants; +import com.tashow.cloud.web.apilog.core.annotation.ApiAccessLog; +import jakarta.annotation.Resource; +import jakarta.servlet.http.HttpServletResponse; +import jakarta.validation.Valid; +import java.io.IOException; +import java.util.List; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +/** 管理后台 - 参数配置 */ +@RestController +@RequestMapping("/infra/config") +@Validated +public class ConfigController { + + @Resource private ConfigService configService; + + /** 创建参数配置 */ + @PostMapping("/create") + @PreAuthorize("@ss.hasPermission('infra:config:create')") + public CommonResult createConfig(@Valid @RequestBody ConfigSaveReqVO createReqVO) { + return success(configService.createConfig(createReqVO)); + } + + /** 修改参数配置 */ + @PutMapping("/update") + @PreAuthorize("@ss.hasPermission('infra:config:update')") + public CommonResult updateConfig(@Valid @RequestBody ConfigSaveReqVO updateReqVO) { + configService.updateConfig(updateReqVO); + return success(true); + } + + /** 删除参数配置 */ + @DeleteMapping("/delete") + @PreAuthorize("@ss.hasPermission('infra:config:delete')") + public CommonResult deleteConfig(@RequestParam("id") Long id) { + configService.deleteConfig(id); + return success(true); + } + + /** 获得参数配置 */ + @GetMapping(value = "/get") + @PreAuthorize("@ss.hasPermission('infra:config:query')") + public CommonResult getConfig(@RequestParam("id") Long id) { + return success(ConfigConvert.INSTANCE.convert(configService.getConfig(id))); + } + + /** 根据参数键名查询参数值", description = "不可见的配置,不允许返回给前端 */ + @GetMapping(value = "/get-value-by-key") + public CommonResult getConfigKey(@RequestParam("key") String key) { + ConfigDO config = configService.getConfigByKey(key); + if (config == null) { + return success(null); + } + if (!config.getVisible()) { + throw exception(ErrorCodeConstants.CONFIG_GET_VALUE_ERROR_IF_VISIBLE); + } + return success(config.getValue()); + } + + /** 获取参数配置分页 */ + @GetMapping("/page") + @PreAuthorize("@ss.hasPermission('infra:config:query')") + public CommonResult> getConfigPage(@Valid ConfigPageReqVO pageReqVO) { + PageResult page = configService.getConfigPage(pageReqVO); + return success(ConfigConvert.INSTANCE.convertPage(page)); + } + + /** 导出参数配置 */ + @GetMapping("/export") + @PreAuthorize("@ss.hasPermission('infra:config:export')") + @ApiAccessLog(operateType = EXPORT) + public void exportConfig(ConfigPageReqVO exportReqVO, HttpServletResponse response) + throws IOException { + exportReqVO.setPageSize(PageParam.PAGE_SIZE_NONE); + List list = configService.getConfigPage(exportReqVO).getList(); + // 输出 + ExcelUtils.write( + response, "参数配置.xls", "数据", ConfigRespVO.class, ConfigConvert.INSTANCE.convertList(list)); + } +} diff --git a/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/controller/admin/config/vo/ConfigPageReqVO.java b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/controller/admin/config/vo/ConfigPageReqVO.java new file mode 100644 index 0000000..961e6f2 --- /dev/null +++ b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/controller/admin/config/vo/ConfigPageReqVO.java @@ -0,0 +1,30 @@ +package com.tashow.cloud.infra.controller.admin.config.vo; + +import static com.tashow.cloud.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND; + +import com.tashow.cloud.common.pojo.PageParam; +import java.time.LocalDateTime; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; +import org.springframework.format.annotation.DateTimeFormat; + +/** 管理后台 - 参数配置分页 Request VO */ +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class ConfigPageReqVO extends PageParam { + + /** 数据源名称,模糊匹配 */ + private String name; + + /** 参数键名,模糊匹配 */ + private String key; + + /** 参数类型,参见 SysConfigTypeEnum 枚举 */ + private Integer type; + + /** 创建时间 */ + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) + private LocalDateTime[] createTime; +} diff --git a/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/controller/admin/config/vo/ConfigRespVO.java b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/controller/admin/config/vo/ConfigRespVO.java new file mode 100644 index 0000000..7022a5c --- /dev/null +++ b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/controller/admin/config/vo/ConfigRespVO.java @@ -0,0 +1,53 @@ +package com.tashow.cloud.infra.controller.admin.config.vo; + +import com.alibaba.excel.annotation.ExcelIgnoreUnannotated; +import com.alibaba.excel.annotation.ExcelProperty; +import com.tashow.cloud.excel.excel.core.annotations.DictFormat; +import com.tashow.cloud.excel.excel.core.convert.DictConvert; +import com.tashow.cloud.infraapi.enums.DictTypeConstants; +import java.time.LocalDateTime; +import lombok.Data; + +/** 管理后台 - 参数配置信息 Response VO */ +@Data +@ExcelIgnoreUnannotated +public class ConfigRespVO { + + /** 参数配置序号" */ + @ExcelProperty("参数配置序号") + private Long id; + + /** 参数分类" */ + @ExcelProperty("参数分类") + private String category; + + /** 参数名称", example = "数据库名 */ + @ExcelProperty("参数名称") + private String name; + + /** 参数键名" */ + @ExcelProperty("参数键名") + private String key; + + /** 参数键值" */ + @ExcelProperty("参数键值") + private String value; + + /** 参数类型,参见 SysConfigTypeEnum 枚举" */ + @ExcelProperty(value = "参数类型", converter = DictConvert.class) + @DictFormat(DictTypeConstants.CONFIG_TYPE) + private Integer type; + + /** 是否可见" */ + @ExcelProperty(value = "是否可见", converter = DictConvert.class) + @DictFormat(DictTypeConstants.BOOLEAN_STRING) + private Boolean visible; + + /** 备注 */ + @ExcelProperty("备注") + private String remark; + + /** 创建时间", example = "时间戳格式 */ + @ExcelProperty("创建时间") + private LocalDateTime createTime; +} diff --git a/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/controller/admin/config/vo/ConfigSaveReqVO.java b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/controller/admin/config/vo/ConfigSaveReqVO.java new file mode 100644 index 0000000..0e91f35 --- /dev/null +++ b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/controller/admin/config/vo/ConfigSaveReqVO.java @@ -0,0 +1,42 @@ +package com.tashow.cloud.infra.controller.admin.config.vo; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Size; +import lombok.Data; + +/** 管理后台 - 参数配置创建/修改 Request VO */ +@Data +public class ConfigSaveReqVO { + + /** 参数配置序号 */ + private Long id; + + /** 参数分组" */ + @NotEmpty(message = "参数分组不能为空") + @Size(max = 50, message = "参数名称不能超过 50 个字符") + private String category; + + /** 参数名称", example = "数据库名 */ + @NotBlank(message = "参数名称不能为空") + @Size(max = 100, message = "参数名称不能超过 100 个字符") + private String name; + + /** 参数键名" */ + @NotBlank(message = "参数键名长度不能为空") + @Size(max = 100, message = "参数键名长度不能超过 100 个字符") + private String key; + + /** 参数键值" */ + @NotBlank(message = "参数键值不能为空") + @Size(max = 500, message = "参数键值长度不能超过 500 个字符") + private String value; + + /** 是否可见" */ + @NotNull(message = "是否可见不能为空") + private Boolean visible; + + /** 备注 */ + private String remark; +} diff --git a/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/controller/admin/db/DataSourceConfigController.java b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/controller/admin/db/DataSourceConfigController.java new file mode 100644 index 0000000..4821770 --- /dev/null +++ b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/controller/admin/db/DataSourceConfigController.java @@ -0,0 +1,66 @@ +package com.tashow.cloud.infra.controller.admin.db; + +import static com.tashow.cloud.common.pojo.CommonResult.success; + +import com.tashow.cloud.common.pojo.CommonResult; +import com.tashow.cloud.common.util.object.BeanUtils; +import com.tashow.cloud.infra.controller.admin.db.vo.DataSourceConfigRespVO; +import com.tashow.cloud.infra.controller.admin.db.vo.DataSourceConfigSaveReqVO; +import com.tashow.cloud.infra.dal.dataobject.db.DataSourceConfigDO; +import com.tashow.cloud.infra.service.db.DataSourceConfigService; +import jakarta.annotation.Resource; +import jakarta.validation.Valid; +import java.util.List; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +/** 管理后台 - 数据源配置 */ +@RestController +@RequestMapping("/infra/data-source-config") +@Validated +public class DataSourceConfigController { + + @Resource private DataSourceConfigService dataSourceConfigService; + + /** 创建数据源配置 */ + @PostMapping("/create") + @PreAuthorize("@ss.hasPermission('infra:data-source-config:create')") + public CommonResult createDataSourceConfig( + @Valid @RequestBody DataSourceConfigSaveReqVO createReqVO) { + return success(dataSourceConfigService.createDataSourceConfig(createReqVO)); + } + + /** 更新数据源配置 */ + @PutMapping("/update") + @PreAuthorize("@ss.hasPermission('infra:data-source-config:update')") + public CommonResult updateDataSourceConfig( + @Valid @RequestBody DataSourceConfigSaveReqVO updateReqVO) { + dataSourceConfigService.updateDataSourceConfig(updateReqVO); + return success(true); + } + + /** 删除数据源配置 */ + @DeleteMapping("/delete") + @PreAuthorize("@ss.hasPermission('infra:data-source-config:delete')") + public CommonResult deleteDataSourceConfig(@RequestParam("id") Long id) { + dataSourceConfigService.deleteDataSourceConfig(id); + return success(true); + } + + /** 获得数据源配置 */ + @GetMapping("/get") + @PreAuthorize("@ss.hasPermission('infra:data-source-config:query')") + public CommonResult getDataSourceConfig(@RequestParam("id") Long id) { + DataSourceConfigDO config = dataSourceConfigService.getDataSourceConfig(id); + return success(BeanUtils.toBean(config, DataSourceConfigRespVO.class)); + } + + /** 获得数据源配置列表 */ + @GetMapping("/list") + @PreAuthorize("@ss.hasPermission('infra:data-source-config:query')") + public CommonResult> getDataSourceConfigList() { + List list = dataSourceConfigService.getDataSourceConfigList(); + return success(BeanUtils.toBean(list, DataSourceConfigRespVO.class)); + } +} diff --git a/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/controller/admin/db/vo/DataSourceConfigRespVO.java b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/controller/admin/db/vo/DataSourceConfigRespVO.java new file mode 100644 index 0000000..0eaee24 --- /dev/null +++ b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/controller/admin/db/vo/DataSourceConfigRespVO.java @@ -0,0 +1,26 @@ +package com.tashow.cloud.infra.controller.admin.db.vo; + +import java.time.LocalDateTime; +import lombok.Data; + +/** 管理后台 - 数据源配置 Response VO */ +@Data +public class DataSourceConfigRespVO { + + /** 主键编号" */ + private Integer id; + + /** 数据源名称" */ + private String name; + + /** 数据源连接" */ + private String url; + + /** 用户名" */ + private String username; + + /** + * 创建时间 + */ + private LocalDateTime createTime; +} diff --git a/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/controller/admin/db/vo/DataSourceConfigSaveReqVO.java b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/controller/admin/db/vo/DataSourceConfigSaveReqVO.java new file mode 100644 index 0000000..26ca6d4 --- /dev/null +++ b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/controller/admin/db/vo/DataSourceConfigSaveReqVO.java @@ -0,0 +1,28 @@ +package com.tashow.cloud.infra.controller.admin.db.vo; + +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +/** 管理后台 - 数据源配置创建/修改 Request VO */ +@Data +public class DataSourceConfigSaveReqVO { + + /** 主键编号 */ + private Long id; + + /** 数据源名称" */ + @NotNull(message = "数据源名称不能为空") + private String name; + + /** 数据源连接" */ + @NotNull(message = "数据源连接不能为空") + private String url; + + /** 用户名" */ + @NotNull(message = "用户名不能为空") + private String username; + + /** 密码" */ + @NotNull(message = "密码不能为空") + private String password; +} diff --git a/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/controller/admin/file/FileConfigController.http b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/controller/admin/file/FileConfigController.http new file mode 100644 index 0000000..14b6228 --- /dev/null +++ b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/controller/admin/file/FileConfigController.http @@ -0,0 +1,45 @@ +### 请求 /infra/file-config/create 接口 => 成功 +POST {{baseUrl}}/infra/file-config/create +Content-Type: application/json +tenant-id: {{adminTenantId}} +Authorization: Bearer {{token}} + +{ + "name": "S3 - 七牛云", + "remark": "", + "storage": 20, + "config": { + "accessKey": "b7yvuhBSAGjmtPhMFcn9iMOxUOY_I06cA_p0ZUx8", + "accessSecret": "kXM1l5ia1RvSX3QaOEcwI3RLz3Y2rmNszWonKZtP", + "bucket": "ruoyi-vue-pro", + "endpoint": "s3-cn-south-1.qiniucs.com", + "domain": "http://test.yudao.iocoder.cn", + "region": "oss-cn-beijing" + } +} + +### 请求 /infra/file-config/update 接口 => 成功 +PUT {{baseUrl}}/infra/file-config/update +Content-Type: application/json +tenant-id: {{adminTenantId}} +Authorization: Bearer {{token}} + +{ + "id": 2, + "name": "S3 - 七牛云", + "remark": "", + "config": { + "accessKey": "b7yvuhBSAGjmtPhMFcn9iMOxUOY_I06cA_p0ZUx8", + "accessSecret": "kXM1l5ia1RvSX3QaOEcwI3RLz3Y2rmNszWonKZtP", + "bucket": "ruoyi-vue-pro", + "endpoint": "s3-cn-south-1.qiniucs.com", + "domain": "http://test.yudao.iocoder.cn", + "region": "oss-cn-beijing" + } +} + +### 请求 /infra/file-config/test 接口 => 成功 +GET {{baseUrl}}/infra/file-config/test?id=2 +Content-Type: application/json +tenant-id: {{adminTenantId}} +Authorization: Bearer {{token}} diff --git a/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/controller/admin/file/FileConfigController.java b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/controller/admin/file/FileConfigController.java new file mode 100644 index 0000000..aabbd6f --- /dev/null +++ b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/controller/admin/file/FileConfigController.java @@ -0,0 +1,83 @@ +package com.tashow.cloud.infra.controller.admin.file; + +import static com.tashow.cloud.common.pojo.CommonResult.success; + +import com.tashow.cloud.common.pojo.CommonResult; +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.common.util.object.BeanUtils; +import com.tashow.cloud.infra.controller.admin.file.vo.config.FileConfigPageReqVO; +import com.tashow.cloud.infra.controller.admin.file.vo.config.FileConfigRespVO; +import com.tashow.cloud.infra.controller.admin.file.vo.config.FileConfigSaveReqVO; +import com.tashow.cloud.infra.dal.dataobject.file.FileConfigDO; +import com.tashow.cloud.infra.service.file.FileConfigService; +import jakarta.annotation.Resource; +import jakarta.validation.Valid; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +/** 管理后台 - 文件配置 */ +@RestController +@RequestMapping("/infra/file-config") +@Validated +public class FileConfigController { + + @Resource private FileConfigService fileConfigService; + + /** 创建文件配置 */ + @PostMapping("/create") + @PreAuthorize("@ss.hasPermission('infra:file-config:create')") + public CommonResult createFileConfig(@Valid @RequestBody FileConfigSaveReqVO createReqVO) { + return success(fileConfigService.createFileConfig(createReqVO)); + } + + /** 更新文件配置 */ + @PutMapping("/update") + @PreAuthorize("@ss.hasPermission('infra:file-config:update')") + public CommonResult updateFileConfig( + @Valid @RequestBody FileConfigSaveReqVO updateReqVO) { + fileConfigService.updateFileConfig(updateReqVO); + return success(true); + } + + /** 更新文件配置为 Master */ + @PutMapping("/update-master") + @PreAuthorize("@ss.hasPermission('infra:file-config:update')") + public CommonResult updateFileConfigMaster(@RequestParam("id") Long id) { + fileConfigService.updateFileConfigMaster(id); + return success(true); + } + + /** 删除文件配置 */ + @DeleteMapping("/delete") + @PreAuthorize("@ss.hasPermission('infra:file-config:delete')") + public CommonResult deleteFileConfig(@RequestParam("id") Long id) { + fileConfigService.deleteFileConfig(id); + return success(true); + } + + /** 获得文件配置 */ + @GetMapping("/get") + @PreAuthorize("@ss.hasPermission('infra:file-config:query')") + public CommonResult getFileConfig(@RequestParam("id") Long id) { + FileConfigDO config = fileConfigService.getFileConfig(id); + return success(BeanUtils.toBean(config, FileConfigRespVO.class)); + } + + /** 获得文件配置分页 */ + @GetMapping("/page") + @PreAuthorize("@ss.hasPermission('infra:file-config:query')") + public CommonResult> getFileConfigPage( + @Valid FileConfigPageReqVO pageVO) { + PageResult pageResult = fileConfigService.getFileConfigPage(pageVO); + return success(BeanUtils.toBean(pageResult, FileConfigRespVO.class)); + } + + /** 测试文件配置是否正确 */ + @GetMapping("/test") + @PreAuthorize("@ss.hasPermission('infra:file-config:query')") + public CommonResult testFileConfig(@RequestParam("id") Long id) throws Exception { + String url = fileConfigService.testFileConfig(id); + return success(url); + } +} diff --git a/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/controller/admin/file/FileController.java b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/controller/admin/file/FileController.java new file mode 100644 index 0000000..c1a432d --- /dev/null +++ b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/controller/admin/file/FileController.java @@ -0,0 +1,100 @@ +package com.tashow.cloud.infra.controller.admin.file; + +import static com.tashow.cloud.common.pojo.CommonResult.success; +import static com.tashow.cloud.infra.framework.file.core.utils.FileTypeUtils.writeAttachment; + +import cn.hutool.core.io.IoUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.core.util.URLUtil; +import com.tashow.cloud.common.pojo.CommonResult; +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.common.util.object.BeanUtils; +import com.tashow.cloud.infra.controller.admin.file.vo.file.*; +import com.tashow.cloud.infra.dal.dataobject.file.FileDO; +import com.tashow.cloud.infra.service.file.FileService; +import jakarta.annotation.Resource; +import jakarta.annotation.security.PermitAll; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import jakarta.validation.Valid; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpStatus; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; + +/** 管理后台 - 文件存储 */ +@RestController +@RequestMapping("/infra/file") +@Validated +@Slf4j +public class FileController { + + @Resource private FileService fileService; + + /** 上传文件", description = "模式一:后端上传文件 */ + @PostMapping("/upload") + public CommonResult uploadFile(FileUploadReqVO uploadReqVO) throws Exception { + MultipartFile file = uploadReqVO.getFile(); + String path = uploadReqVO.getPath(); + return success( + fileService.createFile( + file.getOriginalFilename(), path, IoUtil.readBytes(file.getInputStream()))); + } + + /** 获取文件预签名地址", description = "模式二:前端上传文件:用于前端直接上传七牛、阿里云 OSS 等文件存储器 */ + @GetMapping("/presigned-url") + public CommonResult getFilePresignedUrl(@RequestParam("path") String path) + throws Exception { + return success(fileService.getFilePresignedUrl(path)); + } + + /** 创建文件", description = "模式二:前端上传文件:配合 presigned-url 接口,记录上传了上传的文件 */ + @PostMapping("/create") + public CommonResult createFile(@Valid @RequestBody FileCreateReqVO createReqVO) { + return success(fileService.createFile(createReqVO)); + } + + /** 删除文件 */ + @DeleteMapping("/delete") + @PreAuthorize("@ss.hasPermission('infra:file:delete')") + public CommonResult deleteFile(@RequestParam("id") Long id) throws Exception { + fileService.deleteFile(id); + return success(true); + } + + /** 下载文件 */ + @GetMapping("/{configId}/get/**") + @PermitAll + public void getFileContent( + HttpServletRequest request, + HttpServletResponse response, + @PathVariable("configId") Long configId) + throws Exception { + // 获取请求的路径 + String path = StrUtil.subAfter(request.getRequestURI(), "/get/", false); + if (StrUtil.isEmpty(path)) { + throw new IllegalArgumentException("结尾的 path 路径必须传递"); + } + // 解码,解决中文路径的问题 https://gitee.com/zhijiantianya/ruoyi-vue-pro/pulls/807/ + path = URLUtil.decode(path); + + // 读取内容 + byte[] content = fileService.getFileContent(configId, path); + if (content == null) { + log.warn("[getFileContent][configId({}) path({}) 文件不存在]", configId, path); + response.setStatus(HttpStatus.NOT_FOUND.value()); + return; + } + writeAttachment(response, path, content); + } + + /** 获得文件分页 */ + @GetMapping("/page") + @PreAuthorize("@ss.hasPermission('infra:file:query')") + public CommonResult> getFilePage(@Valid FilePageReqVO pageVO) { + PageResult pageResult = fileService.getFilePage(pageVO); + return success(BeanUtils.toBean(pageResult, FileRespVO.class)); + } +} diff --git a/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/controller/admin/file/vo/config/FileConfigPageReqVO.java b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/controller/admin/file/vo/config/FileConfigPageReqVO.java new file mode 100644 index 0000000..3821edc --- /dev/null +++ b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/controller/admin/file/vo/config/FileConfigPageReqVO.java @@ -0,0 +1,27 @@ +package com.tashow.cloud.infra.controller.admin.file.vo.config; + +import static com.tashow.cloud.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND; + +import com.tashow.cloud.common.pojo.PageParam; +import java.time.LocalDateTime; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; +import org.springframework.format.annotation.DateTimeFormat; + +/** 管理后台 - 文件配置分页 Request VO */ +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class FileConfigPageReqVO extends PageParam { + + /** 配置名 */ + private String name; + + /** 存储器 */ + private Integer storage; + + /** 创建时间 */ + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) + private LocalDateTime[] createTime; +} diff --git a/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/controller/admin/file/vo/config/FileConfigRespVO.java b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/controller/admin/file/vo/config/FileConfigRespVO.java new file mode 100644 index 0000000..82b731c --- /dev/null +++ b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/controller/admin/file/vo/config/FileConfigRespVO.java @@ -0,0 +1,31 @@ +package com.tashow.cloud.infra.controller.admin.file.vo.config; + +import com.tashow.cloud.infra.framework.file.core.client.FileClientConfig; +import java.time.LocalDateTime; +import lombok.Data; + +/** 管理后台 - 文件配置 Response VO */ +@Data +public class FileConfigRespVO { + + /** 编号" */ + private Long id; + + /** 配置名" */ + private String name; + + /** 存储器,参见 FileStorageEnum 枚举类" */ + private Integer storage; + + /** 是否为主配置" */ + private Boolean master; + + /** 存储配置 */ + private FileClientConfig config; + + /** 备注 */ + private String remark; + + /** 创建时间 */ + private LocalDateTime createTime; +} diff --git a/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/controller/admin/file/vo/config/FileConfigSaveReqVO.java b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/controller/admin/file/vo/config/FileConfigSaveReqVO.java new file mode 100644 index 0000000..ef3a2bd --- /dev/null +++ b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/controller/admin/file/vo/config/FileConfigSaveReqVO.java @@ -0,0 +1,28 @@ +package com.tashow.cloud.infra.controller.admin.file.vo.config; + +import jakarta.validation.constraints.NotNull; +import java.util.Map; +import lombok.Data; + +/** 管理后台 - 文件配置创建/修改 Request VO */ +@Data +public class FileConfigSaveReqVO { + + /** 编号 */ + private Long id; + + /** 配置名" */ + @NotNull(message = "配置名不能为空") + private String name; + + /** 存储器,参见 FileStorageEnum 枚举类" */ + @NotNull(message = "存储器不能为空") + private Integer storage; + + /** 存储配置,配置是动态参数,所以使用 Map 接收 */ + @NotNull(message = "存储配置不能为空") + private Map config; + + /** 备注 */ + private String remark; +} diff --git a/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/controller/admin/file/vo/file/FileCreateReqVO.java b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/controller/admin/file/vo/file/FileCreateReqVO.java new file mode 100644 index 0000000..fed0c88 --- /dev/null +++ b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/controller/admin/file/vo/file/FileCreateReqVO.java @@ -0,0 +1,33 @@ +package com.tashow.cloud.infra.controller.admin.file.vo.file; + +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +/** 管理后台 - 文件创建 Request VO */ +@Data +public class FileCreateReqVO { + + @NotNull(message = "文件配置编号不能为空") + /** 文件配置编号" */ + private Long configId; + + @NotNull(message = "文件路径不能为空") + /** 文件路径" */ + private String path; + + @NotNull(message = "原文件名不能为空") + /** 原文件名" */ + private String name; + + @NotNull(message = "文件 URL不能为空") + /** 文件 URL" */ + private String url; + + /** 文件 MIME 类型 */ + private String type; + + /** + * 文件大小", example = "2048 + */ + private Integer size; +} diff --git a/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/controller/admin/file/vo/file/FilePageReqVO.java b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/controller/admin/file/vo/file/FilePageReqVO.java new file mode 100644 index 0000000..1b69520 --- /dev/null +++ b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/controller/admin/file/vo/file/FilePageReqVO.java @@ -0,0 +1,27 @@ +package com.tashow.cloud.infra.controller.admin.file.vo.file; + +import static com.tashow.cloud.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND; + +import com.tashow.cloud.common.pojo.PageParam; +import java.time.LocalDateTime; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; +import org.springframework.format.annotation.DateTimeFormat; + +/** 管理后台 - 文件分页 Request VO */ +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class FilePageReqVO extends PageParam { + + /** 文件路径,模糊匹配 */ + private String path; + + /** 文件类型,模糊匹配 */ + private String type; + + /** 创建时间 */ + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) + private LocalDateTime[] createTime; +} diff --git a/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/controller/admin/file/vo/file/FilePresignedUrlRespVO.java b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/controller/admin/file/vo/file/FilePresignedUrlRespVO.java new file mode 100644 index 0000000..f942c4f --- /dev/null +++ b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/controller/admin/file/vo/file/FilePresignedUrlRespVO.java @@ -0,0 +1,25 @@ +package com.tashow.cloud.infra.controller.admin.file.vo.file; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@AllArgsConstructor +@NoArgsConstructor +/** 管理后台 - 文件预签名地址 Response VO */ +@Data +public class FilePresignedUrlRespVO { + + /** 配置编号" */ + private Long configId; + + /** 文件上传 URL" */ + private String uploadUrl; + + /** + * 文件访问 URL + * + *

前端上传完文件后,需要使用该 URL 进行访问 + */ + private String url; +} diff --git a/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/controller/admin/file/vo/file/FileRespVO.java b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/controller/admin/file/vo/file/FileRespVO.java new file mode 100644 index 0000000..22dc6ed --- /dev/null +++ b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/controller/admin/file/vo/file/FileRespVO.java @@ -0,0 +1,33 @@ +package com.tashow.cloud.infra.controller.admin.file.vo.file; + +import java.time.LocalDateTime; +import lombok.Data; + +/** 管理后台 - 文件 Response VO,不返回 content 字段,太大 */ +@Data +public class FileRespVO { + + /** 文件编号" */ + private Long id; + + /** 配置编号" */ + private Long configId; + + /** 文件路径" */ + private String path; + + /** 原文件名" */ + private String name; + + /** 文件 URL" */ + private String url; + + /** 文件MIME类型 */ + private String type; + + /** 文件大小", example = "2048 */ + private Integer size; + + /** 创建时间 */ + private LocalDateTime createTime; +} diff --git a/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/controller/admin/file/vo/file/FileUploadReqVO.java b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/controller/admin/file/vo/file/FileUploadReqVO.java new file mode 100644 index 0000000..467a2c9 --- /dev/null +++ b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/controller/admin/file/vo/file/FileUploadReqVO.java @@ -0,0 +1,17 @@ +package com.tashow.cloud.infra.controller.admin.file.vo.file; + +import jakarta.validation.constraints.NotNull; +import lombok.Data; +import org.springframework.web.multipart.MultipartFile; + +/** 管理后台 - 上传文件 Request VO */ +@Data +public class FileUploadReqVO { + + /** 文件附件 */ + @NotNull(message = "文件附件不能为空") + private MultipartFile file; + + /** 文件附件 */ + private String path; +} diff --git a/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/controller/admin/job/JobController.java b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/controller/admin/job/JobController.java new file mode 100644 index 0000000..b25e296 --- /dev/null +++ b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/controller/admin/job/JobController.java @@ -0,0 +1,24 @@ +package com.tashow.cloud.infra.controller.admin.job; + +import static com.tashow.cloud.common.pojo.CommonResult.error; + +import com.tashow.cloud.common.pojo.CommonResult; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +/** 管理后台 - 定时任务 */ +@RestController +@RequestMapping("/infra/job") +@Validated +public class JobController { + + /** 获得定时任务分页 */ + @GetMapping("/page") + @PreAuthorize("@ss.hasPermission('infra:job:query')") + public CommonResult getJobPage() { + return error(-1, "Cloud 版本使用 XXL-Job 作为定时任务!请参考 https://cloud.iocoder.cn/job/ 文档操作"); + } +} diff --git a/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/controller/admin/logger/ApiAccessLogController.java b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/controller/admin/logger/ApiAccessLogController.java new file mode 100644 index 0000000..14de08a --- /dev/null +++ b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/controller/admin/logger/ApiAccessLogController.java @@ -0,0 +1,60 @@ +package com.tashow.cloud.infra.controller.admin.logger; + +import static com.tashow.cloud.common.pojo.CommonResult.success; +import static com.tashow.cloud.web.apilog.core.enums.OperateTypeEnum.EXPORT; + +import com.tashow.cloud.common.pojo.CommonResult; +import com.tashow.cloud.common.pojo.PageParam; +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.common.util.object.BeanUtils; +import com.tashow.cloud.excel.excel.core.util.ExcelUtils; +import com.tashow.cloud.infra.controller.admin.logger.vo.apiaccesslog.ApiAccessLogPageReqVO; +import com.tashow.cloud.infra.controller.admin.logger.vo.apiaccesslog.ApiAccessLogRespVO; +import com.tashow.cloud.infra.dal.dataobject.logger.ApiAccessLogDO; +import com.tashow.cloud.infra.service.logger.ApiAccessLogService; +import com.tashow.cloud.web.apilog.core.annotation.ApiAccessLog; +import jakarta.annotation.Resource; +import jakarta.servlet.http.HttpServletResponse; +import jakarta.validation.Valid; +import java.io.IOException; +import java.util.List; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +/** 管理后台 - API 访问日志 */ +@RestController +@RequestMapping("/infra/api-access-log") +@Validated +public class ApiAccessLogController { + + @Resource private ApiAccessLogService apiAccessLogService; + + /** 获得API 访问日志分页 */ + @GetMapping("/page") + @PreAuthorize("@ss.hasPermission('infra:api-access-log:query')") + public CommonResult> getApiAccessLogPage( + @Valid ApiAccessLogPageReqVO pageReqVO) { + PageResult pageResult = apiAccessLogService.getApiAccessLogPage(pageReqVO); + return success(BeanUtils.toBean(pageResult, ApiAccessLogRespVO.class)); + } + + /** 导出API 访问日志 Excel */ + @GetMapping("/export-excel") + @PreAuthorize("@ss.hasPermission('infra:api-access-log:export')") + @ApiAccessLog(operateType = EXPORT) + public void exportApiAccessLogExcel( + @Valid ApiAccessLogPageReqVO exportReqVO, HttpServletResponse response) throws IOException { + exportReqVO.setPageSize(PageParam.PAGE_SIZE_NONE); + List list = apiAccessLogService.getApiAccessLogPage(exportReqVO).getList(); + // 导出 Excel + ExcelUtils.write( + response, + "API 访问日志.xls", + "数据", + ApiAccessLogRespVO.class, + BeanUtils.toBean(list, ApiAccessLogRespVO.class)); + } +} diff --git a/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/controller/admin/logger/ApiErrorLogController.java b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/controller/admin/logger/ApiErrorLogController.java new file mode 100644 index 0000000..b756f8b --- /dev/null +++ b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/controller/admin/logger/ApiErrorLogController.java @@ -0,0 +1,68 @@ +package com.tashow.cloud.infra.controller.admin.logger; + +import static com.tashow.cloud.common.pojo.CommonResult.success; +import static com.tashow.cloud.web.apilog.core.enums.OperateTypeEnum.EXPORT; +import static com.tashow.cloud.web.web.core.util.WebFrameworkUtils.getLoginUserId; + +import com.tashow.cloud.common.pojo.CommonResult; +import com.tashow.cloud.common.pojo.PageParam; +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.common.util.object.BeanUtils; +import com.tashow.cloud.excel.excel.core.util.ExcelUtils; +import com.tashow.cloud.infra.controller.admin.logger.vo.apierrorlog.ApiErrorLogPageReqVO; +import com.tashow.cloud.infra.controller.admin.logger.vo.apierrorlog.ApiErrorLogRespVO; +import com.tashow.cloud.infra.dal.dataobject.logger.ApiErrorLogDO; +import com.tashow.cloud.infra.service.logger.ApiErrorLogService; +import com.tashow.cloud.web.apilog.core.annotation.ApiAccessLog; +import jakarta.annotation.Resource; +import jakarta.servlet.http.HttpServletResponse; +import jakarta.validation.Valid; +import java.io.IOException; +import java.util.List; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +/** 管理后台 - API 错误日志 */ +@RestController +@RequestMapping("/infra/api-error-log") +@Validated +public class ApiErrorLogController { + + @Resource private ApiErrorLogService apiErrorLogService; + + /** 更新 API 错误日志的状态 */ + @PutMapping("/update-status") + @PreAuthorize("@ss.hasPermission('infra:api-error-log:update-status')") + public CommonResult updateApiErrorLogProcess( + @RequestParam("id") Long id, @RequestParam("processStatus") Integer processStatus) { + apiErrorLogService.updateApiErrorLogProcess(id, processStatus, getLoginUserId()); + return success(true); + } + + /** 获得 API 错误日志分页 */ + @GetMapping("/page") + @PreAuthorize("@ss.hasPermission('infra:api-error-log:query')") + public CommonResult> getApiErrorLogPage( + @Valid ApiErrorLogPageReqVO pageReqVO) { + PageResult pageResult = apiErrorLogService.getApiErrorLogPage(pageReqVO); + return success(BeanUtils.toBean(pageResult, ApiErrorLogRespVO.class)); + } + + /** 导出 API 错误日志 Excel */ + @GetMapping("/export-excel") + @PreAuthorize("@ss.hasPermission('infra:api-error-log:export')") + @ApiAccessLog(operateType = EXPORT) + public void exportApiErrorLogExcel( + @Valid ApiErrorLogPageReqVO exportReqVO, HttpServletResponse response) throws IOException { + exportReqVO.setPageSize(PageParam.PAGE_SIZE_NONE); + List list = apiErrorLogService.getApiErrorLogPage(exportReqVO).getList(); + // 导出 Excel + ExcelUtils.write( + response, + "API 错误日志.xls", + "数据", + ApiErrorLogRespVO.class, + BeanUtils.toBean(list, ApiErrorLogRespVO.class)); + } +} diff --git a/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/controller/admin/logger/vo/apiaccesslog/ApiAccessLogPageReqVO.java b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/controller/admin/logger/vo/apiaccesslog/ApiAccessLogPageReqVO.java new file mode 100644 index 0000000..411f0b5 --- /dev/null +++ b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/controller/admin/logger/vo/apiaccesslog/ApiAccessLogPageReqVO.java @@ -0,0 +1,39 @@ +package com.tashow.cloud.infra.controller.admin.logger.vo.apiaccesslog; + +import static com.tashow.cloud.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND; + +import com.tashow.cloud.common.pojo.PageParam; +import java.time.LocalDateTime; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; +import org.springframework.format.annotation.DateTimeFormat; + +/** 管理后台 - API 访问日志分页 Request VO */ +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class ApiAccessLogPageReqVO extends PageParam { + + /** 用户编号 */ + private Long userId; + + /** 用户类型 */ + private Integer userType; + + /** 应用名 */ + private String applicationName; + + /** 请求地址,模糊匹配 */ + private String requestUrl; + + /** 开始时间 */ + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) + private LocalDateTime[] beginTime; + + /** 执行时长,大于等于,单位:毫秒 */ + private Integer duration; + + /** 结果码 */ + private Integer resultCode; +} diff --git a/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/controller/admin/logger/vo/apiaccesslog/ApiAccessLogRespVO.java b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/controller/admin/logger/vo/apiaccesslog/ApiAccessLogRespVO.java new file mode 100644 index 0000000..3011fc9 --- /dev/null +++ b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/controller/admin/logger/vo/apiaccesslog/ApiAccessLogRespVO.java @@ -0,0 +1,96 @@ +package com.tashow.cloud.infra.controller.admin.logger.vo.apiaccesslog; + +import com.alibaba.excel.annotation.ExcelIgnoreUnannotated; +import com.alibaba.excel.annotation.ExcelProperty; +import com.tashow.cloud.excel.excel.core.annotations.DictFormat; +import com.tashow.cloud.excel.excel.core.convert.DictConvert; +import com.tashow.cloud.systemapi.enums.DictTypeConstants; +import java.time.LocalDateTime; +import lombok.Data; + +/** 管理后台 - API 访问日志 Response VO */ +@Data +@ExcelIgnoreUnannotated +public class ApiAccessLogRespVO { + + /** 日志主键" */ + @ExcelProperty("日志主键") + private Long id; + + /** 链路追踪编号" */ + @ExcelProperty("链路追踪编号") + private String traceId; + + /** 用户编号" */ + @ExcelProperty("用户编号") + private Long userId; + + /** 用户类型,参见 UserTypeEnum 枚举" */ + @ExcelProperty(value = "用户类型", converter = DictConvert.class) + @DictFormat(DictTypeConstants.USER_TYPE) + private Integer userType; + + /** 应用名" */ + @ExcelProperty("应用名") + private String applicationName; + + /** 请求方法名" */ + @ExcelProperty("请求方法名") + private String requestMethod; + + /** 请求地址", example = "/xxx/yyy */ + @ExcelProperty("请求地址") + private String requestUrl; + + /** 请求参数 */ + @ExcelProperty("请求参数") + private String requestParams; + + /** 响应结果 */ + @ExcelProperty("响应结果") + private String responseBody; + + /** 用户 IP" */ + @ExcelProperty("用户 IP") + private String userIp; + + /** 浏览器 UA" */ + @ExcelProperty("浏览器 UA") + private String userAgent; + + /** 操作模块", example = "商品模块 */ + @ExcelProperty("操作模块") + private String operateModule; + + /** 操作名", example = "创建商品 */ + @ExcelProperty("操作名") + private String operateName; + + /** 操作分类" */ + @ExcelProperty(value = "操作分类", converter = DictConvert.class) + @DictFormat(com.tashow.cloud.infraapi.enums.DictTypeConstants.OPERATE_TYPE) + private Integer operateType; + + /** 开始请求时间 */ + @ExcelProperty("开始请求时间") + private LocalDateTime beginTime; + + /** 结束请求时间 */ + @ExcelProperty("结束请求时间") + private LocalDateTime endTime; + + /** 执行时长" */ + @ExcelProperty("执行时长") + private Integer duration; + + /** 结果码" */ + @ExcelProperty("结果码") + private Integer resultCode; + + /** 结果提示 */ + @ExcelProperty("结果提示") + private String resultMsg; + + /** 创建时间 */ + private LocalDateTime createTime; +} diff --git a/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/controller/admin/logger/vo/apierrorlog/ApiErrorLogPageReqVO.java b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/controller/admin/logger/vo/apierrorlog/ApiErrorLogPageReqVO.java new file mode 100644 index 0000000..34913d4 --- /dev/null +++ b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/controller/admin/logger/vo/apierrorlog/ApiErrorLogPageReqVO.java @@ -0,0 +1,36 @@ +package com.tashow.cloud.infra.controller.admin.logger.vo.apierrorlog; + +import static com.tashow.cloud.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND; + +import com.tashow.cloud.common.pojo.PageParam; +import java.time.LocalDateTime; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; +import org.springframework.format.annotation.DateTimeFormat; + +/** 管理后台 - API 错误日志分页 Request VO */ +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class ApiErrorLogPageReqVO extends PageParam { + + /** 用户编号 */ + private Long userId; + + /** 用户类型 */ + private Integer userType; + + /** 应用名 */ + private String applicationName; + + /** 请求地址 */ + private String requestUrl; + + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) + /** 异常发生时间 */ + private LocalDateTime[] exceptionTime; + + /** 处理状态 */ + private Integer processStatus; +} diff --git a/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/controller/admin/logger/vo/apierrorlog/ApiErrorLogRespVO.java b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/controller/admin/logger/vo/apierrorlog/ApiErrorLogRespVO.java new file mode 100644 index 0000000..cb6359a --- /dev/null +++ b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/controller/admin/logger/vo/apierrorlog/ApiErrorLogRespVO.java @@ -0,0 +1,109 @@ +package com.tashow.cloud.infra.controller.admin.logger.vo.apierrorlog; + +import com.alibaba.excel.annotation.ExcelIgnoreUnannotated; +import com.alibaba.excel.annotation.ExcelProperty; +import com.tashow.cloud.excel.excel.core.annotations.DictFormat; +import com.tashow.cloud.excel.excel.core.convert.DictConvert; +import com.tashow.cloud.infraapi.enums.DictTypeConstants; +import java.time.LocalDateTime; +import lombok.Data; + +/** 管理后台 - API 错误日志 Response VO */ +@Data +@ExcelIgnoreUnannotated +public class ApiErrorLogRespVO { + + /** 编号" */ + @ExcelProperty("编号") + private Long id; + + /** 链路追踪编号" */ + @ExcelProperty("链路追踪编号") + private String traceId; + + /** 用户编号" */ + @ExcelProperty("用户编号") + private Long userId; + + /** 用户类型" */ + @ExcelProperty(value = "用户类型", converter = DictConvert.class) + @DictFormat(com.tashow.cloud.systemapi.enums.DictTypeConstants.USER_TYPE) + private Integer userType; + + /** 应用名" */ + @ExcelProperty("应用名") + private String applicationName; + + /** 请求方法名" */ + @ExcelProperty("请求方法名") + private String requestMethod; + + /** 请求地址", example = "/xx/yy */ + @ExcelProperty("请求地址") + private String requestUrl; + + /** 请求参数 */ + @ExcelProperty("请求参数") + private String requestParams; + + /** 用户 IP" */ + @ExcelProperty("用户 IP") + private String userIp; + + /** 浏览器 UA" */ + @ExcelProperty("浏览器 UA") + private String userAgent; + + /** 异常发生时间 */ + @ExcelProperty("异常发生时间") + private LocalDateTime exceptionTime; + + /** 异常名 */ + @ExcelProperty("异常名") + private String exceptionName; + + /** 异常导致的消息 */ + @ExcelProperty("异常导致的消息") + private String exceptionMessage; + + /** 异常导致的根消息 */ + @ExcelProperty("异常导致的根消息") + private String exceptionRootCauseMessage; + + /** 异常的栈轨迹 */ + @ExcelProperty("异常的栈轨迹") + private String exceptionStackTrace; + + /** 异常发生的类全名 */ + @ExcelProperty("异常发生的类全名") + private String exceptionClassName; + + /** 异常发生的类文件 */ + @ExcelProperty("异常发生的类文件") + private String exceptionFileName; + + /** 异常发生的方法名 */ + @ExcelProperty("异常发生的方法名") + private String exceptionMethodName; + + /** 异常发生的方法所在行 */ + @ExcelProperty("异常发生的方法所在行") + private Integer exceptionLineNumber; + + /** 处理状态" */ + @ExcelProperty(value = "处理状态", converter = DictConvert.class) + @DictFormat(DictTypeConstants.API_ERROR_LOG_PROCESS_STATUS) + private Integer processStatus; + + /** 处理时间 */ + @ExcelProperty("处理时间") + private LocalDateTime processTime; + + /** 处理用户编号 */ + @ExcelProperty("处理用户编号") + private Integer processUserId; + + /** 创建时间 */ + @ExcelProperty("创建时间") + private LocalDateTime createTime; +} diff --git a/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/controller/admin/redis/RedisController.http b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/controller/admin/redis/RedisController.http new file mode 100644 index 0000000..68b5487 --- /dev/null +++ b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/controller/admin/redis/RedisController.http @@ -0,0 +1,4 @@ +### 请求 /infra/redis/get-monitor-info 接口 => 成功 +GET {{baseUrl}}/infra/redis/get-monitor-info +Authorization: Bearer {{token}} +tenant-id: {{adminTenantId}} diff --git a/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/controller/admin/redis/RedisController.java b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/controller/admin/redis/RedisController.java new file mode 100644 index 0000000..3a21513 --- /dev/null +++ b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/controller/admin/redis/RedisController.java @@ -0,0 +1,41 @@ +package com.tashow.cloud.infra.controller.admin.redis; + +import static com.tashow.cloud.common.pojo.CommonResult.success; + +import com.tashow.cloud.common.pojo.CommonResult; +import com.tashow.cloud.infra.controller.admin.redis.vo.RedisMonitorRespVO; +import com.tashow.cloud.infra.convert.redis.RedisConvert; +import jakarta.annotation.Resource; +import java.util.Properties; +import org.springframework.data.redis.connection.RedisServerCommands; +import org.springframework.data.redis.core.RedisCallback; +import org.springframework.data.redis.core.StringRedisTemplate; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +/** 管理后台 - Redis 监控 */ +@RestController +@RequestMapping("/infra/redis") +public class RedisController { + + @Resource private StringRedisTemplate stringRedisTemplate; + + /** 获得 Redis 监控信息 */ + @GetMapping("/get-monitor-info") + @PreAuthorize("@ss.hasPermission('infra:redis:get-monitor-info')") + public CommonResult getRedisMonitorInfo() { + // 获得 Redis 统计信息 + Properties info = + stringRedisTemplate.execute((RedisCallback) RedisServerCommands::info); + Long dbSize = stringRedisTemplate.execute(RedisServerCommands::dbSize); + Properties commandStats = + stringRedisTemplate.execute( + (RedisCallback) + connection -> connection.serverCommands().info("commandstats")); + assert commandStats != null; // 断言,避免警告 + // 拼接结果返回 + return success(RedisConvert.INSTANCE.build(info, dbSize, commandStats)); + } +} diff --git a/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/controller/admin/redis/vo/RedisMonitorRespVO.java b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/controller/admin/redis/vo/RedisMonitorRespVO.java new file mode 100644 index 0000000..d615823 --- /dev/null +++ b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/controller/admin/redis/vo/RedisMonitorRespVO.java @@ -0,0 +1,38 @@ +package com.tashow.cloud.infra.controller.admin.redis.vo; + +import java.util.List; +import java.util.Properties; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; + +/** 管理后台 - Redis 监控信息 Response VO */ +@Data +@Builder +@AllArgsConstructor +public class RedisMonitorRespVO { + + private Properties info; + + /** Redis key 数量" */ + private Long dbSize; + + /** CommandStat 数组 */ + private List commandStats; + + /** Redis 命令统计结果 */ + @Data + @Builder + @AllArgsConstructor + public static class CommandStat { + + /** Redis 命令" */ + private String command; + + /** 调用次数" */ + private Long calls; + + /** 消耗 CPU 秒数" */ + private Long usec; + } +} diff --git a/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/controller/app/file/AppFileController.java b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/controller/app/file/AppFileController.java new file mode 100644 index 0000000..b5493c5 --- /dev/null +++ b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/controller/app/file/AppFileController.java @@ -0,0 +1,53 @@ +package com.tashow.cloud.infra.controller.app.file; + +import static com.tashow.cloud.common.pojo.CommonResult.success; + +import cn.hutool.core.io.IoUtil; +import com.tashow.cloud.common.pojo.CommonResult; +import com.tashow.cloud.infra.controller.admin.file.vo.file.FileCreateReqVO; +import com.tashow.cloud.infra.controller.admin.file.vo.file.FilePresignedUrlRespVO; +import com.tashow.cloud.infra.controller.app.file.vo.AppFileUploadReqVO; +import com.tashow.cloud.infra.service.file.FileService; +import jakarta.annotation.Resource; +import jakarta.annotation.security.PermitAll; +import jakarta.validation.Valid; +import lombok.extern.slf4j.Slf4j; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; + +/** 用户 App - 文件存储 */ +@RestController +@RequestMapping("/infra/file") +@Validated +@Slf4j +public class AppFileController { + + @Resource private FileService fileService; + + /** 上传文件 */ + @PostMapping("/upload") + @PermitAll + public CommonResult uploadFile(AppFileUploadReqVO uploadReqVO) throws Exception { + MultipartFile file = uploadReqVO.getFile(); + String path = uploadReqVO.getPath(); + return success( + fileService.createFile( + file.getOriginalFilename(), path, IoUtil.readBytes(file.getInputStream()))); + } + + /** 获取文件预签名地址", description = "模式二:前端上传文件:用于前端直接上传七牛、阿里云 OSS 等文件存储器 */ + @GetMapping("/presigned-url") + @PermitAll + public CommonResult getFilePresignedUrl(@RequestParam("path") String path) + throws Exception { + return success(fileService.getFilePresignedUrl(path)); + } + + /** 创建文件", description = "模式二:前端上传文件:配合 presigned-url 接口,记录上传了上传的文件 */ + @PostMapping("/create") + @PermitAll + public CommonResult createFile(@Valid @RequestBody FileCreateReqVO createReqVO) { + return success(fileService.createFile(createReqVO)); + } +} diff --git a/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/controller/app/file/vo/AppFileUploadReqVO.java b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/controller/app/file/vo/AppFileUploadReqVO.java new file mode 100644 index 0000000..27ab32e --- /dev/null +++ b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/controller/app/file/vo/AppFileUploadReqVO.java @@ -0,0 +1,17 @@ +package com.tashow.cloud.infra.controller.app.file.vo; + +import jakarta.validation.constraints.NotNull; +import lombok.Data; +import org.springframework.web.multipart.MultipartFile; + +/** 用户 App - 上传文件 Request VO */ +@Data +public class AppFileUploadReqVO { + + /** 文件附件 */ + @NotNull(message = "文件附件不能为空") + private MultipartFile file; + + /** 文件附件 */ + private String path; +} diff --git a/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/controller/app/package-info.java b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/controller/app/package-info.java new file mode 100644 index 0000000..e3cb81c --- /dev/null +++ b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/controller/app/package-info.java @@ -0,0 +1,4 @@ +/** + * 占位 + */ +package com.tashow.cloud.infra.controller.app; diff --git a/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/controller/package-info.java b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/controller/package-info.java new file mode 100644 index 0000000..149a690 --- /dev/null +++ b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/controller/package-info.java @@ -0,0 +1,6 @@ +/** + * 提供 RESTful API 给前端: + * 1. admin 包:提供给管理后台 yudao-ui-admin 前端项目 + * 2. app 包:提供给用户 APP yudao-ui-app 前端项目,它的 Controller 和 VO 都要添加 App 前缀,用于和管理后台进行区分 + */ +package com.tashow.cloud.infra.controller; diff --git a/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/convert/codegen/CodegenConvert.java b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/convert/codegen/CodegenConvert.java new file mode 100644 index 0000000..c8d8285 --- /dev/null +++ b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/convert/codegen/CodegenConvert.java @@ -0,0 +1,68 @@ +package com.tashow.cloud.infra.convert.codegen; + +import com.tashow.cloud.common.util.collection.CollectionUtils; +import com.tashow.cloud.common.util.object.BeanUtils; +import com.tashow.cloud.infra.controller.admin.codegen.vo.CodegenDetailRespVO; +import com.tashow.cloud.infra.controller.admin.codegen.vo.CodegenPreviewRespVO; +import com.tashow.cloud.infra.controller.admin.codegen.vo.column.CodegenColumnRespVO; +import com.tashow.cloud.infra.controller.admin.codegen.vo.table.CodegenTableRespVO; +import com.tashow.cloud.infra.dal.dataobject.codegen.CodegenColumnDO; +import com.tashow.cloud.infra.dal.dataobject.codegen.CodegenTableDO; +import com.baomidou.mybatisplus.generator.config.po.TableField; +import com.baomidou.mybatisplus.generator.config.po.TableInfo; +import org.apache.ibatis.type.JdbcType; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.Mappings; +import org.mapstruct.Named; +import org.mapstruct.factory.Mappers; + +import java.util.List; +import java.util.Map; + +@Mapper +public interface CodegenConvert { + + CodegenConvert INSTANCE = Mappers.getMapper(CodegenConvert.class); + + // ========== TableInfo 相关 ========== + + @Mappings({ + @Mapping(source = "name", target = "tableName"), + @Mapping(source = "comment", target = "tableComment"), + }) + CodegenTableDO convert(TableInfo bean); + + List convertList(List list); + + @Mappings({ + @Mapping(source = "name", target = "columnName"), + @Mapping(source = "metaInfo.jdbcType", target = "dataType", qualifiedByName = "getDataType"), + @Mapping(source = "comment", target = "columnComment"), + @Mapping(source = "metaInfo.nullable", target = "nullable"), + @Mapping(source = "keyFlag", target = "primaryKey"), + @Mapping(source = "columnType.type", target = "javaType"), + @Mapping(source = "propertyName", target = "javaField"), + }) + CodegenColumnDO convert(TableField bean); + + @Named("getDataType") + default String getDataType(JdbcType jdbcType) { + return jdbcType.name(); + } + + // ========== 其它 ========== + + default CodegenDetailRespVO convert(CodegenTableDO table, List columns) { + CodegenDetailRespVO respVO = new CodegenDetailRespVO(); + respVO.setTable(BeanUtils.toBean(table, CodegenTableRespVO.class)); + respVO.setColumns(BeanUtils.toBean(columns, CodegenColumnRespVO.class)); + return respVO; + } + + default List convert(Map codes) { + return CollectionUtils.convertList(codes.entrySet(), + entry -> new CodegenPreviewRespVO().setFilePath(entry.getKey()).setCode(entry.getValue())); + } + +} diff --git a/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/convert/config/ConfigConvert.java b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/convert/config/ConfigConvert.java new file mode 100644 index 0000000..8548296 --- /dev/null +++ b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/convert/config/ConfigConvert.java @@ -0,0 +1,28 @@ +package com.tashow.cloud.infra.convert.config; + +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.infra.controller.admin.config.vo.ConfigRespVO; +import com.tashow.cloud.infra.controller.admin.config.vo.ConfigSaveReqVO; +import com.tashow.cloud.infra.dal.dataobject.config.ConfigDO; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.factory.Mappers; + +import java.util.List; + +@Mapper +public interface ConfigConvert { + + ConfigConvert INSTANCE = Mappers.getMapper(ConfigConvert.class); + + PageResult convertPage(PageResult page); + + List convertList(List list); + + @Mapping(source = "configKey", target = "key") + ConfigRespVO convert(ConfigDO bean); + + @Mapping(source = "key", target = "configKey") + ConfigDO convert(ConfigSaveReqVO bean); + +} diff --git a/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/convert/file/FileConfigConvert.java b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/convert/file/FileConfigConvert.java new file mode 100644 index 0000000..ed141ec --- /dev/null +++ b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/convert/file/FileConfigConvert.java @@ -0,0 +1,22 @@ +package com.tashow.cloud.infra.convert.file; + +import com.tashow.cloud.infra.controller.admin.file.vo.config.FileConfigSaveReqVO; +import com.tashow.cloud.infra.dal.dataobject.file.FileConfigDO; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.factory.Mappers; + +/** + * 文件配置 Convert + * + * @author 芋道源码 + */ +@Mapper +public interface FileConfigConvert { + + FileConfigConvert INSTANCE = Mappers.getMapper(FileConfigConvert.class); + + @Mapping(target = "config", ignore = true) + FileConfigDO convert(FileConfigSaveReqVO bean); + +} diff --git a/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/convert/package-info.java b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/convert/package-info.java new file mode 100644 index 0000000..dc3d339 --- /dev/null +++ b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/convert/package-info.java @@ -0,0 +1,6 @@ +/** + * 提供 POJO 类的实体转换 + * + * 目前使用 MapStruct 框架 + */ +package com.tashow.cloud.infra.convert; diff --git a/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/convert/redis/RedisConvert.java b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/convert/redis/RedisConvert.java new file mode 100644 index 0000000..00cc775 --- /dev/null +++ b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/convert/redis/RedisConvert.java @@ -0,0 +1,29 @@ +package com.tashow.cloud.infra.convert.redis; + +import cn.hutool.core.util.StrUtil; +import com.tashow.cloud.infra.controller.admin.redis.vo.RedisMonitorRespVO; +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +import java.util.ArrayList; +import java.util.Properties; + +@Mapper +public interface RedisConvert { + + RedisConvert INSTANCE = Mappers.getMapper(RedisConvert.class); + + default RedisMonitorRespVO build(Properties info, Long dbSize, Properties commandStats) { + RedisMonitorRespVO respVO = RedisMonitorRespVO.builder().info(info).dbSize(dbSize) + .commandStats(new ArrayList<>(commandStats.size())).build(); + commandStats.forEach((key, value) -> { + respVO.getCommandStats().add(RedisMonitorRespVO.CommandStat.builder() + .command(StrUtil.subAfter((String) key, "cmdstat_", false)) + .calls(Long.valueOf(StrUtil.subBetween((String) value, "calls=", ","))) + .usec(Long.valueOf(StrUtil.subBetween((String) value, "usec=", ","))) + .build()); + }); + return respVO; + } + +} diff --git a/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/convert/《芋道 Spring Boot 对象转换 MapStruct 入门》.md b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/convert/《芋道 Spring Boot 对象转换 MapStruct 入门》.md new file mode 100644 index 0000000..8153487 --- /dev/null +++ b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/convert/《芋道 Spring Boot 对象转换 MapStruct 入门》.md @@ -0,0 +1 @@ + diff --git a/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/dal/dataobject/codegen/CodegenColumnDO.java b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/dal/dataobject/codegen/CodegenColumnDO.java new file mode 100644 index 0000000..1b0b322 --- /dev/null +++ b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/dal/dataobject/codegen/CodegenColumnDO.java @@ -0,0 +1,140 @@ +package com.tashow.cloud.infra.dal.dataobject.codegen; + +import com.tashow.cloud.mybatis.mybatis.core.dataobject.BaseDO; +import com.tashow.cloud.infra.enums.codegen.CodegenColumnHtmlTypeEnum; +import com.tashow.cloud.infra.enums.codegen.CodegenColumnListConditionEnum; +import com.tashow.cloud.infra.enums.codegen.CodegenColumnHtmlTypeEnum; +import com.tashow.cloud.infra.enums.codegen.CodegenColumnListConditionEnum; +import com.baomidou.mybatisplus.annotation.KeySequence; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.generator.config.po.TableField; +import com.tashow.cloud.infra.enums.codegen.CodegenColumnHtmlTypeEnum; +import com.tashow.cloud.infra.enums.codegen.CodegenColumnListConditionEnum; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.experimental.Accessors; + +/** + * 代码生成 column 字段定义 + * + * @author 芋道源码 + */ +@TableName(value = "infra_codegen_column", autoResultMap = true) +@KeySequence("infra_codegen_column_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 +@Data +@Accessors(chain = true) +@EqualsAndHashCode(callSuper = true) +public class CodegenColumnDO extends BaseDO { + + /** + * ID 编号 + */ + @TableId + private Long id; + /** + * 表编号 + *

+ * 关联 {@link CodegenTableDO#getId()} + */ + private Long tableId; + + // ========== 表相关字段 ========== + + /** + * 字段名 + * + * 关联 {@link TableField#getName()} + */ + private String columnName; + /** + * 数据库字段类型 + * + * 关联 {@link TableField.MetaInfo#getJdbcType()} + */ + private String dataType; + /** + * 字段描述 + * + * 关联 {@link TableField#getComment()} + */ + private String columnComment; + /** + * 是否允许为空 + * + * 关联 {@link TableField.MetaInfo#isNullable()} + */ + private Boolean nullable; + /** + * 是否主键 + * + * 关联 {@link TableField#isKeyFlag()} + */ + private Boolean primaryKey; + /** + * 排序 + */ + private Integer ordinalPosition; + + // ========== Java 相关字段 ========== + + /** + * Java 属性类型 + * + * 例如说 String、Boolean 等等 + * + * 关联 {@link TableField#getColumnType()} + */ + private String javaType; + /** + * Java 属性名 + * + * 关联 {@link TableField#getPropertyName()} + */ + private String javaField; + /** + * 字典类型 + *

+ * 关联 DictTypeDO 的 type 属性 + */ + private String dictType; + /** + * 数据示例,主要用于生成 Swagger 注解的 example 字段 + */ + private String example; + + // ========== CRUD 相关字段 ========== + + /** + * 是否为 Create 创建操作的字段 + */ + private Boolean createOperation; + /** + * 是否为 Update 更新操作的字段 + */ + private Boolean updateOperation; + /** + * 是否为 List 查询操作的字段 + */ + private Boolean listOperation; + /** + * List 查询操作的条件类型 + *

+ * 枚举 {@link CodegenColumnListConditionEnum} + */ + private String listOperationCondition; + /** + * 是否为 List 查询操作的返回字段 + */ + private Boolean listOperationResult; + + // ========== UI 相关字段 ========== + + /** + * 显示类型 + *

+ * 枚举 {@link CodegenColumnHtmlTypeEnum} + */ + private String htmlType; + +} diff --git a/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/dal/dataobject/codegen/CodegenTableDO.java b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/dal/dataobject/codegen/CodegenTableDO.java new file mode 100644 index 0000000..416258f --- /dev/null +++ b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/dal/dataobject/codegen/CodegenTableDO.java @@ -0,0 +1,164 @@ +package com.tashow.cloud.infra.dal.dataobject.codegen; + +import com.tashow.cloud.mybatis.mybatis.core.dataobject.BaseDO; +import com.tashow.cloud.infra.enums.codegen.CodegenFrontTypeEnum; +import com.tashow.cloud.infra.enums.codegen.CodegenSceneEnum; +import com.tashow.cloud.infra.enums.codegen.CodegenTemplateTypeEnum; +import com.tashow.cloud.infra.dal.dataobject.db.DataSourceConfigDO; +import com.tashow.cloud.infra.enums.codegen.CodegenFrontTypeEnum; +import com.tashow.cloud.infra.enums.codegen.CodegenSceneEnum; +import com.tashow.cloud.infra.enums.codegen.CodegenTemplateTypeEnum; +import com.baomidou.mybatisplus.annotation.KeySequence; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.generator.config.po.TableInfo; +import com.tashow.cloud.infra.enums.codegen.CodegenFrontTypeEnum; +import com.tashow.cloud.infra.enums.codegen.CodegenSceneEnum; +import com.tashow.cloud.infra.enums.codegen.CodegenTemplateTypeEnum; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.experimental.Accessors; + +/** + * 代码生成 table 表定义 + * + * @author 芋道源码 + */ +@TableName(value = "infra_codegen_table", autoResultMap = true) +@KeySequence("infra_codegen_table_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 +@Data +@Accessors(chain = true) +@EqualsAndHashCode(callSuper = true) +public class CodegenTableDO extends BaseDO { + + /** + * ID 编号 + */ + @TableId + private Long id; + + /** + * 数据源编号 + * + * 关联 {@link DataSourceConfigDO#getId()} + */ + private Long dataSourceConfigId; + /** + * 生成场景 + * + * 枚举 {@link CodegenSceneEnum} + */ + private Integer scene; + + // ========== 表相关字段 ========== + + /** + * 表名称 + * + * 关联 {@link TableInfo#getName()} + */ + private String tableName; + /** + * 表描述 + * + * 关联 {@link TableInfo#getComment()} + */ + private String tableComment; + /** + * 备注 + */ + private String remark; + + // ========== 类相关字段 ========== + + /** + * 模块名,即一级目录 + * + * 例如说,system、infra、tool 等等 + */ + private String moduleName; + /** + * 业务名,即二级目录 + * + * 例如说,user、permission、dict 等等 + */ + private String businessName; + /** + * 类名称(首字母大写) + * + * 例如说,SysUser、SysMenu、SysDictData 等等 + */ + private String className; + /** + * 类描述 + */ + private String classComment; + /** + * 作者 + */ + private String author; + + // ========== 生成相关字段 ========== + + /** + * 模板类型 + * + * 枚举 {@link CodegenTemplateTypeEnum} + */ + private Integer templateType; + /** + * 代码生成的前端类型 + * + * 枚举 {@link CodegenFrontTypeEnum} + */ + private Integer frontType; + + // ========== 菜单相关字段 ========== + + /** + * 父菜单编号 + * + * 关联 MenuDO 的 id 属性 + */ + private Long parentMenuId; + + // ========== 主子表相关字段 ========== + + /** + * 主表的编号 + * + * 关联 {@link CodegenTableDO#getId()} + */ + private Long masterTableId; + /** + * 【自己】子表关联主表的字段编号 + * + * 关联 {@link CodegenColumnDO#getId()} + */ + private Long subJoinColumnId; + /** + * 主表与子表是否一对多 + * + * true:一对多 + * false:一对一 + */ + private Boolean subJoinMany; + + // ========== 树表相关字段 ========== + + /** + * 树表的父字段编号 + * + * 关联 {@link CodegenColumnDO#getId()} + */ + private Long treeParentColumnId; + /** + * 树表的名字字段编号 + * + * 名字的用途:新增或修改时,select 框展示的字段 + * + * 关联 {@link CodegenColumnDO#getId()} + */ + private Long treeNameColumnId; + +} diff --git a/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/dal/dataobject/config/ConfigDO.java b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/dal/dataobject/config/ConfigDO.java new file mode 100644 index 0000000..1834f48 --- /dev/null +++ b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/dal/dataobject/config/ConfigDO.java @@ -0,0 +1,66 @@ +package com.tashow.cloud.infra.dal.dataobject.config; + +import com.tashow.cloud.mybatis.mybatis.core.dataobject.BaseDO; +import com.tashow.cloud.infra.enums.config.ConfigTypeEnum; +import com.tashow.cloud.infra.enums.config.ConfigTypeEnum; +import com.baomidou.mybatisplus.annotation.KeySequence; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import com.tashow.cloud.infra.enums.config.ConfigTypeEnum; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; + +/** + * 参数配置表 + * + * @author 芋道源码 + */ +@TableName("infra_config") +@KeySequence("infra_config_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class ConfigDO extends BaseDO { + + /** + * 参数主键 + */ + @TableId + private Long id; + /** + * 参数分类 + */ + private String category; + /** + * 参数名称 + */ + private String name; + /** + * 参数键名 + * + * 支持多 DB 类型时,无法直接使用 key + @TableField("config_key") 来实现转换,原因是 "config_key" AS key 而存在报错 + */ + private String configKey; + /** + * 参数键值 + */ + private String value; + /** + * 参数类型 + * + * 枚举 {@link ConfigTypeEnum} + */ + private Integer type; + /** + * 是否可见 + * + * 不可见的参数,一般是敏感参数,前端不可获取 + */ + private Boolean visible; + /** + * 备注 + */ + private String remark; + +} diff --git a/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/dal/dataobject/db/DataSourceConfigDO.java b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/dal/dataobject/db/DataSourceConfigDO.java new file mode 100644 index 0000000..39dfa3d --- /dev/null +++ b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/dal/dataobject/db/DataSourceConfigDO.java @@ -0,0 +1,48 @@ +package com.tashow.cloud.infra.dal.dataobject.db; + +import com.tashow.cloud.mybatis.mybatis.core.dataobject.BaseDO; +import com.baomidou.mybatisplus.annotation.KeySequence; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableName; +import com.tashow.cloud.mybatis.mybatis.core.type.EncryptTypeHandler; +import lombok.Data; + +/** + * 数据源配置 + * + * @author 芋道源码 + */ +@TableName(value = "infra_data_source_config", autoResultMap = true) +@KeySequence("infra_data_source_config_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 +@Data +public class DataSourceConfigDO extends BaseDO { + + /** + * 主键编号 - Master 数据源 + */ + public static final Long ID_MASTER = 0L; + + /** + * 主键编号 + */ + private Long id; + /** + * 连接名 + */ + private String name; + + /** + * 数据源连接 + */ + private String url; + /** + * 用户名 + */ + private String username; + /** + * 密码 + */ + @TableField(typeHandler = EncryptTypeHandler.class) + private String password; + +} diff --git a/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/dal/dataobject/demo/demo01/Demo01ContactDO.java b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/dal/dataobject/demo/demo01/Demo01ContactDO.java new file mode 100644 index 0000000..35d72c1 --- /dev/null +++ b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/dal/dataobject/demo/demo01/Demo01ContactDO.java @@ -0,0 +1,54 @@ +package com.tashow.cloud.infra.dal.dataobject.demo.demo01; + +import com.tashow.cloud.mybatis.mybatis.core.dataobject.BaseDO; +import com.baomidou.mybatisplus.annotation.KeySequence; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.*; + +import java.time.LocalDateTime; + +/** + * 示例联系人 DO + * + * @author 芋道源码 + */ +@TableName("yudao_demo01_contact") +@KeySequence("yudao_demo01_contact_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class Demo01ContactDO extends BaseDO { + + /** + * 编号 + */ + @TableId + private Long id; + /** + * 名字 + */ + private String name; + /** + * 性别 + * + * 枚举 {@link TODO system_user_sex 对应的类} + */ + private Integer sex; + /** + * 出生年 + */ + private LocalDateTime birthday; + /** + * 简介 + */ + private String description; + /** + * 头像 + */ + private String avatar; + +} diff --git a/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/dal/dataobject/demo/demo02/Demo02CategoryDO.java b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/dal/dataobject/demo/demo02/Demo02CategoryDO.java new file mode 100644 index 0000000..1b6bb2b --- /dev/null +++ b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/dal/dataobject/demo/demo02/Demo02CategoryDO.java @@ -0,0 +1,40 @@ +package com.tashow.cloud.infra.dal.dataobject.demo.demo02; + +import com.tashow.cloud.mybatis.mybatis.core.dataobject.BaseDO; +import com.baomidou.mybatisplus.annotation.KeySequence; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.*; + +/** + * 示例分类 DO + * + * @author 芋道源码 + */ +@TableName("yudao_demo02_category") +@KeySequence("yudao_demo02_category_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class Demo02CategoryDO extends BaseDO { + + public static final Long PARENT_ID_ROOT = 0L; + + /** + * 编号 + */ + @TableId + private Long id; + /** + * 名字 + */ + private String name; + /** + * 父级编号 + */ + private Long parentId; + +} diff --git a/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/dal/dataobject/demo/demo03/Demo03CourseDO.java b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/dal/dataobject/demo/demo03/Demo03CourseDO.java new file mode 100644 index 0000000..488cba5 --- /dev/null +++ b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/dal/dataobject/demo/demo03/Demo03CourseDO.java @@ -0,0 +1,42 @@ +package com.tashow.cloud.infra.dal.dataobject.demo.demo03; + +import com.tashow.cloud.mybatis.mybatis.core.dataobject.BaseDO; +import com.baomidou.mybatisplus.annotation.KeySequence; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.*; + +/** + * 学生课程 DO + * + * @author 芋道源码 + */ +@TableName("yudao_demo03_course") +@KeySequence("yudao_demo03_course_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class Demo03CourseDO extends BaseDO { + + /** + * 编号 + */ + @TableId + private Long id; + /** + * 学生编号 + */ + private Long studentId; + /** + * 名字 + */ + private String name; + /** + * 分数 + */ + private Integer score; + +} diff --git a/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/dal/dataobject/demo/demo03/Demo03GradeDO.java b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/dal/dataobject/demo/demo03/Demo03GradeDO.java new file mode 100644 index 0000000..3254e22 --- /dev/null +++ b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/dal/dataobject/demo/demo03/Demo03GradeDO.java @@ -0,0 +1,42 @@ +package com.tashow.cloud.infra.dal.dataobject.demo.demo03; + +import com.tashow.cloud.mybatis.mybatis.core.dataobject.BaseDO; +import com.baomidou.mybatisplus.annotation.KeySequence; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.*; + +/** + * 学生班级 DO + * + * @author 芋道源码 + */ +@TableName("yudao_demo03_grade") +@KeySequence("yudao_demo03_grade_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class Demo03GradeDO extends BaseDO { + + /** + * 编号 + */ + @TableId + private Long id; + /** + * 学生编号 + */ + private Long studentId; + /** + * 名字 + */ + private String name; + /** + * 班主任 + */ + private String teacher; + +} diff --git a/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/dal/dataobject/demo/demo03/Demo03StudentDO.java b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/dal/dataobject/demo/demo03/Demo03StudentDO.java new file mode 100644 index 0000000..b3397a1 --- /dev/null +++ b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/dal/dataobject/demo/demo03/Demo03StudentDO.java @@ -0,0 +1,50 @@ +package com.tashow.cloud.infra.dal.dataobject.demo.demo03; + +import com.tashow.cloud.mybatis.mybatis.core.dataobject.BaseDO; +import com.baomidou.mybatisplus.annotation.KeySequence; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.*; + +import java.time.LocalDateTime; + +/** + * 学生 DO + * + * @author 芋道源码 + */ +@TableName("yudao_demo03_student") +@KeySequence("yudao_demo03_student_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class Demo03StudentDO extends BaseDO { + + /** + * 编号 + */ + @TableId + private Long id; + /** + * 名字 + */ + private String name; + /** + * 性别 + * + * 枚举 {@link TODO system_user_sex 对应的类} + */ + private Integer sex; + /** + * 出生日期 + */ + private LocalDateTime birthday; + /** + * 简介 + */ + private String description; + +} diff --git a/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/dal/dataobject/file/FileConfigDO.java b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/dal/dataobject/file/FileConfigDO.java new file mode 100644 index 0000000..e18b8ca --- /dev/null +++ b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/dal/dataobject/file/FileConfigDO.java @@ -0,0 +1,111 @@ +package com.tashow.cloud.infra.dal.dataobject.file; + +import cn.hutool.core.util.StrUtil; +import com.tashow.cloud.common.util.json.JsonUtils; +import com.tashow.cloud.mybatis.mybatis.core.dataobject.BaseDO; +import com.tashow.cloud.infra.framework.file.core.client.FileClientConfig; +import com.tashow.cloud.infra.framework.file.core.client.db.DBFileClientConfig; +import com.tashow.cloud.infra.framework.file.core.client.ftp.FtpFileClientConfig; +import com.tashow.cloud.infra.framework.file.core.client.local.LocalFileClientConfig; +import com.tashow.cloud.infra.framework.file.core.client.s3.S3FileClientConfig; +import com.tashow.cloud.infra.framework.file.core.client.sftp.SftpFileClientConfig; +import com.tashow.cloud.infra.framework.file.core.enums.FileStorageEnum; +import com.baomidou.mybatisplus.annotation.KeySequence; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.extension.handlers.AbstractJsonTypeHandler; +import com.fasterxml.jackson.core.type.TypeReference; +import lombok.*; + +import java.lang.reflect.Field; + +/** + * 文件配置表 + * + * @author 芋道源码 + */ +@TableName(value = "infra_file_config", autoResultMap = true) +@KeySequence("infra_file_config_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class FileConfigDO extends BaseDO { + + /** + * 配置编号,数据库自增 + */ + private Long id; + /** + * 配置名 + */ + private String name; + /** + * 存储器 + * + * 枚举 {@link FileStorageEnum} + */ + private Integer storage; + /** + * 备注 + */ + private String remark; + /** + * 是否为主配置 + * + * 由于我们可以配置多个文件配置,默认情况下,使用主配置进行文件的上传 + */ + private Boolean master; + + /** + * 支付渠道配置 + */ + @TableField(typeHandler = FileClientConfigTypeHandler.class) + private FileClientConfig config; + + public static class FileClientConfigTypeHandler extends AbstractJsonTypeHandler { + + public FileClientConfigTypeHandler(Class type) { + super(type); + } + + public FileClientConfigTypeHandler(Class type, Field field) { + super(type, field); + } + + @Override + public Object parse(String json) { + FileClientConfig config = JsonUtils.parseObjectQuietly(json, new TypeReference<>() {}); + if (config != null) { + return config; + } + + // 兼容老版本的包路径 + String className = JsonUtils.parseObject(json, "@class", String.class); + className = StrUtil.subAfter(className, ".", true); + switch (className) { + case "DBFileClientConfig": + return JsonUtils.parseObject2(json, DBFileClientConfig.class); + case "FtpFileClientConfig": + return JsonUtils.parseObject2(json, FtpFileClientConfig.class); + case "LocalFileClientConfig": + return JsonUtils.parseObject2(json, LocalFileClientConfig.class); + case "SftpFileClientConfig": + return JsonUtils.parseObject2(json, SftpFileClientConfig.class); + case "S3FileClientConfig": + return JsonUtils.parseObject2(json, S3FileClientConfig.class); + default: + throw new IllegalArgumentException("未知的 FileClientConfig 类型:" + json); + } + } + + @Override + public String toJson(Object obj) { + return JsonUtils.toJsonString(obj); + } + + } + +} diff --git a/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/dal/dataobject/file/FileContentDO.java b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/dal/dataobject/file/FileContentDO.java new file mode 100644 index 0000000..23b8117 --- /dev/null +++ b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/dal/dataobject/file/FileContentDO.java @@ -0,0 +1,48 @@ +package com.tashow.cloud.infra.dal.dataobject.file; + +import com.tashow.cloud.mybatis.mybatis.core.dataobject.BaseDO; +import com.tashow.cloud.infra.framework.file.core.client.db.DBFileClient; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.KeySequence; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.*; + +/** + * 文件内容表 + * + * 专门用于存储 {@link DBFileClient} 的文件内容 + * + * @author 芋道源码 + */ +@TableName("infra_file_content") +@KeySequence("infra_file_content_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class FileContentDO extends BaseDO { + + /** + * 编号,数据库自增 + */ + @TableId + private Long id; + /** + * 配置编号 + * + * 关联 {@link FileConfigDO#getId()} + */ + private Long configId; + /** + * 路径,即文件名 + */ + private String path; + /** + * 文件内容 + */ + private byte[] content; + +} diff --git a/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/dal/dataobject/file/FileDO.java b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/dal/dataobject/file/FileDO.java new file mode 100644 index 0000000..60213ac --- /dev/null +++ b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/dal/dataobject/file/FileDO.java @@ -0,0 +1,55 @@ +package com.tashow.cloud.infra.dal.dataobject.file; + +import com.tashow.cloud.mybatis.mybatis.core.dataobject.BaseDO; +import com.baomidou.mybatisplus.annotation.KeySequence; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.*; + +/** + * 文件表 + * 每次文件上传,都会记录一条记录到该表中 + * + * @author 芋道源码 + */ +@TableName("infra_file") +@KeySequence("infra_file_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class FileDO extends BaseDO { + + /** + * 编号,数据库自增 + */ + private Long id; + /** + * 配置编号 + * + * 关联 {@link FileConfigDO#getId()} + */ + private Long configId; + /** + * 原文件名 + */ + private String name; + /** + * 路径,即文件名 + */ + private String path; + /** + * 访问地址 + */ + private String url; + /** + * 文件的 MIME 类型,例如 "application/octet-stream" + */ + private String type; + /** + * 文件大小 + */ + private Integer size; + +} diff --git a/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/dal/dataobject/logger/ApiAccessLogDO.java b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/dal/dataobject/logger/ApiAccessLogDO.java new file mode 100644 index 0000000..6d2221b --- /dev/null +++ b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/dal/dataobject/logger/ApiAccessLogDO.java @@ -0,0 +1,140 @@ +package com.tashow.cloud.infra.dal.dataobject.logger; + +import com.tashow.cloud.common.enums.UserTypeEnum; +import com.tashow.cloud.common.pojo.CommonResult; +import com.tashow.cloud.mybatis.mybatis.core.dataobject.BaseDO; +import com.baomidou.mybatisplus.annotation.KeySequence; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import com.tashow.cloud.web.apilog.core.enums.OperateTypeEnum; +import lombok.*; + +import java.time.LocalDateTime; + +/** + * API 访问日志 + * + * @author 芋道源码 + */ +@TableName("infra_api_access_log") +@KeySequence(value = "infra_api_access_log_seq") +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class ApiAccessLogDO extends BaseDO { + + /** + * {@link #requestParams} 的最大长度 + */ + public static final Integer REQUEST_PARAMS_MAX_LENGTH = 8000; + + /** + * {@link #resultMsg} 的最大长度 + */ + public static final Integer RESULT_MSG_MAX_LENGTH = 512; + + /** + * 编号 + */ + @TableId + private Long id; + /** + * 链路追踪编号 + * + * 一般来说,通过链路追踪编号,可以将访问日志,错误日志,链路追踪日志,logger 打印日志等,结合在一起,从而进行排错。 + */ + private String traceId; + /** + * 用户编号 + */ + private Long userId; + /** + * 用户类型 + * + * 枚举 {@link UserTypeEnum} + */ + private Integer userType; + /** + * 应用名 + * + * 目前读取 `spring.application.name` 配置项 + */ + private String applicationName; + + // ========== 请求相关字段 ========== + + /** + * 请求方法名 + */ + private String requestMethod; + /** + * 访问地址 + */ + private String requestUrl; + /** + * 请求参数 + * + * query: Query String + * body: Quest Body + */ + private String requestParams; + /** + * 响应结果 + */ + private String responseBody; + /** + * 用户 IP + */ + private String userIp; + /** + * 浏览器 UA + */ + private String userAgent; + + // ========== 执行相关字段 ========== + + /** + * 操作模块 + */ + private String operateModule; + /** + * 操作名 + */ + private String operateName; + /** + * 操作分类 + * + * 枚举 {@link OperateTypeEnum} + */ + private Integer operateType; + + /** + * 开始请求时间 + */ + private LocalDateTime beginTime; + /** + * 结束请求时间 + */ + private LocalDateTime endTime; + /** + * 执行时长,单位:毫秒 + */ + private Integer duration; + + /** + * 结果码 + * + * 目前使用的 {@link CommonResult#getCode()} 属性 + */ + private Integer resultCode; + /** + * 结果提示 + * + * 目前使用的 {@link CommonResult#getMsg()} 属性 + */ + private String resultMsg; + +} diff --git a/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/dal/dataobject/logger/ApiErrorLogDO.java b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/dal/dataobject/logger/ApiErrorLogDO.java new file mode 100644 index 0000000..ad3b299 --- /dev/null +++ b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/dal/dataobject/logger/ApiErrorLogDO.java @@ -0,0 +1,163 @@ +package com.tashow.cloud.infra.dal.dataobject.logger; + +import com.tashow.cloud.common.enums.UserTypeEnum; +import com.tashow.cloud.mybatis.mybatis.core.dataobject.BaseDO; +import com.tashow.cloud.infra.enums.logger.ApiErrorLogProcessStatusEnum; +import com.tashow.cloud.infra.enums.logger.ApiErrorLogProcessStatusEnum; +import com.baomidou.mybatisplus.annotation.KeySequence; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import com.tashow.cloud.infra.enums.logger.ApiErrorLogProcessStatusEnum; +import lombok.*; + +import java.time.LocalDateTime; + +/** + * API 异常数据 + * + * @author 芋道源码 + */ +@TableName("infra_api_error_log") +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +@Builder +@NoArgsConstructor +@AllArgsConstructor +@KeySequence(value = "infra_api_error_log_seq") +public class ApiErrorLogDO extends BaseDO { + + /** + * {@link #requestParams} 的最大长度 + */ + public static final Integer REQUEST_PARAMS_MAX_LENGTH = 8000; + + /** + * 编号 + */ + @TableId + private Long id; + /** + * 用户编号 + */ + private Long userId; + /** + * 链路追踪编号 + * + * 一般来说,通过链路追踪编号,可以将访问日志,错误日志,链路追踪日志,logger 打印日志等,结合在一起,从而进行排错。 + */ + private String traceId; + /** + * 用户类型 + * + * 枚举 {@link UserTypeEnum} + */ + private Integer userType; + /** + * 应用名 + * + * 目前读取 spring.application.name + */ + private String applicationName; + + // ========== 请求相关字段 ========== + + /** + * 请求方法名 + */ + private String requestMethod; + /** + * 访问地址 + */ + private String requestUrl; + /** + * 请求参数 + * + * query: Query String + * body: Quest Body + */ + private String requestParams; + /** + * 用户 IP + */ + private String userIp; + /** + * 浏览器 UA + */ + private String userAgent; + + // ========== 异常相关字段 ========== + + /** + * 异常发生时间 + */ + private LocalDateTime exceptionTime; + /** + * 异常名 + * + * {@link Throwable#getClass()} 的类全名 + */ + private String exceptionName; + /** + * 异常导致的消息 + * + * {@link cn.hutool.core.exceptions.ExceptionUtil#getMessage(Throwable)} + */ + private String exceptionMessage; + /** + * 异常导致的根消息 + * + * {@link cn.hutool.core.exceptions.ExceptionUtil#getRootCauseMessage(Throwable)} + */ + private String exceptionRootCauseMessage; + /** + * 异常的栈轨迹 + * + * {@link org.apache.commons.lang3.exception.ExceptionUtils#getStackTrace(Throwable)} + */ + private String exceptionStackTrace; + /** + * 异常发生的类全名 + * + * {@link StackTraceElement#getClassName()} + */ + private String exceptionClassName; + /** + * 异常发生的类文件 + * + * {@link StackTraceElement#getFileName()} + */ + private String exceptionFileName; + /** + * 异常发生的方法名 + * + * {@link StackTraceElement#getMethodName()} + */ + private String exceptionMethodName; + /** + * 异常发生的方法所在行 + * + * {@link StackTraceElement#getLineNumber()} + */ + private Integer exceptionLineNumber; + + // ========== 处理相关字段 ========== + + /** + * 处理状态 + * + * 枚举 {@link ApiErrorLogProcessStatusEnum} + */ + private Integer processStatus; + /** + * 处理时间 + */ + private LocalDateTime processTime; + /** + * 处理用户编号 + * + * 关联 cn.iocoder.yudao.adminserver.modules.system.dal.dataobject.user.SysUserDO.SysUserDO#getId() + */ + private Long processUserId; + +} diff --git a/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/dal/mysql/codegen/CodegenColumnMapper.java b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/dal/mysql/codegen/CodegenColumnMapper.java new file mode 100644 index 0000000..556fd87 --- /dev/null +++ b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/dal/mysql/codegen/CodegenColumnMapper.java @@ -0,0 +1,26 @@ +package com.tashow.cloud.infra.dal.mysql.codegen; + +import com.tashow.cloud.mybatis.mybatis.core.mapper.BaseMapperX; +import com.tashow.cloud.mybatis.mybatis.core.query.LambdaQueryWrapperX; +import com.tashow.cloud.infra.dal.dataobject.codegen.CodegenColumnDO; +import com.tashow.cloud.infra.dal.dataobject.codegen.CodegenColumnDO; +import com.tashow.cloud.infra.dal.dataobject.codegen.CodegenColumnDO; +import org.apache.ibatis.annotations.Mapper; + +import java.util.List; + +@Mapper +public interface CodegenColumnMapper extends BaseMapperX { + + default List selectListByTableId(Long tableId) { + return selectList(new LambdaQueryWrapperX() + .eq(CodegenColumnDO::getTableId, tableId) + .orderByAsc(CodegenColumnDO::getOrdinalPosition)); + } + + default void deleteListByTableId(Long tableId) { + delete(new LambdaQueryWrapperX() + .eq(CodegenColumnDO::getTableId, tableId)); + } + +} diff --git a/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/dal/mysql/codegen/CodegenTableMapper.java b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/dal/mysql/codegen/CodegenTableMapper.java new file mode 100644 index 0000000..8d810e7 --- /dev/null +++ b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/dal/mysql/codegen/CodegenTableMapper.java @@ -0,0 +1,43 @@ +package com.tashow.cloud.infra.dal.mysql.codegen; + +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.mybatis.mybatis.core.mapper.BaseMapperX; +import com.tashow.cloud.mybatis.mybatis.core.query.LambdaQueryWrapperX; +import com.tashow.cloud.infra.controller.admin.codegen.vo.table.CodegenTablePageReqVO; +import com.tashow.cloud.infra.dal.dataobject.codegen.CodegenTableDO; +import com.tashow.cloud.infra.controller.admin.codegen.vo.table.CodegenTablePageReqVO; +import com.tashow.cloud.infra.dal.dataobject.codegen.CodegenTableDO; +import com.tashow.cloud.infra.controller.admin.codegen.vo.table.CodegenTablePageReqVO; +import com.tashow.cloud.infra.dal.dataobject.codegen.CodegenTableDO; +import org.apache.ibatis.annotations.Mapper; + +import java.util.List; + +@Mapper +public interface CodegenTableMapper extends BaseMapperX { + + default CodegenTableDO selectByTableNameAndDataSourceConfigId(String tableName, Long dataSourceConfigId) { + return selectOne(CodegenTableDO::getTableName, tableName, + CodegenTableDO::getDataSourceConfigId, dataSourceConfigId); + } + + default PageResult selectPage(CodegenTablePageReqVO pageReqVO) { + return selectPage(pageReqVO, new LambdaQueryWrapperX() + .likeIfPresent(CodegenTableDO::getTableName, pageReqVO.getTableName()) + .likeIfPresent(CodegenTableDO::getTableComment, pageReqVO.getTableComment()) + .likeIfPresent(CodegenTableDO::getClassName, pageReqVO.getClassName()) + .betweenIfPresent(CodegenTableDO::getCreateTime, pageReqVO.getCreateTime()) + .orderByDesc(CodegenTableDO::getUpdateTime) + ); + } + + default List selectListByDataSourceConfigId(Long dataSourceConfigId) { + return selectList(CodegenTableDO::getDataSourceConfigId, dataSourceConfigId); + } + + default List selectListByTemplateTypeAndMasterTableId(Integer templateType, Long masterTableId) { + return selectList(CodegenTableDO::getTemplateType, templateType, + CodegenTableDO::getMasterTableId, masterTableId); + } + +} diff --git a/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/dal/mysql/config/ConfigMapper.java b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/dal/mysql/config/ConfigMapper.java new file mode 100644 index 0000000..c877329 --- /dev/null +++ b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/dal/mysql/config/ConfigMapper.java @@ -0,0 +1,27 @@ +package com.tashow.cloud.infra.dal.mysql.config; + +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.mybatis.mybatis.core.mapper.BaseMapperX; +import com.tashow.cloud.mybatis.mybatis.core.query.LambdaQueryWrapperX; +import com.tashow.cloud.infra.dal.dataobject.config.ConfigDO; +import com.tashow.cloud.infra.controller.admin.config.vo.ConfigPageReqVO; +import com.tashow.cloud.infra.dal.dataobject.config.ConfigDO; +import com.tashow.cloud.infra.dal.dataobject.config.ConfigDO; +import org.apache.ibatis.annotations.Mapper; + +@Mapper +public interface ConfigMapper extends BaseMapperX { + + default ConfigDO selectByKey(String key) { + return selectOne(ConfigDO::getConfigKey, key); + } + + default PageResult selectPage(ConfigPageReqVO reqVO) { + return selectPage(reqVO, new LambdaQueryWrapperX() + .likeIfPresent(ConfigDO::getName, reqVO.getName()) + .likeIfPresent(ConfigDO::getConfigKey, reqVO.getKey()) + .eqIfPresent(ConfigDO::getType, reqVO.getType()) + .betweenIfPresent(ConfigDO::getCreateTime, reqVO.getCreateTime())); + } + +} diff --git a/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/dal/mysql/db/DataSourceConfigMapper.java b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/dal/mysql/db/DataSourceConfigMapper.java new file mode 100644 index 0000000..32c7ce9 --- /dev/null +++ b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/dal/mysql/db/DataSourceConfigMapper.java @@ -0,0 +1,14 @@ +package com.tashow.cloud.infra.dal.mysql.db; + +import com.tashow.cloud.mybatis.mybatis.core.mapper.BaseMapperX; +import com.tashow.cloud.infra.dal.dataobject.db.DataSourceConfigDO; +import org.apache.ibatis.annotations.Mapper; + +/** + * 数据源配置 Mapper + * + * @author 芋道源码 + */ +@Mapper +public interface DataSourceConfigMapper extends BaseMapperX { +} diff --git a/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/dal/mysql/file/FileConfigMapper.java b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/dal/mysql/file/FileConfigMapper.java new file mode 100644 index 0000000..889fd72 --- /dev/null +++ b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/dal/mysql/file/FileConfigMapper.java @@ -0,0 +1,27 @@ +package com.tashow.cloud.infra.dal.mysql.file; + +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.mybatis.mybatis.core.mapper.BaseMapperX; +import com.tashow.cloud.mybatis.mybatis.core.query.LambdaQueryWrapperX; +import com.tashow.cloud.infra.dal.dataobject.file.FileConfigDO; +import com.tashow.cloud.infra.controller.admin.file.vo.config.FileConfigPageReqVO; +import com.tashow.cloud.infra.dal.dataobject.file.FileConfigDO; +import com.tashow.cloud.infra.dal.dataobject.file.FileConfigDO; +import org.apache.ibatis.annotations.Mapper; + +@Mapper +public interface FileConfigMapper extends BaseMapperX { + + default PageResult selectPage(FileConfigPageReqVO reqVO) { + return selectPage(reqVO, new LambdaQueryWrapperX() + .likeIfPresent(FileConfigDO::getName, reqVO.getName()) + .eqIfPresent(FileConfigDO::getStorage, reqVO.getStorage()) + .betweenIfPresent(FileConfigDO::getCreateTime, reqVO.getCreateTime()) + .orderByDesc(FileConfigDO::getId)); + } + + default FileConfigDO selectByMaster() { + return selectOne(FileConfigDO::getMaster, true); + } + +} diff --git a/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/dal/mysql/file/FileContentMapper.java b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/dal/mysql/file/FileContentMapper.java new file mode 100644 index 0000000..c2062fe --- /dev/null +++ b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/dal/mysql/file/FileContentMapper.java @@ -0,0 +1,27 @@ +package com.tashow.cloud.infra.dal.mysql.file; + +import com.tashow.cloud.infra.dal.dataobject.file.FileContentDO; +import com.tashow.cloud.infra.dal.dataobject.file.FileContentDO; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.tashow.cloud.infra.dal.dataobject.file.FileContentDO; +import org.apache.ibatis.annotations.Mapper; + +import java.util.List; + +@Mapper +public interface FileContentMapper extends BaseMapper { + + default void deleteByConfigIdAndPath(Long configId, String path) { + this.delete(new LambdaQueryWrapper() + .eq(FileContentDO::getConfigId, configId) + .eq(FileContentDO::getPath, path)); + } + + default List selectListByConfigIdAndPath(Long configId, String path) { + return selectList(new LambdaQueryWrapper() + .eq(FileContentDO::getConfigId, configId) + .eq(FileContentDO::getPath, path)); + } + +} diff --git a/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/dal/mysql/file/FileMapper.java b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/dal/mysql/file/FileMapper.java new file mode 100644 index 0000000..72f4bbd --- /dev/null +++ b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/dal/mysql/file/FileMapper.java @@ -0,0 +1,28 @@ +package com.tashow.cloud.infra.dal.mysql.file; + +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.mybatis.mybatis.core.mapper.BaseMapperX; +import com.tashow.cloud.mybatis.mybatis.core.query.LambdaQueryWrapperX; +import com.tashow.cloud.infra.dal.dataobject.file.FileDO; +import com.tashow.cloud.infra.controller.admin.file.vo.file.FilePageReqVO; +import com.tashow.cloud.infra.dal.dataobject.file.FileDO; +import com.tashow.cloud.infra.dal.dataobject.file.FileDO; +import org.apache.ibatis.annotations.Mapper; + +/** + * 文件操作 Mapper + * + * @author 芋道源码 + */ +@Mapper +public interface FileMapper extends BaseMapperX { + + default PageResult selectPage(FilePageReqVO reqVO) { + return selectPage(reqVO, new LambdaQueryWrapperX() + .likeIfPresent(FileDO::getPath, reqVO.getPath()) + .likeIfPresent(FileDO::getType, reqVO.getType()) + .betweenIfPresent(FileDO::getCreateTime, reqVO.getCreateTime()) + .orderByDesc(FileDO::getId)); + } + +} diff --git a/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/dal/mysql/logger/ApiAccessLogMapper.java b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/dal/mysql/logger/ApiAccessLogMapper.java new file mode 100644 index 0000000..9a7e6ee --- /dev/null +++ b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/dal/mysql/logger/ApiAccessLogMapper.java @@ -0,0 +1,49 @@ +package com.tashow.cloud.infra.dal.mysql.logger; + +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.mybatis.mybatis.core.mapper.BaseMapperX; +import com.tashow.cloud.mybatis.mybatis.core.query.LambdaQueryWrapperX; +import com.tashow.cloud.infra.controller.admin.logger.vo.apiaccesslog.ApiAccessLogPageReqVO; +import com.tashow.cloud.infra.dal.dataobject.logger.ApiAccessLogDO; +import com.tashow.cloud.infra.controller.admin.logger.vo.apiaccesslog.ApiAccessLogPageReqVO; +import com.tashow.cloud.infra.dal.dataobject.logger.ApiAccessLogDO; +import com.tashow.cloud.infra.controller.admin.logger.vo.apiaccesslog.ApiAccessLogPageReqVO; +import com.tashow.cloud.infra.dal.dataobject.logger.ApiAccessLogDO; +import org.apache.ibatis.annotations.Delete; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; + +import java.time.LocalDateTime; + +/** + * API 访问日志 Mapper + * + * @author 芋道源码 + */ +@Mapper +public interface ApiAccessLogMapper extends BaseMapperX { + + default PageResult selectPage(ApiAccessLogPageReqVO reqVO) { + return selectPage(reqVO, new LambdaQueryWrapperX() + .eqIfPresent(ApiAccessLogDO::getUserId, reqVO.getUserId()) + .eqIfPresent(ApiAccessLogDO::getUserType, reqVO.getUserType()) + .eqIfPresent(ApiAccessLogDO::getApplicationName, reqVO.getApplicationName()) + .likeIfPresent(ApiAccessLogDO::getRequestUrl, reqVO.getRequestUrl()) + .betweenIfPresent(ApiAccessLogDO::getBeginTime, reqVO.getBeginTime()) + .geIfPresent(ApiAccessLogDO::getDuration, reqVO.getDuration()) + .eqIfPresent(ApiAccessLogDO::getResultCode, reqVO.getResultCode()) + .orderByDesc(ApiAccessLogDO::getId) + ); + } + + /** + * 物理删除指定时间之前的日志 + * + * @param createTime 最大时间 + * @param limit 删除条数,防止一次删除太多 + * @return 删除条数 + */ + @Delete("DELETE FROM infra_api_access_log WHERE create_time < #{createTime} LIMIT #{limit}") + Integer deleteByCreateTimeLt(@Param("createTime") LocalDateTime createTime, @Param("limit") Integer limit); + +} diff --git a/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/dal/mysql/logger/ApiErrorLogMapper.java b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/dal/mysql/logger/ApiErrorLogMapper.java new file mode 100644 index 0000000..2b16482 --- /dev/null +++ b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/dal/mysql/logger/ApiErrorLogMapper.java @@ -0,0 +1,48 @@ +package com.tashow.cloud.infra.dal.mysql.logger; + +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.mybatis.mybatis.core.mapper.BaseMapperX; +import com.tashow.cloud.mybatis.mybatis.core.query.LambdaQueryWrapperX; +import com.tashow.cloud.infra.controller.admin.logger.vo.apierrorlog.ApiErrorLogPageReqVO; +import com.tashow.cloud.infra.dal.dataobject.logger.ApiErrorLogDO; +import com.tashow.cloud.infra.controller.admin.logger.vo.apierrorlog.ApiErrorLogPageReqVO; +import com.tashow.cloud.infra.dal.dataobject.logger.ApiErrorLogDO; +import com.tashow.cloud.infra.controller.admin.logger.vo.apierrorlog.ApiErrorLogPageReqVO; +import com.tashow.cloud.infra.dal.dataobject.logger.ApiErrorLogDO; +import org.apache.ibatis.annotations.Delete; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; + +import java.time.LocalDateTime; + +/** + * API 错误日志 Mapper + * + * @author 芋道源码 + */ +@Mapper +public interface ApiErrorLogMapper extends BaseMapperX { + + default PageResult selectPage(ApiErrorLogPageReqVO reqVO) { + return selectPage(reqVO, new LambdaQueryWrapperX() + .eqIfPresent(ApiErrorLogDO::getUserId, reqVO.getUserId()) + .eqIfPresent(ApiErrorLogDO::getUserType, reqVO.getUserType()) + .eqIfPresent(ApiErrorLogDO::getApplicationName, reqVO.getApplicationName()) + .likeIfPresent(ApiErrorLogDO::getRequestUrl, reqVO.getRequestUrl()) + .betweenIfPresent(ApiErrorLogDO::getExceptionTime, reqVO.getExceptionTime()) + .eqIfPresent(ApiErrorLogDO::getProcessStatus, reqVO.getProcessStatus()) + .orderByDesc(ApiErrorLogDO::getId) + ); + } + + /** + * 物理删除指定时间之前的日志 + * + * @param createTime 最大时间 + * @param limit 删除条数,防止一次删除太多 + * @return 删除条数 + */ + @Delete("DELETE FROM infra_api_error_log WHERE create_time < #{createTime} LIMIT #{limit}") + Integer deleteByCreateTimeLt(@Param("createTime") LocalDateTime createTime, @Param("limit")Integer limit); + +} diff --git a/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/enums/codegen/CodegenColumnHtmlTypeEnum.java b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/enums/codegen/CodegenColumnHtmlTypeEnum.java new file mode 100644 index 0000000..7b8e9c6 --- /dev/null +++ b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/enums/codegen/CodegenColumnHtmlTypeEnum.java @@ -0,0 +1,29 @@ +package com.tashow.cloud.infra.enums.codegen; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * 代码生成器的字段 HTML 展示枚举 + */ +@AllArgsConstructor +@Getter +public enum CodegenColumnHtmlTypeEnum { + + INPUT("input"), // 文本框 + TEXTAREA("textarea"), // 文本域 + SELECT("select"), // 下拉框 + RADIO("radio"), // 单选框 + CHECKBOX("checkbox"), // 复选框 + DATETIME("datetime"), // 日期控件 + IMAGE_UPLOAD("imageUpload"), // 上传图片 + FILE_UPLOAD("fileUpload"), // 上传文件 + EDITOR("editor"), // 富文本控件 + ; + + /** + * 条件 + */ + private final String type; + +} diff --git a/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/enums/codegen/CodegenColumnListConditionEnum.java b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/enums/codegen/CodegenColumnListConditionEnum.java new file mode 100644 index 0000000..67b6e41 --- /dev/null +++ b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/enums/codegen/CodegenColumnListConditionEnum.java @@ -0,0 +1,27 @@ +package com.tashow.cloud.infra.enums.codegen; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * 代码生成器的字段过滤条件枚举 + */ +@AllArgsConstructor +@Getter +public enum CodegenColumnListConditionEnum { + + EQ("="), + NE("!="), + GT(">"), + GTE(">="), + LT("<"), + LTE("<="), + LIKE("LIKE"), + BETWEEN("BETWEEN"); + + /** + * 条件 + */ + private final String condition; + +} diff --git a/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/enums/codegen/CodegenFrontTypeEnum.java b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/enums/codegen/CodegenFrontTypeEnum.java new file mode 100644 index 0000000..1425142 --- /dev/null +++ b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/enums/codegen/CodegenFrontTypeEnum.java @@ -0,0 +1,25 @@ +package com.tashow.cloud.infra.enums.codegen; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * 代码生成的前端类型枚举 + * + * @author 芋道源码 + */ +@AllArgsConstructor +@Getter +public enum CodegenFrontTypeEnum { + + VUE2(10), // Vue2 Element UI 标准模版 + VUE3(20), // Vue3 Element Plus 标准模版 + VUE3_VBEN(30), // Vue3 VBEN 模版 + ; + + /** + * 类型 + */ + private final Integer type; + +} diff --git a/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/enums/codegen/CodegenSceneEnum.java b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/enums/codegen/CodegenSceneEnum.java new file mode 100644 index 0000000..f60cfb7 --- /dev/null +++ b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/enums/codegen/CodegenSceneEnum.java @@ -0,0 +1,41 @@ +package com.tashow.cloud.infra.enums.codegen; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +import static cn.hutool.core.util.ArrayUtil.*; + +/** + * 代码生成的场景枚举 + * + * @author 芋道源码 + */ +@AllArgsConstructor +@Getter +public enum CodegenSceneEnum { + + ADMIN(1, "管理后台", "admin", ""), + APP(2, "用户 APP", "app", "App"); + + /** + * 场景 + */ + private final Integer scene; + /** + * 场景名 + */ + private final String name; + /** + * 基础包名 + */ + private final String basePackage; + /** + * Controller 和 VO 类的前缀 + */ + private final String prefixClass; + + public static CodegenSceneEnum valueOf(Integer scene) { + return firstMatch(sceneEnum -> sceneEnum.getScene().equals(scene), values()); + } + +} diff --git a/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/enums/codegen/CodegenTemplateTypeEnum.java b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/enums/codegen/CodegenTemplateTypeEnum.java new file mode 100644 index 0000000..acecac4 --- /dev/null +++ b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/enums/codegen/CodegenTemplateTypeEnum.java @@ -0,0 +1,53 @@ +package com.tashow.cloud.infra.enums.codegen; + +import com.tashow.cloud.common.util.object.ObjectUtils; +import lombok.AllArgsConstructor; +import lombok.Getter; + +import java.util.Objects; + +/** + * 代码生成模板类型 + * + * @author 芋道源码 + */ +@AllArgsConstructor +@Getter +public enum CodegenTemplateTypeEnum { + + ONE(1), // 单表(增删改查) + TREE(2), // 树表(增删改查) + + MASTER_NORMAL(10), // 主子表 - 主表 - 普通模式 + MASTER_ERP(11), // 主子表 - 主表 - ERP 模式 + MASTER_INNER(12), // 主子表 - 主表 - 内嵌模式 + SUB(15), // 主子表 - 子表 + ; + + /** + * 类型 + */ + private final Integer type; + + /** + * 是否为主表 + * + * @param type 类型 + * @return 是否主表 + */ + public static boolean isMaster(Integer type) { + return ObjectUtils.equalsAny(type, + MASTER_NORMAL.type, MASTER_ERP.type, MASTER_INNER.type); + } + + /** + * 是否为树表 + * + * @param type 类型 + * @return 是否树表 + */ + public static boolean isTree(Integer type) { + return Objects.equals(type, TREE.type); + } + +} diff --git a/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/enums/config/ConfigTypeEnum.java b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/enums/config/ConfigTypeEnum.java new file mode 100644 index 0000000..b20b430 --- /dev/null +++ b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/enums/config/ConfigTypeEnum.java @@ -0,0 +1,21 @@ +package com.tashow.cloud.infra.enums.config; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +public enum ConfigTypeEnum { + + /** + * 系统配置 + */ + SYSTEM(1), + /** + * 自定义配置 + */ + CUSTOM(2); + + private final Integer type; + +} diff --git a/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/enums/logger/ApiErrorLogProcessStatusEnum.java b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/enums/logger/ApiErrorLogProcessStatusEnum.java new file mode 100644 index 0000000..7487cba --- /dev/null +++ b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/enums/logger/ApiErrorLogProcessStatusEnum.java @@ -0,0 +1,28 @@ +package com.tashow.cloud.infra.enums.logger; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * API 异常数据的处理状态 + * + * @author 芋道源码 + */ +@AllArgsConstructor +@Getter +public enum ApiErrorLogProcessStatusEnum { + + INIT(0, "未处理"), + DONE(1, "已处理"), + IGNORE(2, "已忽略"); + + /** + * 状态 + */ + private final Integer status; + /** + * 资源类型名 + */ + private final String name; + +} diff --git a/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/enums/package-info.java b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/enums/package-info.java new file mode 100644 index 0000000..41c4aea --- /dev/null +++ b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/enums/package-info.java @@ -0,0 +1,4 @@ +/** + * 占位 + */ +package com.tashow.cloud.infra.enums; diff --git a/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/framework/CanalClient.java b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/framework/CanalClient.java new file mode 100644 index 0000000..95a6876 --- /dev/null +++ b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/framework/CanalClient.java @@ -0,0 +1,204 @@ +package com.tashow.cloud.infra.framework; + + +import com.alibaba.otter.canal.client.CanalConnector; +import com.alibaba.otter.canal.client.CanalConnectors; +import com.alibaba.otter.canal.protocol.CanalEntry.*; +import com.alibaba.otter.canal.protocol.Message; +import com.google.protobuf.InvalidProtocolBufferException; +import org.springframework.stereotype.Component; + +import java.net.InetSocketAddress; +import java.sql.Connection; +import java.sql.SQLException; +import java.util.List; +import java.util.Queue; +import java.util.concurrent.ConcurrentLinkedQueue; + +@Component +public class CanalClient { + + //sql队列 + private Queue SQL_QUEUE = new ConcurrentLinkedQueue<>(); + + + /** + * canal入库方法 + */ + public void run() { + CanalConnector connector = CanalConnectors.newSingleConnector(new InetSocketAddress("43.139.42.137", + 11111), "example", "", ""); + int batchSize = 1000; + try { + connector.connect(); + // connector.subscribe(".*\\..*"); + connector.subscribe("tashow-platform"); + + connector.rollback(); + try { + while (true) { + //尝试从master那边拉去数据batchSize条记录,有多少取多少 + Message message = connector.getWithoutAck(batchSize); + long batchId = message.getId(); + int size = message.getEntries().size(); + if (batchId == -1 || size == 0) { + Thread.sleep(1000); + } else { + dataHandle(message.getEntries()); + } + connector.ack(batchId); + + //当队列里面堆积的sql大于一定数值的时候就模拟执行 + if (SQL_QUEUE.size() >= 1) { + executeQueueSql(); + } + } + } catch (InterruptedException e) { + e.printStackTrace(); + } catch (InvalidProtocolBufferException e) { + e.printStackTrace(); + } + } finally { + connector.disconnect(); + } + } + + /** + * 模拟执行队列里面的sql语句 + */ + public void executeQueueSql() { + int size = SQL_QUEUE.size(); + for (int i = 0; i < size; i++) { + String sql = SQL_QUEUE.poll(); + System.out.println("[sql]----> " + sql); + + this.execute(sql); + } + } + + /** + * 数据处理 + * + * @param entrys + */ + private void dataHandle(List entrys) throws InvalidProtocolBufferException { + for (Entry entry : entrys) { + if(entry.getHeader().getSchemaName().equals("hc")){ + return; + } + if (EntryType.ROWDATA == entry.getEntryType()) { + RowChange rowChange = RowChange.parseFrom(entry.getStoreValue()); + EventType eventType = rowChange.getEventType(); + if (eventType == EventType.DELETE) { + saveDeleteSql(entry); + } else if (eventType == EventType.UPDATE) { + saveUpdateSql(entry); + } else if (eventType == EventType.INSERT) { + saveInsertSql(entry); + } + } + } + } + + /** + * 保存更新语句 + * + * @param entry + */ + private void saveUpdateSql(Entry entry) { + try { + RowChange rowChange = RowChange.parseFrom(entry.getStoreValue()); + List rowDatasList = rowChange.getRowDatasList(); + for (RowData rowData : rowDatasList) { + List newColumnList = rowData.getAfterColumnsList(); + StringBuffer sql = new StringBuffer("update " + entry.getHeader().getTableName() + " set "); + for (int i = 0; i < newColumnList.size(); i++) { + sql.append(" " + newColumnList.get(i).getName() + + " = '" + newColumnList.get(i).getValue() + "'"); + if (i != newColumnList.size() - 1) { + sql.append(","); + } + } + sql.append(" where "); + List oldColumnList = rowData.getBeforeColumnsList(); + for (Column column : oldColumnList) { + if (column.getIsKey()) { + //暂时只支持单一主键 + sql.append(column.getName() + "=" + column.getValue()); + break; + } + } + SQL_QUEUE.add(sql.toString()); + } + } catch (InvalidProtocolBufferException e) { + e.printStackTrace(); + } + } + + /** + * 保存删除语句 + * + * @param entry + */ + private void saveDeleteSql(Entry entry) { + try { + RowChange rowChange = RowChange.parseFrom(entry.getStoreValue()); + List rowDatasList = rowChange.getRowDatasList(); + for (RowData rowData : rowDatasList) { + List columnList = rowData.getBeforeColumnsList(); + StringBuffer sql = new StringBuffer("delete from " + entry.getHeader().getTableName() + " where "); + for (Column column : columnList) { + if (column.getIsKey()) { + //暂时只支持单一主键 + sql.append(column.getName() + "=" + column.getValue()); + break; + } + } + SQL_QUEUE.add(sql.toString()); + } + } catch (InvalidProtocolBufferException e) { + e.printStackTrace(); + } + } + + /** + * 保存插入语句 + * + * @param entry + */ + private void saveInsertSql(Entry entry) { + try { + RowChange rowChange = RowChange.parseFrom(entry.getStoreValue()); + List rowDatasList = rowChange.getRowDatasList(); + for (RowData rowData : rowDatasList) { + List columnList = rowData.getAfterColumnsList(); + StringBuffer sql = new StringBuffer("insert into " + entry.getHeader().getTableName() + " ("); + for (int i = 0; i < columnList.size(); i++) { + sql.append(columnList.get(i).getName()); + if (i != columnList.size() - 1) { + sql.append(","); + } + } + sql.append(") VALUES ("); + for (int i = 0; i < columnList.size(); i++) { + sql.append("'" + columnList.get(i).getValue() + "'"); + if (i != columnList.size() - 1) { + sql.append(","); + } + } + sql.append(")"); + SQL_QUEUE.add(sql.toString()); + } + } catch (InvalidProtocolBufferException e) { + e.printStackTrace(); + } + } + + /** + * 入库 + * @param sql + */ + public void execute(String sql) { + System.out.println("sql======="+sql); + } +} diff --git a/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/framework/codegen/config/CodegenConfiguration.java b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/framework/codegen/config/CodegenConfiguration.java new file mode 100644 index 0000000..e94e109 --- /dev/null +++ b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/framework/codegen/config/CodegenConfiguration.java @@ -0,0 +1,9 @@ +package com.tashow.cloud.infra.framework.codegen.config; + +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Configuration; + +@Configuration(proxyBeanMethods = false) +@EnableConfigurationProperties(CodegenProperties.class) +public class CodegenConfiguration { +} diff --git a/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/framework/codegen/config/CodegenProperties.java b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/framework/codegen/config/CodegenProperties.java new file mode 100644 index 0000000..361b545 --- /dev/null +++ b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/framework/codegen/config/CodegenProperties.java @@ -0,0 +1,45 @@ +package com.tashow.cloud.infra.framework.codegen.config; + +import com.tashow.cloud.infra.enums.codegen.CodegenFrontTypeEnum; +import com.tashow.cloud.infra.enums.codegen.CodegenFrontTypeEnum; +import com.tashow.cloud.infra.enums.codegen.CodegenFrontTypeEnum; +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.validation.annotation.Validated; + +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import java.util.Collection; + +@ConfigurationProperties(prefix = "tashow.codegen") +@Validated +@Data +public class CodegenProperties { + + /** + * 生成的 Java 代码的基础包 + */ + @NotNull(message = "Java 代码的基础包不能为空") + private String basePackage; + + /** + * 数据库名数组 + */ + @NotEmpty(message = "数据库不能为空") + private Collection dbSchemas; + + /** + * 代码生成的前端类型(默认) + * + * 枚举 {@link CodegenFrontTypeEnum#getType()} + */ + @NotNull(message = "代码生成的前端类型不能为空") + private Integer frontType; + + /** + * 是否生成单元测试 + */ + @NotNull(message = "是否生成单元测试不能为空") + private Boolean unitTestEnable; + +} diff --git a/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/framework/codegen/package-info.java b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/framework/codegen/package-info.java new file mode 100644 index 0000000..c309215 --- /dev/null +++ b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/framework/codegen/package-info.java @@ -0,0 +1,4 @@ +/** + * 代码生成器 + */ +package com.tashow.cloud.infra.framework.codegen; diff --git a/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/framework/file/config/YudaoFileAutoConfiguration.java b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/framework/file/config/YudaoFileAutoConfiguration.java new file mode 100644 index 0000000..68ba8e7 --- /dev/null +++ b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/framework/file/config/YudaoFileAutoConfiguration.java @@ -0,0 +1,25 @@ +package com.tashow.cloud.infra.framework.file.config; + +import com.tashow.cloud.infra.framework.file.core.client.FileClientFactory; +import com.tashow.cloud.infra.framework.file.core.client.FileClientFactoryImpl; +import com.tashow.cloud.infra.framework.file.core.client.FileClientFactory; +import com.tashow.cloud.infra.framework.file.core.client.FileClientFactoryImpl; +import com.tashow.cloud.infra.framework.file.core.client.FileClientFactory; +import com.tashow.cloud.infra.framework.file.core.client.FileClientFactoryImpl; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * 文件配置类 + * + * @author 芋道源码 + */ +@Configuration(proxyBeanMethods = false) +public class YudaoFileAutoConfiguration { + + @Bean + public FileClientFactory fileClientFactory() { + return new FileClientFactoryImpl(); + } + +} diff --git a/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/framework/file/core/client/AbstractFileClient.java b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/framework/file/core/client/AbstractFileClient.java new file mode 100644 index 0000000..a35851f --- /dev/null +++ b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/framework/file/core/client/AbstractFileClient.java @@ -0,0 +1,69 @@ +package com.tashow.cloud.infra.framework.file.core.client; + +import cn.hutool.core.util.StrUtil; +import lombok.extern.slf4j.Slf4j; + +/** + * 文件客户端的抽象类,提供模板方法,减少子类的冗余代码 + * + * @author 芋道源码 + */ +@Slf4j +public abstract class AbstractFileClient implements FileClient { + + /** + * 配置编号 + */ + private final Long id; + /** + * 文件配置 + */ + protected Config config; + + public AbstractFileClient(Long id, Config config) { + this.id = id; + this.config = config; + } + + /** + * 初始化 + */ + public final void init() { + doInit(); + log.debug("[init][配置({}) 初始化完成]", config); + } + + /** + * 自定义初始化 + */ + protected abstract void doInit(); + + public final void refresh(Config config) { + // 判断是否更新 + if (config.equals(this.config)) { + return; + } + log.info("[refresh][配置({})发生变化,重新初始化]", config); + this.config = config; + // 初始化 + this.init(); + } + + @Override + public Long getId() { + return id; + } + + /** + * 格式化文件的 URL 访问地址 + * 使用场景:local、ftp、db,通过 FileController 的 getFile 来获取文件内容 + * + * @param domain 自定义域名 + * @param path 文件路径 + * @return URL 访问地址 + */ + protected String formatFileUrl(String domain, String path) { + return StrUtil.format("{}/admin-api/infra/file/{}/get/{}", domain, getId(), path); + } + +} diff --git a/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/framework/file/core/client/FileClient.java b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/framework/file/core/client/FileClient.java new file mode 100644 index 0000000..c386fc5 --- /dev/null +++ b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/framework/file/core/client/FileClient.java @@ -0,0 +1,57 @@ +package com.tashow.cloud.infra.framework.file.core.client; + +import com.tashow.cloud.infra.framework.file.core.client.s3.FilePresignedUrlRespDTO; +import com.tashow.cloud.infra.framework.file.core.client.s3.FilePresignedUrlRespDTO; +import com.tashow.cloud.infra.framework.file.core.client.s3.FilePresignedUrlRespDTO; + +/** + * 文件客户端 + * + * @author 芋道源码 + */ +public interface FileClient { + + /** + * 获得客户端编号 + * + * @return 客户端编号 + */ + Long getId(); + + /** + * 上传文件 + * + * @param content 文件流 + * @param path 相对路径 + * @return 完整路径,即 HTTP 访问地址 + * @throws Exception 上传文件时,抛出 Exception 异常 + */ + String upload(byte[] content, String path, String type) throws Exception; + + /** + * 删除文件 + * + * @param path 相对路径 + * @throws Exception 删除文件时,抛出 Exception 异常 + */ + void delete(String path) throws Exception; + + /** + * 获得文件的内容 + * + * @param path 相对路径 + * @return 文件的内容 + */ + byte[] getContent(String path) throws Exception; + + /** + * 获得文件预签名地址 + * + * @param path 相对路径 + * @return 文件预签名地址 + */ + default FilePresignedUrlRespDTO getPresignedObjectUrl(String path) throws Exception { + throw new UnsupportedOperationException("不支持的操作"); + } + +} diff --git a/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/framework/file/core/client/FileClientConfig.java b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/framework/file/core/client/FileClientConfig.java new file mode 100644 index 0000000..c2f7c6d --- /dev/null +++ b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/framework/file/core/client/FileClientConfig.java @@ -0,0 +1,16 @@ +package com.tashow.cloud.infra.framework.file.core.client; + +import com.fasterxml.jackson.annotation.JsonTypeInfo; + +/** + * 文件客户端的配置 + * 不同实现的客户端,需要不同的配置,通过子类来定义 + * + * @author 芋道源码 + */ +@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS) +// @JsonTypeInfo 注解的作用,Jackson 多态 +// 1. 序列化到时数据库时,增加 @class 属性。 +// 2. 反序列化到内存对象时,通过 @class 属性,可以创建出正确的类型 +public interface FileClientConfig { +} diff --git a/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/framework/file/core/client/FileClientFactory.java b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/framework/file/core/client/FileClientFactory.java new file mode 100644 index 0000000..8361e73 --- /dev/null +++ b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/framework/file/core/client/FileClientFactory.java @@ -0,0 +1,26 @@ +package com.tashow.cloud.infra.framework.file.core.client; + +import com.tashow.cloud.infra.framework.file.core.enums.FileStorageEnum; +import com.tashow.cloud.infra.framework.file.core.enums.FileStorageEnum; +import com.tashow.cloud.infra.framework.file.core.enums.FileStorageEnum; + +public interface FileClientFactory { + + /** + * 获得文件客户端 + * + * @param configId 配置编号 + * @return 文件客户端 + */ + FileClient getFileClient(Long configId); + + /** + * 创建文件客户端 + * + * @param configId 配置编号 + * @param storage 存储器的枚举 {@link FileStorageEnum} + * @param config 文件配置 + */ + void createOrUpdateFileClient(Long configId, Integer storage, Config config); + +} diff --git a/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/framework/file/core/client/FileClientFactoryImpl.java b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/framework/file/core/client/FileClientFactoryImpl.java new file mode 100644 index 0000000..6eee5e3 --- /dev/null +++ b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/framework/file/core/client/FileClientFactoryImpl.java @@ -0,0 +1,58 @@ +package com.tashow.cloud.infra.framework.file.core.client; + +import cn.hutool.core.lang.Assert; +import cn.hutool.core.util.ReflectUtil; +import com.tashow.cloud.infra.framework.file.core.enums.FileStorageEnum; +import com.tashow.cloud.infra.framework.file.core.enums.FileStorageEnum; +import com.tashow.cloud.infra.framework.file.core.enums.FileStorageEnum; +import lombok.extern.slf4j.Slf4j; + +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +/** + * 文件客户端的工厂实现类 + * + * @author 芋道源码 + */ +@Slf4j +public class FileClientFactoryImpl implements FileClientFactory { + + /** + * 文件客户端 Map + * key:配置编号 + */ + private final ConcurrentMap> clients = new ConcurrentHashMap<>(); + + @Override + public FileClient getFileClient(Long configId) { + AbstractFileClient client = clients.get(configId); + if (client == null) { + log.error("[getFileClient][配置编号({}) 找不到客户端]", configId); + } + return client; + } + + @Override + @SuppressWarnings("unchecked") + public void createOrUpdateFileClient(Long configId, Integer storage, Config config) { + AbstractFileClient client = (AbstractFileClient) clients.get(configId); + if (client == null) { + client = this.createFileClient(configId, storage, config); + client.init(); + clients.put(client.getId(), client); + } else { + client.refresh(config); + } + } + + @SuppressWarnings("unchecked") + private AbstractFileClient createFileClient( + Long configId, Integer storage, Config config) { + FileStorageEnum storageEnum = FileStorageEnum.getByStorage(storage); + Assert.notNull(storageEnum, String.format("文件配置(%s) 为空", storageEnum)); + // 创建客户端 + return (AbstractFileClient) ReflectUtil.newInstance(storageEnum.getClientClass(), configId, config); + } + +} diff --git a/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/framework/file/core/client/db/DBFileClient.java b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/framework/file/core/client/db/DBFileClient.java new file mode 100644 index 0000000..c4bc920 --- /dev/null +++ b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/framework/file/core/client/db/DBFileClient.java @@ -0,0 +1,59 @@ +package com.tashow.cloud.infra.framework.file.core.client.db; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.extra.spring.SpringUtil; +import com.tashow.cloud.infra.dal.dataobject.file.FileContentDO; +import com.tashow.cloud.infra.dal.mysql.file.FileContentMapper; +import com.tashow.cloud.infra.dal.dataobject.file.FileContentDO; +import com.tashow.cloud.infra.dal.mysql.file.FileContentMapper; +import com.tashow.cloud.infra.framework.file.core.client.AbstractFileClient; +import com.tashow.cloud.infra.dal.dataobject.file.FileContentDO; +import com.tashow.cloud.infra.dal.mysql.file.FileContentMapper; + +import java.util.Comparator; +import java.util.List; + +/** + * 基于 DB 存储的文件客户端的配置类 + * + * @author 芋道源码 + */ +public class DBFileClient extends AbstractFileClient { + + private FileContentMapper fileContentMapper; + + public DBFileClient(Long id, DBFileClientConfig config) { + super(id, config); + } + + @Override + protected void doInit() { + fileContentMapper = SpringUtil.getBean(FileContentMapper.class); + } + + @Override + public String upload(byte[] content, String path, String type) { + FileContentDO contentDO = new FileContentDO().setConfigId(getId()) + .setPath(path).setContent(content); + fileContentMapper.insert(contentDO); + // 拼接返回路径 + return super.formatFileUrl(config.getDomain(), path); + } + + @Override + public void delete(String path) { + fileContentMapper.deleteByConfigIdAndPath(getId(), path); + } + + @Override + public byte[] getContent(String path) { + List list = fileContentMapper.selectListByConfigIdAndPath(getId(), path); + if (CollUtil.isEmpty(list)) { + return null; + } + // 排序后,拿 id 最大的,即最后上传的 + list.sort(Comparator.comparing(FileContentDO::getId)); + return CollUtil.getLast(list).getContent(); + } + +} diff --git a/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/framework/file/core/client/db/DBFileClientConfig.java b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/framework/file/core/client/db/DBFileClientConfig.java new file mode 100644 index 0000000..512b02f --- /dev/null +++ b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/framework/file/core/client/db/DBFileClientConfig.java @@ -0,0 +1,23 @@ +package com.tashow.cloud.infra.framework.file.core.client.db; + +import com.tashow.cloud.infra.framework.file.core.client.FileClientConfig; +import jakarta.validation.constraints.NotEmpty; +import lombok.Data; +import org.hibernate.validator.constraints.URL; + +/** + * 基于 DB 存储的文件客户端的配置类 + * + * @author 芋道源码 + */ +@Data +public class DBFileClientConfig implements FileClientConfig { + + /** + * 自定义域名 + */ + @NotEmpty(message = "domain 不能为空") + @URL(message = "domain 必须是 URL 格式") + private String domain; + +} diff --git a/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/framework/file/core/client/ftp/FtpFileClient.java b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/framework/file/core/client/ftp/FtpFileClient.java new file mode 100644 index 0000000..07cabf5 --- /dev/null +++ b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/framework/file/core/client/ftp/FtpFileClient.java @@ -0,0 +1,77 @@ +package com.tashow.cloud.infra.framework.file.core.client.ftp; + +import cn.hutool.core.io.FileUtil; +import cn.hutool.core.util.CharsetUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.extra.ftp.Ftp; +import cn.hutool.extra.ftp.FtpException; +import cn.hutool.extra.ftp.FtpMode; +import com.tashow.cloud.infra.framework.file.core.client.AbstractFileClient; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; + +/** + * Ftp 文件客户端 + * + * @author 芋道源码 + */ +public class FtpFileClient extends AbstractFileClient { + + private Ftp ftp; + + public FtpFileClient(Long id, FtpFileClientConfig config) { + super(id, config); + } + + @Override + protected void doInit() { + // 把配置的 \ 替换成 /, 如果路径配置 \a\test, 替换成 /a/test, 替换方法已经处理 null 情况 + config.setBasePath(StrUtil.replace(config.getBasePath(), StrUtil.BACKSLASH, StrUtil.SLASH)); + // ftp的路径是 / 结尾 + if (!config.getBasePath().endsWith(StrUtil.SLASH)) { + config.setBasePath(config.getBasePath() + StrUtil.SLASH); + } + // 初始化 Ftp 对象 + this.ftp = new Ftp(config.getHost(), config.getPort(), config.getUsername(), config.getPassword(), + CharsetUtil.CHARSET_UTF_8, null, null, FtpMode.valueOf(config.getMode())); + } + + @Override + public String upload(byte[] content, String path, String type) { + // 执行写入 + String filePath = getFilePath(path); + String fileName = FileUtil.getName(filePath); + String dir = StrUtil.removeSuffix(filePath, fileName); + ftp.reconnectIfTimeout(); + boolean success = ftp.upload(dir, fileName, new ByteArrayInputStream(content)); + if (!success) { + throw new FtpException(StrUtil.format("上传文件到目标目录 ({}) 失败", filePath)); + } + // 拼接返回路径 + return super.formatFileUrl(config.getDomain(), path); + } + + @Override + public void delete(String path) { + String filePath = getFilePath(path); + ftp.reconnectIfTimeout(); + ftp.delFile(filePath); + } + + @Override + public byte[] getContent(String path) { + String filePath = getFilePath(path); + String fileName = FileUtil.getName(filePath); + String dir = StrUtil.removeSuffix(filePath, fileName); + ByteArrayOutputStream out = new ByteArrayOutputStream(); + ftp.reconnectIfTimeout(); + ftp.download(dir, fileName, out); + return out.toByteArray(); + } + + private String getFilePath(String path) { + return config.getBasePath() + path; + } + +} diff --git a/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/framework/file/core/client/ftp/FtpFileClientConfig.java b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/framework/file/core/client/ftp/FtpFileClientConfig.java new file mode 100644 index 0000000..c8c5ad6 --- /dev/null +++ b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/framework/file/core/client/ftp/FtpFileClientConfig.java @@ -0,0 +1,58 @@ +package com.tashow.cloud.infra.framework.file.core.client.ftp; + +import com.tashow.cloud.infra.framework.file.core.client.FileClientConfig; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import lombok.Data; +import org.hibernate.validator.constraints.URL; + +/** + * Ftp 文件客户端的配置类 + * + * @author 芋道源码 + */ +@Data +public class FtpFileClientConfig implements FileClientConfig { + + /** + * 基础路径 + */ + @NotEmpty(message = "基础路径不能为空") + private String basePath; + + /** + * 自定义域名 + */ + @NotEmpty(message = "domain 不能为空") + @URL(message = "domain 必须是 URL 格式") + private String domain; + + /** + * 主机地址 + */ + @NotEmpty(message = "host 不能为空") + private String host; + /** + * 主机端口 + */ + @NotNull(message = "port 不能为空") + private Integer port; + /** + * 用户名 + */ + @NotEmpty(message = "用户名不能为空") + private String username; + /** + * 密码 + */ + @NotEmpty(message = "密码不能为空") + private String password; + /** + * 连接模式 + * + * 使用 {@link cn.hutool.extra.ftp.FtpMode} 对应的字符串 + */ + @NotEmpty(message = "连接模式不能为空") + private String mode; + +} diff --git a/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/framework/file/core/client/local/LocalFileClient.java b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/framework/file/core/client/local/LocalFileClient.java new file mode 100644 index 0000000..aa99e0a --- /dev/null +++ b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/framework/file/core/client/local/LocalFileClient.java @@ -0,0 +1,52 @@ +package com.tashow.cloud.infra.framework.file.core.client.local; + +import cn.hutool.core.io.FileUtil; +import com.tashow.cloud.infra.framework.file.core.client.AbstractFileClient; + +import java.io.File; + +/** + * 本地文件客户端 + * + * @author 芋道源码 + */ +public class LocalFileClient extends AbstractFileClient { + + public LocalFileClient(Long id, LocalFileClientConfig config) { + super(id, config); + } + + @Override + protected void doInit() { + // 补全风格。例如说 Linux 是 /,Windows 是 \ + if (!config.getBasePath().endsWith(File.separator)) { + config.setBasePath(config.getBasePath() + File.separator); + } + } + + @Override + public String upload(byte[] content, String path, String type) { + // 执行写入 + String filePath = getFilePath(path); + FileUtil.writeBytes(content, filePath); + // 拼接返回路径 + return super.formatFileUrl(config.getDomain(), path); + } + + @Override + public void delete(String path) { + String filePath = getFilePath(path); + FileUtil.del(filePath); + } + + @Override + public byte[] getContent(String path) { + String filePath = getFilePath(path); + return FileUtil.readBytes(filePath); + } + + private String getFilePath(String path) { + return config.getBasePath() + path; + } + +} diff --git a/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/framework/file/core/client/local/LocalFileClientConfig.java b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/framework/file/core/client/local/LocalFileClientConfig.java new file mode 100644 index 0000000..375dad6 --- /dev/null +++ b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/framework/file/core/client/local/LocalFileClientConfig.java @@ -0,0 +1,29 @@ +package com.tashow.cloud.infra.framework.file.core.client.local; + +import com.tashow.cloud.infra.framework.file.core.client.FileClientConfig; +import jakarta.validation.constraints.NotEmpty; +import lombok.Data; +import org.hibernate.validator.constraints.URL; + +/** + * 本地文件客户端的配置类 + * + * @author 芋道源码 + */ +@Data +public class LocalFileClientConfig implements FileClientConfig { + + /** + * 基础路径 + */ + @NotEmpty(message = "基础路径不能为空") + private String basePath; + + /** + * 自定义域名 + */ + @NotEmpty(message = "domain 不能为空") + @URL(message = "domain 必须是 URL 格式") + private String domain; + +} diff --git a/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/framework/file/core/client/s3/FilePresignedUrlRespDTO.java b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/framework/file/core/client/s3/FilePresignedUrlRespDTO.java new file mode 100644 index 0000000..3af8231 --- /dev/null +++ b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/framework/file/core/client/s3/FilePresignedUrlRespDTO.java @@ -0,0 +1,29 @@ +package com.tashow.cloud.infra.framework.file.core.client.s3; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 文件预签名地址 Response DTO + * + * @author owen + */ +@Data +@AllArgsConstructor +@NoArgsConstructor +public class FilePresignedUrlRespDTO { + + /** + * 文件上传 URL(用于上传) + * + * 例如说: + */ + private String uploadUrl; + + /** + * 文件 URL(用于读取、下载等) + */ + private String url; + +} diff --git a/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/framework/file/core/client/s3/S3FileClient.java b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/framework/file/core/client/s3/S3FileClient.java new file mode 100644 index 0000000..d3990fb --- /dev/null +++ b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/framework/file/core/client/s3/S3FileClient.java @@ -0,0 +1,118 @@ +package com.tashow.cloud.infra.framework.file.core.client.s3; + +import cn.hutool.core.io.IoUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.http.HttpUtil; +import com.tashow.cloud.infra.framework.file.core.client.AbstractFileClient; +import com.amazonaws.HttpMethod; +import com.amazonaws.auth.AWSStaticCredentialsProvider; +import com.amazonaws.auth.BasicAWSCredentials; +import com.amazonaws.client.builder.AwsClientBuilder; +import com.amazonaws.services.s3.AmazonS3Client; +import com.amazonaws.services.s3.AmazonS3ClientBuilder; +import com.amazonaws.services.s3.model.ObjectMetadata; +import com.amazonaws.services.s3.model.S3Object; + +import java.io.ByteArrayInputStream; +import java.util.Date; +import java.util.concurrent.TimeUnit; + +/** + * 基于 S3 协议的文件客户端,实现 MinIO、阿里云、腾讯云、七牛云、华为云等云服务 + *

+ * S3 协议的客户端,采用亚马逊提供的 software.amazon.awssdk.s3 库 + * + * @author 芋道源码 + */ +public class S3FileClient extends AbstractFileClient { + + private AmazonS3Client client; + + public S3FileClient(Long id, S3FileClientConfig config) { + super(id, config); + } + + @Override + protected void doInit() { + // 补全 domain + if (StrUtil.isEmpty(config.getDomain())) { + config.setDomain(buildDomain()); + } + // 初始化客户端 + client = (AmazonS3Client)AmazonS3ClientBuilder.standard() + .withCredentials(buildCredentials()) + .withEndpointConfiguration(buildEndpointConfiguration()) + .build(); + } + + /** + * 基于 config 秘钥,构建 S3 客户端的认证信息 + * + * @return S3 客户端的认证信息 + */ + private AWSStaticCredentialsProvider buildCredentials() { + return new AWSStaticCredentialsProvider( + new BasicAWSCredentials(config.getAccessKey(), config.getAccessSecret())); + } + + /** + * 构建 S3 客户端的 Endpoint 配置,包括 region、endpoint + * + * @return S3 客户端的 EndpointConfiguration 配置 + */ + private AwsClientBuilder.EndpointConfiguration buildEndpointConfiguration() { + return new AwsClientBuilder.EndpointConfiguration(config.getEndpoint(), + null); // 无需设置 region + } + + /** + * 基于 bucket + endpoint 构建访问的 Domain 地址 + * + * @return Domain 地址 + */ + private String buildDomain() { + // 如果已经是 http 或者 https,则不进行拼接.主要适配 MinIO + if (HttpUtil.isHttp(config.getEndpoint()) || HttpUtil.isHttps(config.getEndpoint())) { + return StrUtil.format("{}/{}", config.getEndpoint(), config.getBucket()); + } + // 阿里云、腾讯云、华为云都适合。七牛云比较特殊,必须有自定义域名 + return StrUtil.format("https://{}.{}", config.getBucket(), config.getEndpoint()); + } + + @Override + public String upload(byte[] content, String path, String type) throws Exception { + // 元数据,主要用于设置文件类型 + ObjectMetadata objectMetadata = new ObjectMetadata(); + objectMetadata.setContentType(type); + objectMetadata.setContentLength(content.length); // 如果不设置,会有 “ No content length specified for stream data” 警告日志 + // 执行上传 + client.putObject(config.getBucket(), + path, // 相对路径 + new ByteArrayInputStream(content), // 文件内容 + objectMetadata); + + // 拼接返回路径 + return config.getDomain() + "/" + path; + } + + @Override + public void delete(String path) throws Exception { + client.deleteObject(config.getBucket(), path); + } + + @Override + public byte[] getContent(String path) throws Exception { + S3Object tempS3Object = client.getObject(config.getBucket(), path); + return IoUtil.readBytes(tempS3Object.getObjectContent()); + } + + @Override + public FilePresignedUrlRespDTO getPresignedObjectUrl(String path) throws Exception { + // 设定过期时间为 10 分钟。取值范围:1 秒 ~ 7 天 + Date expiration = new Date(System.currentTimeMillis() + TimeUnit.MINUTES.toMillis(10)); + // 生成上传 URL + String uploadUrl = String.valueOf(client.generatePresignedUrl(config.getBucket(), path, expiration , HttpMethod.PUT)); + return new FilePresignedUrlRespDTO(uploadUrl, config.getDomain() + "/" + path); + } + +} diff --git a/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/framework/file/core/client/s3/S3FileClientConfig.java b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/framework/file/core/client/s3/S3FileClientConfig.java new file mode 100644 index 0000000..9a3cf07 --- /dev/null +++ b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/framework/file/core/client/s3/S3FileClientConfig.java @@ -0,0 +1,80 @@ +package com.tashow.cloud.infra.framework.file.core.client.s3; + +import cn.hutool.core.util.StrUtil; +import com.tashow.cloud.infra.framework.file.core.client.FileClientConfig; +import com.fasterxml.jackson.annotation.JsonIgnore; +import jakarta.validation.constraints.AssertTrue; +import jakarta.validation.constraints.NotNull; +import lombok.Data; +import org.hibernate.validator.constraints.URL; + +/** + * S3 文件客户端的配置类 + * + * @author 芋道源码 + */ +@Data +public class S3FileClientConfig implements FileClientConfig { + + public static final String ENDPOINT_QINIU = "qiniucs.com"; + public static final String ENDPOINT_ALIYUN = "aliyuncs.com"; + public static final String ENDPOINT_TENCENT = "myqcloud.com"; + public static final String ENDPOINT_VOLCES = "volces.com"; // 火山云(字节) + + /** + * 节点地址 + * 1. MinIO:https://www.iocoder.cn/Spring-Boot/MinIO 。例如说,http://127.0.0.1:9000 + * 2. 阿里云:https://help.aliyun.com/document_detail/31837.html + * 3. 腾讯云:https://cloud.tencent.com/document/product/436/6224 + * 4. 七牛云:https://developer.qiniu.com/kodo/4088/s3-access-domainname + * 5. 华为云:https://console.huaweicloud.com/apiexplorer/#/endpoint/OBS + * 6. 火山云:https://www.volcengine.com/docs/6349/107356 + */ + @NotNull(message = "endpoint 不能为空") + private String endpoint; + /** + * 自定义域名 + * 1. MinIO:通过 Nginx 配置 + * 2. 阿里云:https://help.aliyun.com/document_detail/31836.html + * 3. 腾讯云:https://cloud.tencent.com/document/product/436/11142 + * 4. 七牛云:https://developer.qiniu.com/kodo/8556/set-the-custom-source-domain-name + * 5. 华为云:https://support.huaweicloud.com/usermanual-obs/obs_03_0032.html + * 6. 火山云:https://www.volcengine.com/docs/6349/128983 + */ + @URL(message = "domain 必须是 URL 格式") + private String domain; + /** + * 存储 Bucket + */ + @NotNull(message = "bucket 不能为空") + private String bucket; + + /** + * 访问 Key + * 1. MinIO:https://www.iocoder.cn/Spring-Boot/MinIO + * 2. 阿里云:https://ram.console.aliyun.com/manage/ak + * 3. 腾讯云:https://console.cloud.tencent.com/cam/capi + * 4. 七牛云:https://portal.qiniu.com/user/key + * 5. 华为云:https://support.huaweicloud.com/qs-obs/obs_qs_0005.html + * 6. 火山云:https://console.volcengine.com/iam/keymanage/ + */ + @NotNull(message = "accessKey 不能为空") + private String accessKey; + /** + * 访问 Secret + */ + @NotNull(message = "accessSecret 不能为空") + private String accessSecret; + + @SuppressWarnings("RedundantIfStatement") + @AssertTrue(message = "domain 不能为空") + @JsonIgnore + public boolean isDomainValid() { + // 如果是七牛,必须带有 domain + if (StrUtil.contains(endpoint, ENDPOINT_QINIU) && StrUtil.isEmpty(domain)) { + return false; + } + return true; + } + +} diff --git a/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/framework/file/core/client/sftp/SftpFileClient.java b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/framework/file/core/client/sftp/SftpFileClient.java new file mode 100644 index 0000000..383a2df --- /dev/null +++ b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/framework/file/core/client/sftp/SftpFileClient.java @@ -0,0 +1,61 @@ +package com.tashow.cloud.infra.framework.file.core.client.sftp; + +import cn.hutool.core.io.FileUtil; +import cn.hutool.extra.ssh.Sftp; +import com.tashow.cloud.common.util.io.FileUtils; +import com.tashow.cloud.infra.framework.file.core.client.AbstractFileClient; + +import java.io.File; + +/** + * Sftp 文件客户端 + * + * @author 芋道源码 + */ +public class SftpFileClient extends AbstractFileClient { + + private Sftp sftp; + + public SftpFileClient(Long id, SftpFileClientConfig config) { + super(id, config); + } + + @Override + protected void doInit() { + // 补全风格。例如说 Linux 是 /,Windows 是 \ + if (!config.getBasePath().endsWith(File.separator)) { + config.setBasePath(config.getBasePath() + File.separator); + } + // 初始化 Ftp 对象 + this.sftp = new Sftp(config.getHost(), config.getPort(), config.getUsername(), config.getPassword()); + } + + @Override + public String upload(byte[] content, String path, String type) { + // 执行写入 + String filePath = getFilePath(path); + File file = FileUtils.createTempFile(content); + sftp.upload(filePath, file); + // 拼接返回路径 + return super.formatFileUrl(config.getDomain(), path); + } + + @Override + public void delete(String path) { + String filePath = getFilePath(path); + sftp.delFile(filePath); + } + + @Override + public byte[] getContent(String path) { + String filePath = getFilePath(path); + File destFile = FileUtils.createTempFile(); + sftp.download(filePath, destFile); + return FileUtil.readBytes(destFile); + } + + private String getFilePath(String path) { + return config.getBasePath() + path; + } + +} diff --git a/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/framework/file/core/client/sftp/SftpFileClientConfig.java b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/framework/file/core/client/sftp/SftpFileClientConfig.java new file mode 100644 index 0000000..300feff --- /dev/null +++ b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/framework/file/core/client/sftp/SftpFileClientConfig.java @@ -0,0 +1,51 @@ +package com.tashow.cloud.infra.framework.file.core.client.sftp; + +import com.tashow.cloud.infra.framework.file.core.client.FileClientConfig; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import lombok.Data; +import org.hibernate.validator.constraints.URL; + +/** + * Sftp 文件客户端的配置类 + * + * @author 芋道源码 + */ +@Data +public class SftpFileClientConfig implements FileClientConfig { + + /** + * 基础路径 + */ + @NotEmpty(message = "基础路径不能为空") + private String basePath; + + /** + * 自定义域名 + */ + @NotEmpty(message = "domain 不能为空") + @URL(message = "domain 必须是 URL 格式") + private String domain; + + /** + * 主机地址 + */ + @NotEmpty(message = "host 不能为空") + private String host; + /** + * 主机端口 + */ + @NotNull(message = "port 不能为空") + private Integer port; + /** + * 用户名 + */ + @NotEmpty(message = "用户名不能为空") + private String username; + /** + * 密码 + */ + @NotEmpty(message = "密码不能为空") + private String password; + +} diff --git a/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/framework/file/core/enums/FileStorageEnum.java b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/framework/file/core/enums/FileStorageEnum.java new file mode 100644 index 0000000..d51b847 --- /dev/null +++ b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/framework/file/core/enums/FileStorageEnum.java @@ -0,0 +1,73 @@ +package com.tashow.cloud.infra.framework.file.core.enums; + +import cn.hutool.core.util.ArrayUtil; +import com.tashow.cloud.infra.framework.file.core.client.db.DBFileClient; +import com.tashow.cloud.infra.framework.file.core.client.db.DBFileClientConfig; +import com.tashow.cloud.infra.framework.file.core.client.ftp.FtpFileClient; +import com.tashow.cloud.infra.framework.file.core.client.ftp.FtpFileClientConfig; +import com.tashow.cloud.infra.framework.file.core.client.local.LocalFileClient; +import com.tashow.cloud.infra.framework.file.core.client.s3.S3FileClient; +import com.tashow.cloud.infra.framework.file.core.client.s3.S3FileClientConfig; +import com.tashow.cloud.infra.framework.file.core.client.sftp.SftpFileClient; +import com.tashow.cloud.infra.framework.file.core.client.sftp.SftpFileClientConfig; +import com.tashow.cloud.infra.framework.file.core.client.FileClient; +import com.tashow.cloud.infra.framework.file.core.client.FileClientConfig; +import com.tashow.cloud.infra.framework.file.core.client.db.DBFileClient; +import com.tashow.cloud.infra.framework.file.core.client.db.DBFileClientConfig; +import com.tashow.cloud.infra.framework.file.core.client.ftp.FtpFileClient; +import com.tashow.cloud.infra.framework.file.core.client.ftp.FtpFileClientConfig; +import com.tashow.cloud.infra.framework.file.core.client.local.LocalFileClient; +import com.tashow.cloud.infra.framework.file.core.client.local.LocalFileClientConfig; +import com.tashow.cloud.infra.framework.file.core.client.s3.S3FileClient; +import com.tashow.cloud.infra.framework.file.core.client.s3.S3FileClientConfig; +import com.tashow.cloud.infra.framework.file.core.client.sftp.SftpFileClient; +import com.tashow.cloud.infra.framework.file.core.client.sftp.SftpFileClientConfig; +import com.tashow.cloud.infra.framework.file.core.client.db.DBFileClient; +import com.tashow.cloud.infra.framework.file.core.client.db.DBFileClientConfig; +import com.tashow.cloud.infra.framework.file.core.client.ftp.FtpFileClient; +import com.tashow.cloud.infra.framework.file.core.client.ftp.FtpFileClientConfig; +import com.tashow.cloud.infra.framework.file.core.client.local.LocalFileClient; +import com.tashow.cloud.infra.framework.file.core.client.s3.S3FileClient; +import com.tashow.cloud.infra.framework.file.core.client.s3.S3FileClientConfig; +import com.tashow.cloud.infra.framework.file.core.client.sftp.SftpFileClient; +import com.tashow.cloud.infra.framework.file.core.client.sftp.SftpFileClientConfig; +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * 文件存储器枚举 + * + * @author 芋道源码 + */ +@AllArgsConstructor +@Getter +public enum FileStorageEnum { + + DB(1, DBFileClientConfig.class, DBFileClient.class), + + LOCAL(10, LocalFileClientConfig.class, LocalFileClient.class), + FTP(11, FtpFileClientConfig.class, FtpFileClient.class), + SFTP(12, SftpFileClientConfig.class, SftpFileClient.class), + + S3(20, S3FileClientConfig.class, S3FileClient.class), + ; + + /** + * 存储器 + */ + private final Integer storage; + + /** + * 配置类 + */ + private final Class configClass; + /** + * 客户端类 + */ + private final Class clientClass; + + public static FileStorageEnum getByStorage(Integer storage) { + return ArrayUtil.firstMatch(o -> o.getStorage().equals(storage), values()); + } + +} diff --git a/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/framework/file/core/utils/FileTypeUtils.java b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/framework/file/core/utils/FileTypeUtils.java new file mode 100644 index 0000000..92d1483 --- /dev/null +++ b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/framework/file/core/utils/FileTypeUtils.java @@ -0,0 +1,76 @@ +package com.tashow.cloud.infra.framework.file.core.utils; + +import cn.hutool.core.io.IoUtil; +import cn.hutool.core.util.StrUtil; +import com.alibaba.ttl.TransmittableThreadLocal; +import jakarta.servlet.http.HttpServletResponse; +import lombok.SneakyThrows; +import org.apache.tika.Tika; + +import java.io.IOException; +import java.net.URLEncoder; + +/** + * 文件类型 Utils + * + * @author 芋道源码 + */ +public class FileTypeUtils { + + private static final ThreadLocal TIKA = TransmittableThreadLocal.withInitial(Tika::new); + + /** + * 获得文件的 mineType,对于doc,jar等文件会有误差 + * + * @param data 文件内容 + * @return mineType 无法识别时会返回“application/octet-stream” + */ + @SneakyThrows + public static String getMineType(byte[] data) { + return TIKA.get().detect(data); + } + + /** + * 已知文件名,获取文件类型,在某些情况下比通过字节数组准确,例如使用jar文件时,通过名字更为准确 + * + * @param name 文件名 + * @return mineType 无法识别时会返回“application/octet-stream” + */ + public static String getMineType(String name) { + return TIKA.get().detect(name); + } + + /** + * 在拥有文件和数据的情况下,最好使用此方法,最为准确 + * + * @param data 文件内容 + * @param name 文件名 + * @return mineType 无法识别时会返回“application/octet-stream” + */ + public static String getMineType(byte[] data, String name) { + return TIKA.get().detect(data, name); + } + + /** + * 返回附件 + * + * @param response 响应 + * @param filename 文件名 + * @param content 附件内容 + */ + public static void writeAttachment(HttpServletResponse response, String filename, byte[] content) throws IOException { + // 设置 header 和 contentType + response.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(filename, "UTF-8")); + String contentType = getMineType(content, filename); + response.setContentType(contentType); + // 针对 video 的特殊处理,解决视频地址在移动端播放的兼容性问题 + if (StrUtil.containsIgnoreCase(contentType, "video")) { + response.setHeader("Content-Length", String.valueOf(content.length - 1)); + response.setHeader("Content-Range", String.valueOf(content.length - 1)); + response.setHeader("Accept-Ranges", "bytes"); + } + // 输出附件 + IoUtil.write(response.getOutputStream(), false, content); + } + +} diff --git a/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/framework/file/package-info.java b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/framework/file/package-info.java new file mode 100644 index 0000000..bd5ad3f --- /dev/null +++ b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/framework/file/package-info.java @@ -0,0 +1,12 @@ +/** + * 文件客户端,支持多种存储器 + * + * 1. local:本地磁盘 + * 2. ftp:FTP 服务器 + * 3. sftp:SFTP 服务器 + * 4. db:数据库 + * 5. s3:支持 S3 协议的云存储服务,例如说 MinIO、阿里云、华为云、腾讯云、七牛云等等 + * + * @author 芋道源码 + */ +package com.tashow.cloud.infra.framework.file; diff --git a/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/framework/monitor/config/AdminServerConfiguration.java b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/framework/monitor/config/AdminServerConfiguration.java new file mode 100644 index 0000000..3035b30 --- /dev/null +++ b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/framework/monitor/config/AdminServerConfiguration.java @@ -0,0 +1,9 @@ +package com.tashow.cloud.infra.framework.monitor.config; + +import de.codecentric.boot.admin.server.config.EnableAdminServer; +import org.springframework.context.annotation.Configuration; + +@Configuration(proxyBeanMethods = false) +@EnableAdminServer +public class AdminServerConfiguration { +} diff --git a/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/framework/monitor/package-info.java b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/framework/monitor/package-info.java new file mode 100644 index 0000000..2149935 --- /dev/null +++ b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/framework/monitor/package-info.java @@ -0,0 +1,4 @@ +/** + * 使用 Spring Boot Admin 实现简单的监控平台 + */ +package com.tashow.cloud.infra.framework.monitor; diff --git a/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/framework/monitor/《芋道 Spring Boot 监控工具 Admin 入门》.md b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/framework/monitor/《芋道 Spring Boot 监控工具 Admin 入门》.md new file mode 100644 index 0000000..a1e3676 --- /dev/null +++ b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/framework/monitor/《芋道 Spring Boot 监控工具 Admin 入门》.md @@ -0,0 +1 @@ + diff --git a/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/framework/package-info.java b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/framework/package-info.java new file mode 100644 index 0000000..18de46f --- /dev/null +++ b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/framework/package-info.java @@ -0,0 +1,6 @@ +/** + * 属于 infra 模块的 framework 封装 + * + * @author 芋道源码 + */ +package com.tashow.cloud.infra.framework; diff --git a/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/framework/rpc/config/RpcConfiguration.java b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/framework/rpc/config/RpcConfiguration.java new file mode 100644 index 0000000..b1e9a50 --- /dev/null +++ b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/framework/rpc/config/RpcConfiguration.java @@ -0,0 +1,10 @@ +package com.tashow.cloud.infra.framework.rpc.config; + +import com.tashow.cloud.systemapi.api.user.AdminUserApi; +import org.springframework.cloud.openfeign.EnableFeignClients; +import org.springframework.context.annotation.Configuration; + +@Configuration(proxyBeanMethods = false) +@EnableFeignClients(clients = AdminUserApi.class) +public class RpcConfiguration { +} diff --git a/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/framework/rpc/package-info.java b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/framework/rpc/package-info.java new file mode 100644 index 0000000..2b839c9 --- /dev/null +++ b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/framework/rpc/package-info.java @@ -0,0 +1,4 @@ +/** + * 占位 + */ +package com.tashow.cloud.infra.framework.rpc; diff --git a/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/framework/security/config/SecurityConfiguration.java b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/framework/security/config/SecurityConfiguration.java new file mode 100644 index 0000000..7f62d36 --- /dev/null +++ b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/framework/security/config/SecurityConfiguration.java @@ -0,0 +1,45 @@ +package com.tashow.cloud.infra.framework.security.config; + +import com.tashow.cloud.infraapi.enums.ApiConstants; +import com.tashow.cloud.security.security.config.AuthorizeRequestsCustomizer; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configurers.AuthorizeHttpRequestsConfigurer; + +/** + * Infra 模块的 Security 配置 + */ +@Configuration(proxyBeanMethods = false, value = "infraSecurityConfiguration") +public class SecurityConfiguration { + + @Value("${spring.boot.admin.context-path:''}") + private String adminSeverContextPath; + + @Bean("infraAuthorizeRequestsCustomizer") + public AuthorizeRequestsCustomizer authorizeRequestsCustomizer() { + return new AuthorizeRequestsCustomizer() { + + @Override + public void customize(AuthorizeHttpRequestsConfigurer.AuthorizationManagerRequestMatcherRegistry registry) { + // Spring Boot Actuator 的安全配置 + registry.requestMatchers("/actuator").permitAll() + .requestMatchers("/actuator/**").permitAll(); + // Druid 监控 + registry.requestMatchers("/druid/**").permitAll(); + // Spring Boot Admin Server 的安全配置 + registry.requestMatchers(adminSeverContextPath).permitAll() + .requestMatchers(adminSeverContextPath + "/**").permitAll(); + // 文件读取 + registry.requestMatchers(buildAdminApi("/infra/file/*/get/**")).permitAll(); + + // TODO 芋艿:这个每个项目都需要重复配置,得捉摸有没通用的方案 + // RPC 服务的安全配置 + registry.requestMatchers(ApiConstants.PREFIX + "/**").permitAll(); + } + + }; + } + +} diff --git a/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/framework/security/core/package-info.java b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/framework/security/core/package-info.java new file mode 100644 index 0000000..01e0ad8 --- /dev/null +++ b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/framework/security/core/package-info.java @@ -0,0 +1,4 @@ +/** + * 占位 + */ +package com.tashow.cloud.infra.framework.security.core; diff --git a/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/job/logger/AccessLogCleanJob.java b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/job/logger/AccessLogCleanJob.java new file mode 100644 index 0000000..7f6c5d7 --- /dev/null +++ b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/job/logger/AccessLogCleanJob.java @@ -0,0 +1,40 @@ +package com.tashow.cloud.infra.job.logger; + +import com.tashow.cloud.infra.service.logger.ApiAccessLogService; +import com.tashow.cloud.tenant.core.aop.TenantIgnore; +import com.xxl.job.core.handler.annotation.XxlJob; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import jakarta.annotation.Resource; + +/** + * 物理删除 N 天前的访问日志的 Job + * + * @author j-sentinel + */ +@Component +@Slf4j +public class AccessLogCleanJob { + + @Resource + private ApiAccessLogService apiAccessLogService; + + /** + * 清理超过(14)天的日志 + */ + private static final Integer JOB_CLEAN_RETAIN_DAY = 14; + + /** + * 每次删除间隔的条数,如果值太高可能会造成数据库的压力过大 + */ + private static final Integer DELETE_LIMIT = 100; + + @XxlJob("accessLogCleanJob") + @TenantIgnore + public void execute() { + Integer count = apiAccessLogService.cleanAccessLog(JOB_CLEAN_RETAIN_DAY, DELETE_LIMIT); + log.info("[execute][定时执行清理访问日志数量 ({}) 个]", count); + } + +} diff --git a/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/job/logger/ErrorLogCleanJob.java b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/job/logger/ErrorLogCleanJob.java new file mode 100644 index 0000000..b84fc48 --- /dev/null +++ b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/job/logger/ErrorLogCleanJob.java @@ -0,0 +1,40 @@ +package com.tashow.cloud.infra.job.logger; + +import com.tashow.cloud.infra.service.logger.ApiErrorLogService; +import com.tashow.cloud.tenant.core.aop.TenantIgnore; +import com.xxl.job.core.handler.annotation.XxlJob; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import jakarta.annotation.Resource; + +/** + * 物理删除 N 天前的错误日志的 Job + * + * @author j-sentinel + */ +@Slf4j +@Component +public class ErrorLogCleanJob { + + @Resource + private ApiErrorLogService apiErrorLogService; + + /** + * 清理超过(14)天的日志 + */ + private static final Integer JOB_CLEAN_RETAIN_DAY = 14; + + /** + * 每次删除间隔的条数,如果值太高可能会造成数据库的压力过大 + */ + private static final Integer DELETE_LIMIT = 100; + + @XxlJob("errorLogCleanJob") + @TenantIgnore + public void execute() { + Integer count = apiErrorLogService.cleanErrorLog(JOB_CLEAN_RETAIN_DAY,DELETE_LIMIT); + log.info("[execute][定时执行清理错误日志数量 ({}) 个]", count); + } + +} diff --git a/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/job/package-info.java b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/job/package-info.java new file mode 100644 index 0000000..855452b --- /dev/null +++ b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/job/package-info.java @@ -0,0 +1,4 @@ +/** + * 占位,无特殊含义 + */ +package com.tashow.cloud.infra.job; diff --git a/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/mq/consumer/package-info.java b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/mq/consumer/package-info.java new file mode 100644 index 0000000..c0d8cb3 --- /dev/null +++ b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/mq/consumer/package-info.java @@ -0,0 +1,4 @@ +/** + * 消息队列的消费者 + */ +package com.tashow.cloud.infra.mq.consumer; diff --git a/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/mq/message/package-info.java b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/mq/message/package-info.java new file mode 100644 index 0000000..a0e2c93 --- /dev/null +++ b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/mq/message/package-info.java @@ -0,0 +1,4 @@ +/** + * 消息队列的消息 + */ +package com.tashow.cloud.infra.mq.message; diff --git a/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/mq/producer/package-info.java b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/mq/producer/package-info.java new file mode 100644 index 0000000..21dd3f3 --- /dev/null +++ b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/mq/producer/package-info.java @@ -0,0 +1,4 @@ +/** + * 消息队列的生产者 + */ +package com.tashow.cloud.infra.mq.producer; diff --git a/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/service/codegen/CodegenService.java b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/service/codegen/CodegenService.java new file mode 100644 index 0000000..207d449 --- /dev/null +++ b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/service/codegen/CodegenService.java @@ -0,0 +1,101 @@ +package com.tashow.cloud.infra.service.codegen; + +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.infra.controller.admin.codegen.vo.CodegenCreateListReqVO; +import com.tashow.cloud.infra.controller.admin.codegen.vo.CodegenUpdateReqVO; +import com.tashow.cloud.infra.controller.admin.codegen.vo.table.CodegenTablePageReqVO; +import com.tashow.cloud.infra.controller.admin.codegen.vo.table.DatabaseTableRespVO; +import com.tashow.cloud.infra.dal.dataobject.codegen.CodegenColumnDO; +import com.tashow.cloud.infra.dal.dataobject.codegen.CodegenTableDO; + +import java.util.List; +import java.util.Map; + +/** + * 代码生成 Service 接口 + * + * @author 芋道源码 + */ +public interface CodegenService { + + /** + * 基于数据库的表结构,创建代码生成器的表定义 + * + * @param userId 用户编号 + * @param reqVO 表信息 + * @return 创建的表定义的编号数组 + */ + List createCodegenList(Long userId, CodegenCreateListReqVO reqVO); + + /** + * 更新数据库的表和字段定义 + * + * @param updateReqVO 更新信息 + */ + void updateCodegen(CodegenUpdateReqVO updateReqVO); + + /** + * 基于数据库的表结构,同步数据库的表和字段定义 + * + * @param tableId 表编号 + */ + void syncCodegenFromDB(Long tableId); + + /** + * 删除数据库的表和字段定义 + * + * @param tableId 数据编号 + */ + void deleteCodegen(Long tableId); + + /** + * 获得表定义列表 + * + * @param dataSourceConfigId 数据源配置的编号 + * @return 表定义列表 + */ + List getCodegenTableList(Long dataSourceConfigId); + + /** + * 获得表定义分页 + * + * @param pageReqVO 分页条件 + * @return 表定义分页 + */ + PageResult getCodegenTablePage(CodegenTablePageReqVO pageReqVO); + + /** + * 获得表定义 + * + * @param id 表编号 + * @return 表定义 + */ + CodegenTableDO getCodegenTable(Long id); + + /** + * 获得指定表的字段定义数组 + * + * @param tableId 表编号 + * @return 字段定义数组 + */ + List getCodegenColumnListByTableId(Long tableId); + + /** + * 执行指定表的代码生成 + * + * @param tableId 表编号 + * @return 生成结果。key 为文件路径,value 为对应的代码内容 + */ + Map generationCodes(Long tableId); + + /** + * 获得数据库自带的表定义列表 + * + * @param dataSourceConfigId 数据源的配置编号 + * @param name 表名称 + * @param comment 表描述 + * @return 表定义列表 + */ + List getDatabaseTableList(Long dataSourceConfigId, String name, String comment); + +} diff --git a/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/service/codegen/CodegenServiceImpl.java b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/service/codegen/CodegenServiceImpl.java new file mode 100644 index 0000000..d5fd8d1 --- /dev/null +++ b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/service/codegen/CodegenServiceImpl.java @@ -0,0 +1,322 @@ +package com.tashow.cloud.infra.service.codegen; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.util.StrUtil; +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.common.util.object.BeanUtils; +import com.tashow.cloud.infra.controller.admin.codegen.vo.CodegenCreateListReqVO; +import com.tashow.cloud.infra.controller.admin.codegen.vo.CodegenUpdateReqVO; +import com.tashow.cloud.infra.controller.admin.codegen.vo.table.CodegenTablePageReqVO; +import com.tashow.cloud.infra.controller.admin.codegen.vo.table.DatabaseTableRespVO; +import com.tashow.cloud.infra.dal.dataobject.codegen.CodegenColumnDO; +import com.tashow.cloud.infra.dal.dataobject.codegen.CodegenTableDO; +import com.tashow.cloud.infra.dal.mysql.codegen.CodegenColumnMapper; +import com.tashow.cloud.infra.dal.mysql.codegen.CodegenTableMapper; +import com.tashow.cloud.infra.enums.codegen.CodegenSceneEnum; +import com.tashow.cloud.infra.enums.codegen.CodegenTemplateTypeEnum; +import com.tashow.cloud.infra.service.codegen.inner.CodegenBuilder; +import com.tashow.cloud.infra.service.codegen.inner.CodegenEngine; +import com.tashow.cloud.infra.service.db.DatabaseTableService; +import com.tashow.cloud.infra.controller.admin.codegen.vo.CodegenCreateListReqVO; +import com.tashow.cloud.infra.controller.admin.codegen.vo.CodegenUpdateReqVO; +import com.tashow.cloud.infra.controller.admin.codegen.vo.table.CodegenTablePageReqVO; +import com.tashow.cloud.infra.controller.admin.codegen.vo.table.DatabaseTableRespVO; +import com.tashow.cloud.infra.dal.dataobject.codegen.CodegenColumnDO; +import com.tashow.cloud.infra.dal.dataobject.codegen.CodegenTableDO; +import com.tashow.cloud.infra.dal.mysql.codegen.CodegenColumnMapper; +import com.tashow.cloud.infra.dal.mysql.codegen.CodegenTableMapper; +import com.tashow.cloud.infra.enums.codegen.CodegenSceneEnum; +import com.tashow.cloud.infra.enums.codegen.CodegenTemplateTypeEnum; +import com.tashow.cloud.infra.framework.codegen.config.CodegenProperties; +import com.tashow.cloud.infra.service.codegen.inner.CodegenBuilder; +import com.tashow.cloud.infra.service.codegen.inner.CodegenEngine; +import com.tashow.cloud.infra.service.db.DatabaseTableService; +import com.tashow.cloud.infra.controller.admin.codegen.vo.CodegenCreateListReqVO; +import com.tashow.cloud.infra.controller.admin.codegen.vo.CodegenUpdateReqVO; +import com.tashow.cloud.infra.controller.admin.codegen.vo.table.CodegenTablePageReqVO; +import com.tashow.cloud.infra.controller.admin.codegen.vo.table.DatabaseTableRespVO; +import com.tashow.cloud.infra.dal.dataobject.codegen.CodegenColumnDO; +import com.tashow.cloud.infra.dal.dataobject.codegen.CodegenTableDO; +import com.tashow.cloud.infra.dal.mysql.codegen.CodegenColumnMapper; +import com.tashow.cloud.infra.dal.mysql.codegen.CodegenTableMapper; +import com.tashow.cloud.infraapi.enums.ErrorCodeConstants; +import com.tashow.cloud.infra.enums.codegen.CodegenSceneEnum; +import com.tashow.cloud.infra.enums.codegen.CodegenTemplateTypeEnum; +import com.tashow.cloud.infra.service.codegen.inner.CodegenBuilder; +import com.tashow.cloud.infra.service.codegen.inner.CodegenEngine; +import com.tashow.cloud.infra.service.db.DatabaseTableService; +import com.tashow.cloud.systemapi.api.user.AdminUserApi; +import com.baomidou.mybatisplus.generator.config.po.TableField; +import com.baomidou.mybatisplus.generator.config.po.TableInfo; +import com.google.common.annotations.VisibleForTesting; +import jakarta.annotation.Resource; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.*; +import java.util.function.BiPredicate; +import java.util.stream.Collectors; +import java.util.stream.IntStream; + +import static com.tashow.cloud.common.exception.util.ServiceExceptionUtil.exception; +import static com.tashow.cloud.common.util.collection.CollectionUtils.convertMap; +import static com.tashow.cloud.common.util.collection.CollectionUtils.convertSet; + +/** + * 代码生成 Service 实现类 + * + * @author 芋道源码 + */ +@Service +public class CodegenServiceImpl implements CodegenService { + + @Resource + private DatabaseTableService databaseTableService; + + @Resource + private CodegenTableMapper codegenTableMapper; + @Resource + private CodegenColumnMapper codegenColumnMapper; + + @Resource + private AdminUserApi userApi; + + @Resource + private CodegenBuilder codegenBuilder; + @Resource + private CodegenEngine codegenEngine; + + @Resource + private CodegenProperties codegenProperties; + + @Override + @Transactional(rollbackFor = Exception.class) + public List createCodegenList(Long userId, CodegenCreateListReqVO reqVO) { + List ids = new ArrayList<>(reqVO.getTableNames().size()); + // 遍历添加。虽然效率会低一点,但是没必要做成完全批量,因为不会这么大量 + reqVO.getTableNames().forEach(tableName -> ids.add(createCodegen(userId, reqVO.getDataSourceConfigId(), tableName))); + return ids; + } + + private Long createCodegen(Long userId, Long dataSourceConfigId, String tableName) { + // 从数据库中,获得数据库表结构 + TableInfo tableInfo = databaseTableService.getTable(dataSourceConfigId, tableName); + // 导入 + return createCodegen0(userId, dataSourceConfigId, tableInfo); + } + + private Long createCodegen0(Long userId, Long dataSourceConfigId, TableInfo tableInfo) { + // 校验导入的表和字段非空 + validateTableInfo(tableInfo); + // 校验是否已经存在 + if (codegenTableMapper.selectByTableNameAndDataSourceConfigId(tableInfo.getName(), + dataSourceConfigId) != null) { + throw exception(ErrorCodeConstants.CODEGEN_TABLE_EXISTS); + } + + // 构建 CodegenTableDO 对象,插入到 DB 中 + CodegenTableDO table = codegenBuilder.buildTable(tableInfo); + table.setDataSourceConfigId(dataSourceConfigId); + table.setScene(CodegenSceneEnum.ADMIN.getScene()); // 默认配置下,使用管理后台的模板 + table.setFrontType(codegenProperties.getFrontType()); + table.setAuthor(userApi.getUser(userId).getCheckedData().getNickname()); + codegenTableMapper.insert(table); + + // 构建 CodegenColumnDO 数组,插入到 DB 中 + List columns = codegenBuilder.buildColumns(table.getId(), tableInfo.getFields()); + // 如果没有主键,则使用第一个字段作为主键 + if (!tableInfo.isHavePrimaryKey()) { + columns.get(0).setPrimaryKey(true); + } + codegenColumnMapper.insertBatch(columns); + return table.getId(); + } + + @VisibleForTesting + void validateTableInfo(TableInfo tableInfo) { + if (tableInfo == null) { + throw exception(ErrorCodeConstants.CODEGEN_IMPORT_TABLE_NULL); + } + if (StrUtil.isEmpty(tableInfo.getComment())) { + throw exception(ErrorCodeConstants.CODEGEN_TABLE_INFO_TABLE_COMMENT_IS_NULL); + } + if (CollUtil.isEmpty(tableInfo.getFields())) { + throw exception(ErrorCodeConstants.CODEGEN_IMPORT_COLUMNS_NULL); + } + tableInfo.getFields().forEach(field -> { + if (StrUtil.isEmpty(field.getComment())) { + throw exception(ErrorCodeConstants.CODEGEN_TABLE_INFO_COLUMN_COMMENT_IS_NULL, field.getName()); + } + }); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void updateCodegen(CodegenUpdateReqVO updateReqVO) { + // 校验是否已经存在 + if (codegenTableMapper.selectById(updateReqVO.getTable().getId()) == null) { + throw exception(ErrorCodeConstants.CODEGEN_TABLE_NOT_EXISTS); + } + // 校验主表字段存在 + if (Objects.equals(updateReqVO.getTable().getTemplateType(), CodegenTemplateTypeEnum.SUB.getType())) { + if (codegenTableMapper.selectById(updateReqVO.getTable().getMasterTableId()) == null) { + throw exception(ErrorCodeConstants.CODEGEN_MASTER_TABLE_NOT_EXISTS, updateReqVO.getTable().getMasterTableId()); + } + if (CollUtil.findOne(updateReqVO.getColumns(), // 关联主表的字段不存在 + column -> column.getId().equals(updateReqVO.getTable().getSubJoinColumnId())) == null) { + throw exception(ErrorCodeConstants.CODEGEN_SUB_COLUMN_NOT_EXISTS, updateReqVO.getTable().getSubJoinColumnId()); + } + } + + // 更新 table 表定义 + CodegenTableDO updateTableObj = BeanUtils.toBean(updateReqVO.getTable(), CodegenTableDO.class); + codegenTableMapper.updateById(updateTableObj); + // 更新 column 字段定义 + List updateColumnObjs = BeanUtils.toBean(updateReqVO.getColumns(), CodegenColumnDO.class); + updateColumnObjs.forEach(updateColumnObj -> codegenColumnMapper.updateById(updateColumnObj)); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void syncCodegenFromDB(Long tableId) { + // 校验是否已经存在 + CodegenTableDO table = codegenTableMapper.selectById(tableId); + if (table == null) { + throw exception(ErrorCodeConstants.CODEGEN_TABLE_NOT_EXISTS); + } + // 从数据库中,获得数据库表结构 + TableInfo tableInfo = databaseTableService.getTable(table.getDataSourceConfigId(), table.getTableName()); + // 执行同步 + syncCodegen0(tableId, tableInfo); + } + + private void syncCodegen0(Long tableId, TableInfo tableInfo) { + // 1. 校验导入的表和字段非空 + validateTableInfo(tableInfo); + List tableFields = tableInfo.getFields(); + + // 2. 构建 CodegenColumnDO 数组,只同步新增的字段 + List codegenColumns = codegenColumnMapper.selectListByTableId(tableId); + Set codegenColumnNames = convertSet(codegenColumns, CodegenColumnDO::getColumnName); + + // 3.1 计算需要【修改】的字段,插入时重新插入,删除时将原来的删除 + Map codegenColumnDOMap = convertMap(codegenColumns, CodegenColumnDO::getColumnName); + BiPredicate primaryKeyPredicate = + (tableField, codegenColumn) -> tableField.getMetaInfo().getJdbcType().name().equals(codegenColumn.getDataType()) + && tableField.getMetaInfo().isNullable() == codegenColumn.getNullable() + && tableField.isKeyFlag() == codegenColumn.getPrimaryKey() + && tableField.getComment().equals(codegenColumn.getColumnComment()); + Set modifyFieldNames = IntStream.range(0, tableFields.size()).mapToObj(index -> { + TableField tableField = tableFields.get(index); + String columnName = tableField.getColumnName(); + CodegenColumnDO codegenColumn = codegenColumnDOMap.get(columnName); + if (codegenColumn == null) { + return null; + } + if (!primaryKeyPredicate.test(tableField, codegenColumn) || codegenColumn.getOrdinalPosition() != index) { + return columnName; + } + return null; + }).filter(Objects::nonNull).collect(Collectors.toSet()); + // 3.2 计算需要【删除】的字段 + Set tableFieldNames = convertSet(tableFields, TableField::getName); + Set deleteColumnIds = codegenColumns.stream() + .filter(column -> (!tableFieldNames.contains(column.getColumnName())) || modifyFieldNames.contains(column.getColumnName())) + .map(CodegenColumnDO::getId).collect(Collectors.toSet()); + // 移除已经存在的字段 + tableFields.removeIf(column -> codegenColumnNames.contains(column.getColumnName()) && (!modifyFieldNames.contains(column.getColumnName()))); + if (CollUtil.isEmpty(tableFields) && CollUtil.isEmpty(deleteColumnIds)) { + throw exception(ErrorCodeConstants.CODEGEN_SYNC_NONE_CHANGE); + } + + // 4.1 插入新增的字段 + List columns = codegenBuilder.buildColumns(tableId, tableFields); + codegenColumnMapper.insertBatch(columns); + // 4.2 删除不存在的字段 + if (CollUtil.isNotEmpty(deleteColumnIds)) { + codegenColumnMapper.deleteBatchIds(deleteColumnIds); + } + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void deleteCodegen(Long tableId) { + // 校验是否已经存在 + if (codegenTableMapper.selectById(tableId) == null) { + throw exception(ErrorCodeConstants.CODEGEN_TABLE_NOT_EXISTS); + } + + // 删除 table 表定义 + codegenTableMapper.deleteById(tableId); + // 删除 column 字段定义 + codegenColumnMapper.deleteListByTableId(tableId); + } + + @Override + public List getCodegenTableList(Long dataSourceConfigId) { + return codegenTableMapper.selectListByDataSourceConfigId(dataSourceConfigId); + } + + @Override + public PageResult getCodegenTablePage(CodegenTablePageReqVO pageReqVO) { + return codegenTableMapper.selectPage(pageReqVO); + } + + @Override + public CodegenTableDO getCodegenTable(Long id) { + return codegenTableMapper.selectById(id); + } + + @Override + public List getCodegenColumnListByTableId(Long tableId) { + return codegenColumnMapper.selectListByTableId(tableId); + } + + @Override + public Map generationCodes(Long tableId) { + // 校验是否已经存在 + CodegenTableDO table = codegenTableMapper.selectById(tableId); + if (table == null) { + throw exception(ErrorCodeConstants.CODEGEN_TABLE_NOT_EXISTS); + } + List columns = codegenColumnMapper.selectListByTableId(tableId); + if (CollUtil.isEmpty(columns)) { + throw exception(ErrorCodeConstants.CODEGEN_COLUMN_NOT_EXISTS); + } + + // 如果是主子表,则加载对应的子表信息 + List subTables = null; + List> subColumnsList = null; + if (CodegenTemplateTypeEnum.isMaster(table.getTemplateType())) { + // 校验子表存在 + subTables = codegenTableMapper.selectListByTemplateTypeAndMasterTableId( + CodegenTemplateTypeEnum.SUB.getType(), tableId); + if (CollUtil.isEmpty(subTables)) { + throw exception(ErrorCodeConstants.CODEGEN_MASTER_GENERATION_FAIL_NO_SUB_TABLE); + } + // 校验子表的关联字段存在 + subColumnsList = new ArrayList<>(); + for (CodegenTableDO subTable : subTables) { + List subColumns = codegenColumnMapper.selectListByTableId(subTable.getId()); + if (CollUtil.findOne(subColumns, column -> column.getId().equals(subTable.getSubJoinColumnId())) == null) { + throw exception(ErrorCodeConstants.CODEGEN_SUB_COLUMN_NOT_EXISTS, subTable.getId()); + } + subColumnsList.add(subColumns); + } + } + + // 执行生成 + return codegenEngine.execute(table, columns, subTables, subColumnsList); + } + + @Override + public List getDatabaseTableList(Long dataSourceConfigId, String name, String comment) { + List tables = databaseTableService.getTableList(dataSourceConfigId, name, comment); + // 移除在 Codegen 中,已经存在的 + Set existsTables = convertSet( + codegenTableMapper.selectListByDataSourceConfigId(dataSourceConfigId), CodegenTableDO::getTableName); + tables.removeIf(table -> existsTables.contains(table.getName())); + return BeanUtils.toBean(tables, DatabaseTableRespVO.class); + } + +} diff --git a/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/service/codegen/inner/CodegenBuilder.java b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/service/codegen/inner/CodegenBuilder.java new file mode 100644 index 0000000..46158b0 --- /dev/null +++ b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/service/codegen/inner/CodegenBuilder.java @@ -0,0 +1,233 @@ +package com.tashow.cloud.infra.service.codegen.inner; + +import cn.hutool.core.map.MapUtil; +import cn.hutool.core.util.ReflectUtil; +import cn.hutool.core.util.StrUtil; +import com.tashow.cloud.mybatis.mybatis.core.dataobject.BaseDO; +import com.tashow.cloud.infra.convert.codegen.CodegenConvert; +import com.tashow.cloud.infra.dal.dataobject.codegen.CodegenColumnDO; +import com.tashow.cloud.infra.dal.dataobject.codegen.CodegenTableDO; +import com.tashow.cloud.infra.enums.codegen.CodegenColumnHtmlTypeEnum; +import com.tashow.cloud.infra.enums.codegen.CodegenColumnListConditionEnum; +import com.tashow.cloud.infra.enums.codegen.CodegenTemplateTypeEnum; +import com.tashow.cloud.infra.convert.codegen.CodegenConvert; +import com.tashow.cloud.infra.dal.dataobject.codegen.CodegenColumnDO; +import com.tashow.cloud.infra.dal.dataobject.codegen.CodegenTableDO; +import com.tashow.cloud.infra.enums.codegen.CodegenColumnHtmlTypeEnum; +import com.tashow.cloud.infra.enums.codegen.CodegenColumnListConditionEnum; +import com.tashow.cloud.infra.enums.codegen.CodegenTemplateTypeEnum; +import com.baomidou.mybatisplus.generator.config.po.TableField; +import com.baomidou.mybatisplus.generator.config.po.TableInfo; +import com.google.common.collect.Sets; +import com.tashow.cloud.infra.convert.codegen.CodegenConvert; +import com.tashow.cloud.infra.dal.dataobject.codegen.CodegenColumnDO; +import com.tashow.cloud.infra.dal.dataobject.codegen.CodegenTableDO; +import com.tashow.cloud.infra.enums.codegen.CodegenColumnHtmlTypeEnum; +import com.tashow.cloud.infra.enums.codegen.CodegenColumnListConditionEnum; +import com.tashow.cloud.infra.enums.codegen.CodegenTemplateTypeEnum; +import org.springframework.stereotype.Component; + +import java.time.LocalDateTime; +import java.util.*; + +import static cn.hutool.core.text.CharSequenceUtil.*; +import static cn.hutool.core.util.RandomUtil.randomEle; +import static cn.hutool.core.util.RandomUtil.randomInt; + +/** + * 代码生成器的 Builder,负责: + * 1. 将数据库的表 {@link TableInfo} 定义,构建成 {@link CodegenTableDO} + * 2. 将数据库的列 {@link TableField} 构定义,建成 {@link CodegenColumnDO} + */ +@Component +public class CodegenBuilder { + + /** + * 字段名与 {@link CodegenColumnListConditionEnum} 的默认映射 + * 注意,字段的匹配以后缀的方式 + */ + private static final Map COLUMN_LIST_OPERATION_CONDITION_MAPPINGS = + MapUtil.builder() + .put("name", CodegenColumnListConditionEnum.LIKE) + .put("time", CodegenColumnListConditionEnum.BETWEEN) + .put("date", CodegenColumnListConditionEnum.BETWEEN) + .build(); + + /** + * 字段名与 {@link CodegenColumnHtmlTypeEnum} 的默认映射 + * 注意,字段的匹配以后缀的方式 + */ + private static final Map COLUMN_HTML_TYPE_MAPPINGS = + MapUtil.builder() + .put("status", CodegenColumnHtmlTypeEnum.RADIO) + .put("sex", CodegenColumnHtmlTypeEnum.RADIO) + .put("type", CodegenColumnHtmlTypeEnum.SELECT) + .put("image", CodegenColumnHtmlTypeEnum.IMAGE_UPLOAD) + .put("file", CodegenColumnHtmlTypeEnum.FILE_UPLOAD) + .put("content", CodegenColumnHtmlTypeEnum.EDITOR) + .put("description", CodegenColumnHtmlTypeEnum.EDITOR) + .put("demo", CodegenColumnHtmlTypeEnum.EDITOR) + .put("time", CodegenColumnHtmlTypeEnum.DATETIME) + .put("date", CodegenColumnHtmlTypeEnum.DATETIME) + .build(); + + /** + * 多租户编号的字段名 + */ + public static final String TENANT_ID_FIELD = "tenantId"; + /** + * {@link BaseDO} 的字段 + */ + public static final Set BASE_DO_FIELDS = new HashSet<>(); + /** + * 新增操作,不需要传递的字段 + */ + private static final Set CREATE_OPERATION_EXCLUDE_COLUMN = Sets.newHashSet("id"); + /** + * 修改操作,不需要传递的字段 + */ + private static final Set UPDATE_OPERATION_EXCLUDE_COLUMN = Sets.newHashSet(); + /** + * 列表操作的条件,不需要传递的字段 + */ + private static final Set LIST_OPERATION_EXCLUDE_COLUMN = Sets.newHashSet("id"); + /** + * 列表操作的结果,不需要返回的字段 + */ + private static final Set LIST_OPERATION_RESULT_EXCLUDE_COLUMN = Sets.newHashSet(); + + static { + Arrays.stream(ReflectUtil.getFields(BaseDO.class)).forEach(field -> BASE_DO_FIELDS.add(field.getName())); + BASE_DO_FIELDS.add(TENANT_ID_FIELD); + // 处理 OPERATION 相关的字段 + CREATE_OPERATION_EXCLUDE_COLUMN.addAll(BASE_DO_FIELDS); + UPDATE_OPERATION_EXCLUDE_COLUMN.addAll(BASE_DO_FIELDS); + LIST_OPERATION_EXCLUDE_COLUMN.addAll(BASE_DO_FIELDS); + LIST_OPERATION_EXCLUDE_COLUMN.remove("createTime"); // 创建时间,还是可能需要传递的 + LIST_OPERATION_RESULT_EXCLUDE_COLUMN.addAll(BASE_DO_FIELDS); + LIST_OPERATION_RESULT_EXCLUDE_COLUMN.remove("createTime"); // 创建时间,还是需要返回的 + } + + public CodegenTableDO buildTable(TableInfo tableInfo) { + CodegenTableDO table = CodegenConvert.INSTANCE.convert(tableInfo); + initTableDefault(table); + return table; + } + + /** + * 初始化 Table 表的默认字段 + * + * @param table 表定义 + */ + private void initTableDefault(CodegenTableDO table) { + // 以 system_dept 举例子。moduleName 为 system、businessName 为 dept、className 为 Dept + // 如果希望以 System 前缀,则可以手动在【代码生成 - 修改生成配置 - 基本信息】,将实体类名称改为 SystemDept 即可 + String tableName = table.getTableName().toLowerCase(); + // 第一步,_ 前缀的前面,作为 module 名字;第二步,moduleName 必须小写; + table.setModuleName(subBefore(tableName, '_', false).toLowerCase()); + // 第一步,第一个 _ 前缀的后面,作为 module 名字; 第二步,可能存在多个 _ 的情况,转换成驼峰; 第三步,businessName 必须小写; + table.setBusinessName(toCamelCase(subAfter(tableName, '_', false)).toLowerCase()); + // 驼峰 + 首字母大写;第一步,第一个 _ 前缀的后面,作为 class 名字;第二步,驼峰命名 + table.setClassName(upperFirst(toCamelCase(subAfter(tableName, '_', false)))); + // 去除结尾的表,作为类描述 + table.setClassComment(StrUtil.removeSuffixIgnoreCase(table.getTableComment(), "表")); + table.setTemplateType(CodegenTemplateTypeEnum.ONE.getType()); + } + + public List buildColumns(Long tableId, List tableFields) { + List columns = CodegenConvert.INSTANCE.convertList(tableFields); + int index = 1; + for (CodegenColumnDO column : columns) { + column.setTableId(tableId); + column.setOrdinalPosition(index++); + // 特殊处理:Byte => Integer + if (Byte.class.getSimpleName().equals(column.getJavaType())) { + column.setJavaType(Integer.class.getSimpleName()); + } + // 初始化 Column 列的默认字段 + processColumnOperation(column); // 处理 CRUD 相关的字段的默认值 + processColumnUI(column); // 处理 UI 相关的字段的默认值 + processColumnExample(column); // 处理字段的 swagger example 示例 + } + return columns; + } + + private void processColumnOperation(CodegenColumnDO column) { + // 处理 createOperation 字段 + column.setCreateOperation(!CREATE_OPERATION_EXCLUDE_COLUMN.contains(column.getJavaField()) + && !column.getPrimaryKey()); // 对于主键,创建时无需传递 + // 处理 updateOperation 字段 + column.setUpdateOperation(!UPDATE_OPERATION_EXCLUDE_COLUMN.contains(column.getJavaField()) + || column.getPrimaryKey()); // 对于主键,更新时需要传递 + // 处理 listOperation 字段 + column.setListOperation(!LIST_OPERATION_EXCLUDE_COLUMN.contains(column.getJavaField()) + && !column.getPrimaryKey()); // 对于主键,列表过滤不需要传递 + // 处理 listOperationCondition 字段 + COLUMN_LIST_OPERATION_CONDITION_MAPPINGS.entrySet().stream() + .filter(entry -> StrUtil.endWithIgnoreCase(column.getJavaField(), entry.getKey())) + .findFirst().ifPresent(entry -> column.setListOperationCondition(entry.getValue().getCondition())); + if (column.getListOperationCondition() == null) { + column.setListOperationCondition(CodegenColumnListConditionEnum.EQ.getCondition()); + } + // 处理 listOperationResult 字段 + column.setListOperationResult(!LIST_OPERATION_RESULT_EXCLUDE_COLUMN.contains(column.getJavaField())); + } + + private void processColumnUI(CodegenColumnDO column) { + // 基于后缀进行匹配 + COLUMN_HTML_TYPE_MAPPINGS.entrySet().stream() + .filter(entry -> StrUtil.endWithIgnoreCase(column.getJavaField(), entry.getKey())) + .findFirst().ifPresent(entry -> column.setHtmlType(entry.getValue().getType())); + // 如果是 Boolean 类型时,设置为 radio 类型. + if (Boolean.class.getSimpleName().equals(column.getJavaType())) { + column.setHtmlType(CodegenColumnHtmlTypeEnum.RADIO.getType()); + } + // 如果是 LocalDateTime 类型,则设置为 datetime 类型 + if (LocalDateTime.class.getSimpleName().equals(column.getJavaType())) { + column.setHtmlType(CodegenColumnHtmlTypeEnum.DATETIME.getType()); + } + // 兜底,设置默认为 input 类型 + if (column.getHtmlType() == null) { + column.setHtmlType(CodegenColumnHtmlTypeEnum.INPUT.getType()); + } + } + + /** + * 处理字段的 swagger example 示例 + * + * @param column 字段 + */ + private void processColumnExample(CodegenColumnDO column) { + // id、price、count 等可能是整数的后缀 + if (StrUtil.endWithAnyIgnoreCase(column.getJavaField(), "id", "price", "count")) { + column.setExample(String.valueOf(randomInt(1, Short.MAX_VALUE))); + return; + } + // name + if (StrUtil.endWithIgnoreCase(column.getJavaField(), "name")) { + column.setExample(randomEle(new String[]{"张三", "李四", "王五", "赵六", "芋艿"})); + return; + } + // status + if (StrUtil.endWithAnyIgnoreCase(column.getJavaField(), "status", "type")) { + column.setExample(randomEle(new String[]{"1", "2"})); + return; + } + // url + if (StrUtil.endWithIgnoreCase(column.getColumnName(), "url")) { + column.setExample("https://www.iocoder.cn"); + return; + } + // reason + if (StrUtil.endWithIgnoreCase(column.getColumnName(), "reason")) { + column.setExample(randomEle(new String[]{"不喜欢", "不对", "不好", "不香"})); + return; + } + // description、memo、remark + if (StrUtil.endWithAnyIgnoreCase(column.getColumnName(), "description", "memo", "remark")) { + column.setExample(randomEle(new String[]{"你猜", "随便", "你说的对"})); + return; + } + } + +} diff --git a/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/service/codegen/inner/CodegenEngine.java b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/service/codegen/inner/CodegenEngine.java new file mode 100644 index 0000000..2757cfb --- /dev/null +++ b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/service/codegen/inner/CodegenEngine.java @@ -0,0 +1,511 @@ +package com.tashow.cloud.infra.service.codegen.inner; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.map.MapUtil; +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.extra.template.TemplateConfig; +import cn.hutool.extra.template.TemplateEngine; +import cn.hutool.extra.template.engine.velocity.VelocityEngine; +import cn.hutool.system.SystemUtil; +import com.tashow.cloud.common.exception.util.ServiceExceptionUtil; +import com.tashow.cloud.common.util.date.LocalDateTimeUtils; +import com.tashow.cloud.common.util.object.ObjectUtils; +import com.tashow.cloud.common.util.string.StrUtils; +import com.tashow.cloud.web.apilog.core.annotation.ApiAccessLog; +import com.tashow.cloud.common.pojo.CommonResult; +import com.tashow.cloud.common.pojo.PageParam; +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.common.util.collection.CollectionUtils; +import com.tashow.cloud.common.util.date.DateUtils; +import com.tashow.cloud.common.util.object.BeanUtils; +import com.tashow.cloud.excel.excel.core.annotations.DictFormat; +import com.tashow.cloud.excel.excel.core.convert.DictConvert; +import com.tashow.cloud.excel.excel.core.util.ExcelUtils; +import com.tashow.cloud.mybatis.mybatis.core.dataobject.BaseDO; +import com.tashow.cloud.mybatis.mybatis.core.mapper.BaseMapperX; +import com.tashow.cloud.mybatis.mybatis.core.query.LambdaQueryWrapperX; +import com.tashow.cloud.infra.dal.dataobject.codegen.CodegenColumnDO; +import com.tashow.cloud.infra.dal.dataobject.codegen.CodegenTableDO; +import com.tashow.cloud.infra.enums.codegen.CodegenFrontTypeEnum; +import com.tashow.cloud.infra.enums.codegen.CodegenSceneEnum; +import com.tashow.cloud.infra.enums.codegen.CodegenTemplateTypeEnum; +import com.tashow.cloud.infra.framework.codegen.config.CodegenProperties; +import com.google.common.annotations.VisibleForTesting; +import com.google.common.collect.ImmutableTable; +import com.google.common.collect.Maps; +import com.google.common.collect.Table; +import com.tashow.cloud.web.apilog.core.enums.OperateTypeEnum; +import jakarta.annotation.PostConstruct; +import jakarta.annotation.Resource; +import lombok.Setter; +import org.springframework.stereotype.Component; + +import java.util.*; + +import static cn.hutool.core.map.MapUtil.getStr; +import static cn.hutool.core.text.CharSequenceUtil.*; + +/** + * 代码生成的引擎,用于具体生成代码 + * 目前基于 {@link org.apache.velocity.app.Velocity} 模板引擎实现 + * + * 考虑到 Java 模板引擎的框架非常多,Freemarker、Velocity、Thymeleaf 等等,所以我们采用 hutool 封装的 {@link cn.hutool.extra.template.Template} 抽象 + * + * @author 芋道源码 + */ +@Component +public class CodegenEngine { + + /** + * 后端的模板配置 + * + * key:模板在 resources 的地址 + * value:生成的路径 + */ + private static final Map SERVER_TEMPLATES = MapUtil.builder(new LinkedHashMap<>()) // 有序 + // Java module-biz Main + .put(javaTemplatePath("controller/vo/pageReqVO"), javaModuleImplVOFilePath("PageReqVO")) + .put(javaTemplatePath("controller/vo/listReqVO"), javaModuleImplVOFilePath("ListReqVO")) + .put(javaTemplatePath("controller/vo/respVO"), javaModuleImplVOFilePath("RespVO")) + .put(javaTemplatePath("controller/vo/saveReqVO"), javaModuleImplVOFilePath("SaveReqVO")) + .put(javaTemplatePath("controller/controller"), javaModuleImplControllerFilePath()) + .put(javaTemplatePath("dal/do"), + javaModuleImplMainFilePath("dal/dataobject/${table.businessName}/${table.className}DO")) + .put(javaTemplatePath("dal/do_sub"), // 特殊:主子表专属逻辑 + javaModuleImplMainFilePath("dal/dataobject/${table.businessName}/${subTable.className}DO")) + .put(javaTemplatePath("dal/mapper"), + javaModuleImplMainFilePath("dal/mysql/${table.businessName}/${table.className}Mapper")) + .put(javaTemplatePath("dal/mapper_sub"), // 特殊:主子表专属逻辑 + javaModuleImplMainFilePath("dal/mysql/${table.businessName}/${subTable.className}Mapper")) + .put(javaTemplatePath("dal/mapper.xml"), mapperXmlFilePath()) + .put(javaTemplatePath("service/serviceImpl"), + javaModuleImplMainFilePath("service/${table.businessName}/${table.className}ServiceImpl")) + .put(javaTemplatePath("service/service"), + javaModuleImplMainFilePath("service/${table.businessName}/${table.className}Service")) + // Java module-biz Test + .put(javaTemplatePath("test/serviceTest"), + javaModuleImplTestFilePath("service/${table.businessName}/${table.className}ServiceImplTest")) + // Java module-api Main + .put(javaTemplatePath("enums/errorcode"), javaModuleApiMainFilePath("enums/ErrorCodeConstants_手动操作")) + // SQL + .put("codegen/sql/sql.vm", "sql/sql.sql") + .put("codegen/sql/h2.vm", "sql/h2.sql") + .build(); + + /** + * 后端的配置模版 + * + * key1:UI 模版的类型 {@link CodegenFrontTypeEnum#getType()} + * key2:模板在 resources 的地址 + * value:生成的路径 + */ + private static final Table FRONT_TEMPLATES = ImmutableTable.builder() + // Vue2 标准模版 + .put(CodegenFrontTypeEnum.VUE2.getType(), vueTemplatePath("views/index.vue"), + vueFilePath("views/${table.moduleName}/${table.businessName}/index.vue")) + .put(CodegenFrontTypeEnum.VUE2.getType(), vueTemplatePath("api/api.js"), + vueFilePath("api/${table.moduleName}/${table.businessName}/index.js")) + .put(CodegenFrontTypeEnum.VUE2.getType(), vueTemplatePath("views/form.vue"), + vueFilePath("views/${table.moduleName}/${table.businessName}/${simpleClassName}Form.vue")) + .put(CodegenFrontTypeEnum.VUE2.getType(), vueTemplatePath("views/components/form_sub_normal.vue"), // 特殊:主子表专属逻辑 + vueFilePath("views/${table.moduleName}/${table.businessName}/components/${subSimpleClassName}Form.vue")) + .put(CodegenFrontTypeEnum.VUE2.getType(), vueTemplatePath("views/components/form_sub_inner.vue"), // 特殊:主子表专属逻辑 + vueFilePath("views/${table.moduleName}/${table.businessName}/components/${subSimpleClassName}Form.vue")) + .put(CodegenFrontTypeEnum.VUE2.getType(), vueTemplatePath("views/components/form_sub_erp.vue"), // 特殊:主子表专属逻辑 + vueFilePath("views/${table.moduleName}/${table.businessName}/components/${subSimpleClassName}Form.vue")) + .put(CodegenFrontTypeEnum.VUE2.getType(), vueTemplatePath("views/components/list_sub_inner.vue"), // 特殊:主子表专属逻辑 + vueFilePath("views/${table.moduleName}/${table.businessName}/components/${subSimpleClassName}List.vue")) + .put(CodegenFrontTypeEnum.VUE2.getType(), vueTemplatePath("views/components/list_sub_erp.vue"), // 特殊:主子表专属逻辑 + vueFilePath("views/${table.moduleName}/${table.businessName}/components/${subSimpleClassName}List.vue")) + // Vue3 标准模版 + .put(CodegenFrontTypeEnum.VUE3.getType(), vue3TemplatePath("views/index.vue"), + vue3FilePath("views/${table.moduleName}/${table.businessName}/index.vue")) + .put(CodegenFrontTypeEnum.VUE3.getType(), vue3TemplatePath("views/form.vue"), + vue3FilePath("views/${table.moduleName}/${table.businessName}/${simpleClassName}Form.vue")) + .put(CodegenFrontTypeEnum.VUE3.getType(), vue3TemplatePath("views/components/form_sub_normal.vue"), // 特殊:主子表专属逻辑 + vue3FilePath("views/${table.moduleName}/${table.businessName}/components/${subSimpleClassName}Form.vue")) + .put(CodegenFrontTypeEnum.VUE3.getType(), vue3TemplatePath("views/components/form_sub_inner.vue"), // 特殊:主子表专属逻辑 + vue3FilePath("views/${table.moduleName}/${table.businessName}/components/${subSimpleClassName}Form.vue")) + .put(CodegenFrontTypeEnum.VUE3.getType(), vue3TemplatePath("views/components/form_sub_erp.vue"), // 特殊:主子表专属逻辑 + vue3FilePath("views/${table.moduleName}/${table.businessName}/components/${subSimpleClassName}Form.vue")) + .put(CodegenFrontTypeEnum.VUE3.getType(), vue3TemplatePath("views/components/list_sub_inner.vue"), // 特殊:主子表专属逻辑 + vue3FilePath("views/${table.moduleName}/${table.businessName}/components/${subSimpleClassName}List.vue")) + .put(CodegenFrontTypeEnum.VUE3.getType(), vue3TemplatePath("views/components/list_sub_erp.vue"), // 特殊:主子表专属逻辑 + vue3FilePath("views/${table.moduleName}/${table.businessName}/components/${subSimpleClassName}List.vue")) + .put(CodegenFrontTypeEnum.VUE3.getType(), vue3TemplatePath("api/api.ts"), + vue3FilePath("api/${table.moduleName}/${table.businessName}/index.ts")) + // Vue3 vben 模版 + .put(CodegenFrontTypeEnum.VUE3_VBEN.getType(), vue3VbenTemplatePath("views/data.ts"), + vue3FilePath("views/${table.moduleName}/${table.businessName}/${classNameVar}.data.ts")) + .put(CodegenFrontTypeEnum.VUE3_VBEN.getType(), vue3VbenTemplatePath("views/index.vue"), + vue3FilePath("views/${table.moduleName}/${table.businessName}/index.vue")) + .put(CodegenFrontTypeEnum.VUE3_VBEN.getType(), vue3VbenTemplatePath("views/form.vue"), + vue3FilePath("views/${table.moduleName}/${table.businessName}/${simpleClassName}Modal.vue")) + .put(CodegenFrontTypeEnum.VUE3_VBEN.getType(), vue3VbenTemplatePath("api/api.ts"), + vue3FilePath("api/${table.moduleName}/${table.businessName}/index.ts")) + .build(); + + @Resource + private CodegenProperties codegenProperties; + + /** + * 是否使用 jakarta 包,用于解决 Spring Boot 2.X 和 3.X 的兼容性问题 + * + * true - 使用 jakarta.validation.constraints.* + * false - 使用 javax.validation.constraints.* + */ + @Setter // 允许设置的原因,是因为单测需要手动改变 + private Boolean jakartaEnable; + + /** + * 模板引擎,由 hutool 实现 + */ + private final TemplateEngine templateEngine; + /** + * 全局通用变量映射 + */ + private final Map globalBindingMap = new HashMap<>(); + + public CodegenEngine() { + // 初始化 TemplateEngine 属性 + TemplateConfig config = new TemplateConfig(); + config.setResourceMode(TemplateConfig.ResourceMode.CLASSPATH); + this.templateEngine = new VelocityEngine(config); + // 设置 javaxEnable,按照是否使用 JDK17 来判断 + this.jakartaEnable = SystemUtil.getJavaInfo().isJavaVersionAtLeast(1700); // 17.00 * 100 + } + + @PostConstruct + @VisibleForTesting + void initGlobalBindingMap() { + // 全局配置 + globalBindingMap.put("basePackage", codegenProperties.getBasePackage()); + globalBindingMap.put("baseFrameworkPackage", codegenProperties.getBasePackage() + + '.' + "framework"); // 用于后续获取测试类的 package 地址 + globalBindingMap.put("jakartaPackage", jakartaEnable ? "jakarta" : "javax"); + // 全局 Java Bean + globalBindingMap.put("CommonResultClassName", CommonResult.class.getName()); + globalBindingMap.put("PageResultClassName", PageResult.class.getName()); + // VO 类,独有字段 + globalBindingMap.put("PageParamClassName", PageParam.class.getName()); + globalBindingMap.put("DictFormatClassName", DictFormat.class.getName()); + // DO 类,独有字段 + globalBindingMap.put("BaseDOClassName", BaseDO.class.getName()); + globalBindingMap.put("baseDOFields", CodegenBuilder.BASE_DO_FIELDS); + globalBindingMap.put("QueryWrapperClassName", LambdaQueryWrapperX.class.getName()); + globalBindingMap.put("BaseMapperClassName", BaseMapperX.class.getName()); + // Util 工具类 + globalBindingMap.put("ServiceExceptionUtilClassName", ServiceExceptionUtil.class.getName()); + globalBindingMap.put("DateUtilsClassName", DateUtils.class.getName()); + globalBindingMap.put("ExcelUtilsClassName", ExcelUtils.class.getName()); + globalBindingMap.put("LocalDateTimeUtilsClassName", LocalDateTimeUtils.class.getName()); + globalBindingMap.put("ObjectUtilsClassName", ObjectUtils.class.getName()); + globalBindingMap.put("DictConvertClassName", DictConvert.class.getName()); + globalBindingMap.put("ApiAccessLogClassName", ApiAccessLog.class.getName()); + globalBindingMap.put("OperateTypeEnumClassName", OperateTypeEnum.class.getName()); + globalBindingMap.put("BeanUtils", BeanUtils.class.getName()); + } + + /** + * 生成代码 + * + * @param table 表定义 + * @param columns table 的字段定义数组 + * @param subTables 子表数组,当且仅当主子表时使用 + * @param subColumnsList subTables 的字段定义数组 + * @return 生成的代码,key 是路径,value 是对应代码 + */ + public Map execute(CodegenTableDO table, List columns, + List subTables, List> subColumnsList) { + // 1.1 初始化 bindMap 上下文 + Map bindingMap = initBindingMap(table, columns, subTables, subColumnsList); + // 1.2 获得模版 + Map templates = getTemplates(table.getFrontType()); + + // 2. 执行生成 + Map result = Maps.newLinkedHashMapWithExpectedSize(templates.size()); // 有序 + templates.forEach((vmPath, filePath) -> { + // 2.1 特殊:主子表专属逻辑 + if (isSubTemplate(vmPath)) { + generateSubCode(table, subTables, result, vmPath, filePath, bindingMap); + return; + // 2.2 特殊:树表专属逻辑 + } else if (isPageReqVOTemplate(vmPath)) { + // 减少多余的类生成,例如说 PageVO.java 类 + if (CodegenTemplateTypeEnum.isTree(table.getTemplateType())) { + return; + } + } else if (isListReqVOTemplate(vmPath)) { + // 减少多余的类生成,例如说 ListVO.java 类 + if (!CodegenTemplateTypeEnum.isTree(table.getTemplateType())) { + return; + } + } + // 2.3 默认生成 + generateCode(result, vmPath, filePath, bindingMap); + }); + return result; + } + + private void generateCode(Map result, String vmPath, + String filePath, Map bindingMap) { + filePath = formatFilePath(filePath, bindingMap); + String content = templateEngine.getTemplate(vmPath).render(bindingMap); + // 格式化代码 + content = prettyCode(content); + result.put(filePath, content); + } + + private void generateSubCode(CodegenTableDO table, List subTables, + Map result, String vmPath, + String filePath, Map bindingMap) { + // 没有子表,所以不生成 + if (CollUtil.isEmpty(subTables)) { + return; + } + // 主子表的模式匹配。目的:过滤掉个性化的模版 + if (vmPath.contains("_normal") + && ObjectUtil.notEqual(table.getTemplateType(), CodegenTemplateTypeEnum.MASTER_NORMAL.getType())) { + return; + } + if (vmPath.contains("_erp") + && ObjectUtil.notEqual(table.getTemplateType(), CodegenTemplateTypeEnum.MASTER_ERP.getType())) { + return; + } + if (vmPath.contains("_inner") + && ObjectUtil.notEqual(table.getTemplateType(), CodegenTemplateTypeEnum.MASTER_INNER.getType())) { + return; + } + + // 逐个生成 + for (int i = 0; i < subTables.size(); i++) { + bindingMap.put("subIndex", i); + generateCode(result, vmPath, filePath, bindingMap); + } + bindingMap.remove("subIndex"); + } + + /** + * 格式化生成后的代码 + * + * 因为尽量让 vm 模版简单,所以统一的处理都在这个方法。 + * 如果不处理,Vue 的 Pretty 格式校验可能会报错 + * + * @param content 格式化前的代码 + * @return 格式化后的代码 + */ + private String prettyCode(String content) { + // Vue 界面:去除字段后面多余的 , 逗号,解决前端的 Pretty 代码格式检查的报错 + content = content.replaceAll(",\n}", "\n}").replaceAll(",\n }", "\n }"); + // Vue 界面:去除多的 dateFormatter,只有一个的情况下,说明没使用到 + if (StrUtil.count(content, "dateFormatter") == 1) { + content = StrUtils.removeLineContains(content, "dateFormatter"); + } + // Vue2 界面:修正 $refs + if (StrUtil.count(content, "this.refs") >= 1) { + content = content.replace("this.refs", "this.$refs"); + } + // Vue 界面:去除多的 dict 相关,只有一个的情况下,说明没使用到 + if (StrUtil.count(content, "getIntDictOptions") == 1) { + content = content.replace("getIntDictOptions, ", ""); + } + if (StrUtil.count(content, "getStrDictOptions") == 1) { + content = content.replace("getStrDictOptions, ", ""); + } + if (StrUtil.count(content, "getBoolDictOptions") == 1) { + content = content.replace("getBoolDictOptions, ", ""); + } + if (StrUtil.count(content, "DICT_TYPE.") == 0) { + content = StrUtils.removeLineContains(content, "DICT_TYPE"); + } + return content; + } + + private Map initBindingMap(CodegenTableDO table, List columns, + List subTables, List> subColumnsList) { + // 创建 bindingMap + Map bindingMap = new HashMap<>(globalBindingMap); + bindingMap.put("table", table); + bindingMap.put("columns", columns); + bindingMap.put("primaryColumn", CollectionUtils.findFirst(columns, CodegenColumnDO::getPrimaryKey)); // 主键字段 + bindingMap.put("sceneEnum", CodegenSceneEnum.valueOf(table.getScene())); + + // className 相关 + // 去掉指定前缀,将 TestDictType 转换成 DictType. 因为在 create 等方法后,不需要带上 Test 前缀 + String simpleClassName = equalsAnyIgnoreCase(table.getClassName(), table.getModuleName()) ? table.getClassName() + : removePrefix(table.getClassName(), upperFirst(table.getModuleName())); + bindingMap.put("simpleClassName", simpleClassName); + bindingMap.put("simpleClassName_underlineCase", toUnderlineCase(simpleClassName)); // 将 DictType 转换成 dict_type + bindingMap.put("classNameVar", lowerFirst(simpleClassName)); // 将 DictType 转换成 dictType,用于变量 + // 将 DictType 转换成 dict-type + String simpleClassNameStrikeCase = toSymbolCase(simpleClassName, '-'); + bindingMap.put("simpleClassName_strikeCase", simpleClassNameStrikeCase); + // permission 前缀 + bindingMap.put("permissionPrefix", table.getModuleName() + ":" + simpleClassNameStrikeCase); + + // 特殊:树表专属逻辑 + if (CodegenTemplateTypeEnum.isTree(table.getTemplateType())) { + CodegenColumnDO treeParentColumn = CollUtil.findOne(columns, + column -> Objects.equals(column.getId(), table.getTreeParentColumnId())); + bindingMap.put("treeParentColumn", treeParentColumn); + bindingMap.put("treeParentColumn_javaField_underlineCase", toUnderlineCase(treeParentColumn.getJavaField())); + CodegenColumnDO treeNameColumn = CollUtil.findOne(columns, + column -> Objects.equals(column.getId(), table.getTreeNameColumnId())); + bindingMap.put("treeNameColumn", treeNameColumn); + bindingMap.put("treeNameColumn_javaField_underlineCase", toUnderlineCase(treeNameColumn.getJavaField())); + } + + // 特殊:主子表专属逻辑 + if (CollUtil.isNotEmpty(subTables)) { + // 创建 bindingMap + bindingMap.put("subTables", subTables); + bindingMap.put("subColumnsList", subColumnsList); + List subPrimaryColumns = new ArrayList<>(); + List subJoinColumns = new ArrayList<>(); + List subJoinColumnStrikeCases = new ArrayList<>(); + List subSimpleClassNames = new ArrayList<>(); + List subClassNameVars = new ArrayList<>(); + List simpleClassNameUnderlineCases = new ArrayList<>(); + List subSimpleClassNameStrikeCases = new ArrayList<>(); + for (int i = 0; i < subTables.size(); i++) { + CodegenTableDO subTable = subTables.get(i); + List subColumns = subColumnsList.get(i); + subPrimaryColumns.add(CollectionUtils.findFirst(subColumns, CodegenColumnDO::getPrimaryKey)); // + CodegenColumnDO subColumn = CollectionUtils.findFirst(subColumns, // 关联的字段 + column -> Objects.equals(column.getId(), subTable.getSubJoinColumnId())); + subJoinColumns.add(subColumn); + subJoinColumnStrikeCases.add(toSymbolCase(subColumn.getJavaField(), '-')); // 将 DictType 转换成 dict-type + // className 相关 + String subSimpleClassName = removePrefix(subTable.getClassName(), upperFirst(subTable.getModuleName())); + subSimpleClassNames.add(subSimpleClassName); + simpleClassNameUnderlineCases.add(toUnderlineCase(subSimpleClassName)); // 将 DictType 转换成 dict_type + subClassNameVars.add(lowerFirst(subSimpleClassName)); // 将 DictType 转换成 dictType,用于变量 + subSimpleClassNameStrikeCases.add(toSymbolCase(subSimpleClassName, '-')); // 将 DictType 转换成 dict-type + } + bindingMap.put("subPrimaryColumns", subPrimaryColumns); + bindingMap.put("subJoinColumns", subJoinColumns); + bindingMap.put("subJoinColumn_strikeCases", subJoinColumnStrikeCases); + bindingMap.put("subSimpleClassNames", subSimpleClassNames); + bindingMap.put("simpleClassNameUnderlineCases", simpleClassNameUnderlineCases); + bindingMap.put("subClassNameVars", subClassNameVars); + bindingMap.put("subSimpleClassName_strikeCases", subSimpleClassNameStrikeCases); + } + return bindingMap; + } + + private Map getTemplates(Integer frontType) { + Map templates = new LinkedHashMap<>(); + templates.putAll(SERVER_TEMPLATES); + templates.putAll(FRONT_TEMPLATES.row(frontType)); + // 如果禁用单元测试,则移除对应的模版 + if (Boolean.FALSE.equals(codegenProperties.getUnitTestEnable())) { + templates.remove(javaTemplatePath("test/serviceTest")); + templates.remove("codegen/sql/h2.vm"); + } + return templates; + } + + @SuppressWarnings("unchecked") + private String formatFilePath(String filePath, Map bindingMap) { + filePath = StrUtil.replace(filePath, "${basePackage}", + getStr(bindingMap, "basePackage").replaceAll("\\.", "/")); + filePath = StrUtil.replace(filePath, "${classNameVar}", + getStr(bindingMap, "classNameVar")); + filePath = StrUtil.replace(filePath, "${simpleClassName}", + getStr(bindingMap, "simpleClassName")); + // sceneEnum 包含的字段 + CodegenSceneEnum sceneEnum = (CodegenSceneEnum) bindingMap.get("sceneEnum"); + filePath = StrUtil.replace(filePath, "${sceneEnum.prefixClass}", sceneEnum.getPrefixClass()); + filePath = StrUtil.replace(filePath, "${sceneEnum.basePackage}", sceneEnum.getBasePackage()); + // table 包含的字段 + CodegenTableDO table = (CodegenTableDO) bindingMap.get("table"); + filePath = StrUtil.replace(filePath, "${table.moduleName}", table.getModuleName()); + filePath = StrUtil.replace(filePath, "${table.businessName}", table.getBusinessName()); + filePath = StrUtil.replace(filePath, "${table.className}", table.getClassName()); + // 特殊:主子表专属逻辑 + Integer subIndex = (Integer) bindingMap.get("subIndex"); + if (subIndex != null) { + CodegenTableDO subTable = ((List) bindingMap.get("subTables")).get(subIndex); + filePath = StrUtil.replace(filePath, "${subTable.moduleName}", subTable.getModuleName()); + filePath = StrUtil.replace(filePath, "${subTable.businessName}", subTable.getBusinessName()); + filePath = StrUtil.replace(filePath, "${subTable.className}", subTable.getClassName()); + filePath = StrUtil.replace(filePath, "${subSimpleClassName}", + ((List) bindingMap.get("subSimpleClassNames")).get(subIndex)); + } + return filePath; + } + + private static String javaTemplatePath(String path) { + return "codegen/java/" + path + ".vm"; + } + + private static String javaModuleImplVOFilePath(String path) { + return javaModuleFilePath("controller/${sceneEnum.basePackage}/${table.businessName}/" + + "vo/${sceneEnum.prefixClass}${table.className}" + path, "biz", "main"); + } + + private static String javaModuleImplControllerFilePath() { + return javaModuleFilePath("controller/${sceneEnum.basePackage}/${table.businessName}/" + + "${sceneEnum.prefixClass}${table.className}Controller", "biz", "main"); + } + + private static String javaModuleImplMainFilePath(String path) { + return javaModuleFilePath(path, "biz", "main"); + } + + private static String javaModuleApiMainFilePath(String path) { + return javaModuleFilePath(path, "api", "main"); + } + + private static String javaModuleImplTestFilePath(String path) { + return javaModuleFilePath(path, "biz", "test"); + } + + private static String javaModuleFilePath(String path, String module, String src) { + return "yudao-module-${table.moduleName}/" + // 顶级模块 + "yudao-module-${table.moduleName}-" + module + "/" + // 子模块 + "src/" + src + "/java/${basePackage}/module/${table.moduleName}/" + path + ".java"; + } + + private static String mapperXmlFilePath() { + return "yudao-module-${table.moduleName}/" + // 顶级模块 + "yudao-module-${table.moduleName}-biz/" + // 子模块 + "src/main/resources/mapper/${table.businessName}/${table.className}Mapper.xml"; + } + + private static String vueTemplatePath(String path) { + return "codegen/vue/" + path + ".vm"; + } + + private static String vueFilePath(String path) { + return "yudao-ui-${sceneEnum.basePackage}-vue2/" + // 顶级目录 + "src/" + path; + } + + private static String vue3TemplatePath(String path) { + return "codegen/vue3/" + path + ".vm"; + } + + private static String vue3FilePath(String path) { + return "yudao-ui-${sceneEnum.basePackage}-vue3/" + // 顶级目录 + "src/" + path; + } + + private static String vue3VbenTemplatePath(String path) { + return "codegen/vue3_vben/" + path + ".vm"; + } + + private static boolean isSubTemplate(String path) { + return path.contains("_sub"); + } + + private static boolean isPageReqVOTemplate(String path) { + return path.contains("pageReqVO"); + } + + private static boolean isListReqVOTemplate(String path) { + return path.contains("listReqVO"); + } + +} diff --git a/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/service/config/ConfigService.java b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/service/config/ConfigService.java new file mode 100644 index 0000000..64f72c6 --- /dev/null +++ b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/service/config/ConfigService.java @@ -0,0 +1,63 @@ +package com.tashow.cloud.infra.service.config; + +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.infra.controller.admin.config.vo.ConfigPageReqVO; +import com.tashow.cloud.infra.controller.admin.config.vo.ConfigSaveReqVO; +import com.tashow.cloud.infra.dal.dataobject.config.ConfigDO; + +import jakarta.validation.Valid; + +/** + * 参数配置 Service 接口 + * + * @author 芋道源码 + */ +public interface ConfigService { + + /** + * 创建参数配置 + * + * @param createReqVO 创建信息 + * @return 配置编号 + */ + Long createConfig(@Valid ConfigSaveReqVO createReqVO); + + /** + * 更新参数配置 + * + * @param updateReqVO 更新信息 + */ + void updateConfig(@Valid ConfigSaveReqVO updateReqVO); + + /** + * 删除参数配置 + * + * @param id 配置编号 + */ + void deleteConfig(Long id); + + /** + * 获得参数配置 + * + * @param id 配置编号 + * @return 参数配置 + */ + ConfigDO getConfig(Long id); + + /** + * 根据参数键,获得参数配置 + * + * @param key 配置键 + * @return 参数配置 + */ + ConfigDO getConfigByKey(String key); + + /** + * 获得参数配置分页列表 + * + * @param reqVO 分页条件 + * @return 分页列表 + */ + PageResult getConfigPage(ConfigPageReqVO reqVO); + +} diff --git a/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/service/config/ConfigServiceImpl.java b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/service/config/ConfigServiceImpl.java new file mode 100644 index 0000000..8696a7c --- /dev/null +++ b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/service/config/ConfigServiceImpl.java @@ -0,0 +1,109 @@ +package com.tashow.cloud.infra.service.config; + +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.infra.convert.config.ConfigConvert; +import com.tashow.cloud.infra.enums.config.ConfigTypeEnum; +import com.tashow.cloud.infra.controller.admin.config.vo.ConfigPageReqVO; +import com.tashow.cloud.infra.controller.admin.config.vo.ConfigSaveReqVO; +import com.tashow.cloud.infra.dal.dataobject.config.ConfigDO; +import com.tashow.cloud.infra.dal.mysql.config.ConfigMapper; +import com.google.common.annotations.VisibleForTesting; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.validation.annotation.Validated; + +import jakarta.annotation.Resource; + +import static com.tashow.cloud.common.exception.util.ServiceExceptionUtil.exception; +import static com.tashow.cloud.infraapi.enums.ErrorCodeConstants.*; + +/** + * 参数配置 Service 实现类 + */ +@Service +@Slf4j +@Validated +public class ConfigServiceImpl implements ConfigService { + + @Resource + private ConfigMapper configMapper; + + @Override + public Long createConfig(ConfigSaveReqVO createReqVO) { + // 校验参数配置 key 的唯一性 + validateConfigKeyUnique(null, createReqVO.getKey()); + + // 插入参数配置 + ConfigDO config = ConfigConvert.INSTANCE.convert(createReqVO); + config.setType(ConfigTypeEnum.CUSTOM.getType()); + configMapper.insert(config); + return config.getId(); + } + + @Override + public void updateConfig(ConfigSaveReqVO updateReqVO) { + // 校验自己存在 + validateConfigExists(updateReqVO.getId()); + // 校验参数配置 key 的唯一性 + validateConfigKeyUnique(updateReqVO.getId(), updateReqVO.getKey()); + + // 更新参数配置 + ConfigDO updateObj = ConfigConvert.INSTANCE.convert(updateReqVO); + configMapper.updateById(updateObj); + } + + @Override + public void deleteConfig(Long id) { + // 校验配置存在 + ConfigDO config = validateConfigExists(id); + // 内置配置,不允许删除 + if (ConfigTypeEnum.SYSTEM.getType().equals(config.getType())) { + throw exception(CONFIG_CAN_NOT_DELETE_SYSTEM_TYPE); + } + // 删除 + configMapper.deleteById(id); + } + + @Override + public ConfigDO getConfig(Long id) { + return configMapper.selectById(id); + } + + @Override + public ConfigDO getConfigByKey(String key) { + return configMapper.selectByKey(key); + } + + @Override + public PageResult getConfigPage(ConfigPageReqVO pageReqVO) { + return configMapper.selectPage(pageReqVO); + } + + @VisibleForTesting + public ConfigDO validateConfigExists(Long id) { + if (id == null) { + return null; + } + ConfigDO config = configMapper.selectById(id); + if (config == null) { + throw exception(CONFIG_NOT_EXISTS); + } + return config; + } + + @VisibleForTesting + public void validateConfigKeyUnique(Long id, String key) { + ConfigDO config = configMapper.selectByKey(key); + if (config == null) { + return; + } + // 如果 id 为空,说明不用比较是否为相同 id 的参数配置 + if (id == null) { + throw exception(CONFIG_KEY_DUPLICATE); + } + if (!config.getId().equals(id)) { + throw exception(CONFIG_KEY_DUPLICATE); + } + } + +} diff --git a/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/service/db/DataSourceConfigService.java b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/service/db/DataSourceConfigService.java new file mode 100644 index 0000000..42ca625 --- /dev/null +++ b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/service/db/DataSourceConfigService.java @@ -0,0 +1,53 @@ +package com.tashow.cloud.infra.service.db; + +import com.tashow.cloud.infra.controller.admin.db.vo.DataSourceConfigSaveReqVO; +import com.tashow.cloud.infra.dal.dataobject.db.DataSourceConfigDO; + +import jakarta.validation.Valid; +import java.util.List; + +/** + * 数据源配置 Service 接口 + * + * @author 芋道源码 + */ +public interface DataSourceConfigService { + + /** + * 创建数据源配置 + * + * @param createReqVO 创建信息 + * @return 编号 + */ + Long createDataSourceConfig(@Valid DataSourceConfigSaveReqVO createReqVO); + + /** + * 更新数据源配置 + * + * @param updateReqVO 更新信息 + */ + void updateDataSourceConfig(@Valid DataSourceConfigSaveReqVO updateReqVO); + + /** + * 删除数据源配置 + * + * @param id 编号 + */ + void deleteDataSourceConfig(Long id); + + /** + * 获得数据源配置 + * + * @param id 编号 + * @return 数据源配置 + */ + DataSourceConfigDO getDataSourceConfig(Long id); + + /** + * 获得数据源配置列表 + * + * @return 数据源配置列表 + */ + List getDataSourceConfigList(); + +} diff --git a/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/service/db/DataSourceConfigServiceImpl.java b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/service/db/DataSourceConfigServiceImpl.java new file mode 100644 index 0000000..104cc38 --- /dev/null +++ b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/service/db/DataSourceConfigServiceImpl.java @@ -0,0 +1,106 @@ +package com.tashow.cloud.infra.service.db; + +import com.tashow.cloud.common.util.object.BeanUtils; +import com.tashow.cloud.infra.controller.admin.db.vo.DataSourceConfigSaveReqVO; +import com.tashow.cloud.infra.dal.dataobject.db.DataSourceConfigDO; +import com.tashow.cloud.infra.dal.mysql.db.DataSourceConfigMapper; +import com.baomidou.dynamic.datasource.creator.DataSourceProperty; +import com.baomidou.dynamic.datasource.spring.boot.autoconfigure.DynamicDataSourceProperties; +import com.tashow.cloud.mybatis.mybatis.core.util.JdbcUtils; +import org.springframework.stereotype.Service; +import org.springframework.validation.annotation.Validated; + +import jakarta.annotation.Resource; +import java.util.List; +import java.util.Objects; + +import static com.tashow.cloud.common.exception.util.ServiceExceptionUtil.exception; +import static com.tashow.cloud.infraapi.enums.ErrorCodeConstants.DATA_SOURCE_CONFIG_NOT_EXISTS; +import static com.tashow.cloud.infraapi.enums.ErrorCodeConstants.DATA_SOURCE_CONFIG_NOT_OK; + +/** + * 数据源配置 Service 实现类 + * + * @author 芋道源码 + */ +@Service +@Validated +public class DataSourceConfigServiceImpl implements DataSourceConfigService { + + @Resource + private DataSourceConfigMapper dataSourceConfigMapper; + + @Resource + private DynamicDataSourceProperties dynamicDataSourceProperties; + + @Override + public Long createDataSourceConfig(DataSourceConfigSaveReqVO createReqVO) { + DataSourceConfigDO config = BeanUtils.toBean(createReqVO, DataSourceConfigDO.class); + validateConnectionOK(config); + + // 插入 + dataSourceConfigMapper.insert(config); + // 返回 + return config.getId(); + } + + @Override + public void updateDataSourceConfig(DataSourceConfigSaveReqVO updateReqVO) { + // 校验存在 + validateDataSourceConfigExists(updateReqVO.getId()); + DataSourceConfigDO updateObj = BeanUtils.toBean(updateReqVO, DataSourceConfigDO.class); + validateConnectionOK(updateObj); + + // 更新 + dataSourceConfigMapper.updateById(updateObj); + } + + @Override + public void deleteDataSourceConfig(Long id) { + // 校验存在 + validateDataSourceConfigExists(id); + // 删除 + dataSourceConfigMapper.deleteById(id); + } + + private void validateDataSourceConfigExists(Long id) { + if (dataSourceConfigMapper.selectById(id) == null) { + throw exception(DATA_SOURCE_CONFIG_NOT_EXISTS); + } + } + + @Override + public DataSourceConfigDO getDataSourceConfig(Long id) { + // 如果 id 为 0,默认为 master 的数据源 + if (Objects.equals(id, DataSourceConfigDO.ID_MASTER)) { + return buildMasterDataSourceConfig(); + } + // 从 DB 中读取 + return dataSourceConfigMapper.selectById(id); + } + + @Override + public List getDataSourceConfigList() { + List result = dataSourceConfigMapper.selectList(); + // 补充 master 数据源 + result.add(0, buildMasterDataSourceConfig()); + return result; + } + + private void validateConnectionOK(DataSourceConfigDO config) { + boolean success = JdbcUtils.isConnectionOK(config.getUrl(), config.getUsername(), config.getPassword()); + if (!success) { + throw exception(DATA_SOURCE_CONFIG_NOT_OK); + } + } + + private DataSourceConfigDO buildMasterDataSourceConfig() { + String primary = dynamicDataSourceProperties.getPrimary(); + DataSourceProperty dataSourceProperty = dynamicDataSourceProperties.getDatasource().get(primary); + return new DataSourceConfigDO().setId(DataSourceConfigDO.ID_MASTER).setName(primary) + .setUrl(dataSourceProperty.getUrl()) + .setUsername(dataSourceProperty.getUsername()) + .setPassword(dataSourceProperty.getPassword()); + } + +} diff --git a/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/service/db/DatabaseTableService.java b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/service/db/DatabaseTableService.java new file mode 100644 index 0000000..2f09552 --- /dev/null +++ b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/service/db/DatabaseTableService.java @@ -0,0 +1,33 @@ +package com.tashow.cloud.infra.service.db; + +import com.baomidou.mybatisplus.generator.config.po.TableInfo; + +import java.util.List; + +/** + * 数据库表 Service + * + * @author 芋道源码 + */ +public interface DatabaseTableService { + + /** + * 获得表列表,基于表名称 + 表描述进行模糊匹配 + * + * @param dataSourceConfigId 数据源配置的编号 + * @param nameLike 表名称,模糊匹配 + * @param commentLike 表描述,模糊匹配 + * @return 表列表 + */ + List getTableList(Long dataSourceConfigId, String nameLike, String commentLike); + + /** + * 获得指定表名 + * + * @param dataSourceConfigId 数据源配置的编号 + * @param tableName 表名称 + * @return 表 + */ + TableInfo getTable(Long dataSourceConfigId, String tableName); + +} diff --git a/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/service/db/DatabaseTableServiceImpl.java b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/service/db/DatabaseTableServiceImpl.java new file mode 100644 index 0000000..21c8a35 --- /dev/null +++ b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/service/db/DatabaseTableServiceImpl.java @@ -0,0 +1,77 @@ +package com.tashow.cloud.infra.service.db; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.lang.Assert; +import cn.hutool.core.util.StrUtil; +import com.tashow.cloud.infra.dal.dataobject.db.DataSourceConfigDO; +import com.baomidou.mybatisplus.generator.config.DataSourceConfig; +import com.baomidou.mybatisplus.generator.config.GlobalConfig; +import com.baomidou.mybatisplus.generator.config.StrategyConfig; +import com.baomidou.mybatisplus.generator.config.builder.ConfigBuilder; +import com.baomidou.mybatisplus.generator.config.po.TableInfo; +import com.baomidou.mybatisplus.generator.config.rules.DateType; +import com.baomidou.mybatisplus.generator.query.SQLQuery; +import com.tashow.cloud.mybatis.mybatis.core.util.JdbcUtils; +import jakarta.annotation.Resource; +import org.springframework.stereotype.Service; + +import java.util.Comparator; +import java.util.List; +import java.util.stream.Collectors; + +/** + * 数据库表 Service 实现类 + * + * @author 芋道源码 + */ +@Service +public class DatabaseTableServiceImpl implements DatabaseTableService { + + @Resource + private DataSourceConfigService dataSourceConfigService; + + @Override + public List getTableList(Long dataSourceConfigId, String nameLike, String commentLike) { + List tables = getTableList0(dataSourceConfigId, null); + return tables.stream().filter(tableInfo -> (StrUtil.isEmpty(nameLike) || tableInfo.getName().contains(nameLike)) + && (StrUtil.isEmpty(commentLike) || tableInfo.getComment().contains(commentLike))) + .collect(Collectors.toList()); + } + + @Override + public TableInfo getTable(Long dataSourceConfigId, String name) { + return CollUtil.getFirst(getTableList0(dataSourceConfigId, name)); + } + + private List getTableList0(Long dataSourceConfigId, String name) { + // 获得数据源配置 + DataSourceConfigDO config = dataSourceConfigService.getDataSourceConfig(dataSourceConfigId); + Assert.notNull(config, "数据源({}) 不存在!", dataSourceConfigId); + + // 使用 MyBatis Plus Generator 解析表结构 + DataSourceConfig.Builder dataSourceConfigBuilder = new DataSourceConfig.Builder(config.getUrl(), config.getUsername(), + config.getPassword()); + if (JdbcUtils.isSQLServer(config.getUrl())) { // 特殊:SQLServer jdbc 非标准,参见 https://github.com/baomidou/mybatis-plus/issues/5419 + dataSourceConfigBuilder.databaseQueryClass(SQLQuery.class); + } + StrategyConfig.Builder strategyConfig = new StrategyConfig.Builder().enableSkipView(); // 忽略视图,业务上一般用不到 + if (StrUtil.isNotEmpty(name)) { + strategyConfig.addInclude(name); + } else { + // 移除工作流和定时任务前缀的表名 + strategyConfig.addExclude("ACT_[\\S\\s]+|QRTZ_[\\S\\s]+|FLW_[\\S\\s]+"); + // 移除 ORACLE 相关的系统表 + strategyConfig.addExclude("IMPDP_[\\S\\s]+|ALL_[\\S\\s]+|HS_[\\S\\\\s]+"); + strategyConfig.addExclude("[\\S\\s]+\\$[\\S\\s]+|[\\S\\s]+\\$"); // 表里不能有 $,一般有都是系统的表 + } + + GlobalConfig globalConfig = new GlobalConfig.Builder().dateType(DateType.TIME_PACK).build(); // 只使用 LocalDateTime 类型,不使用 LocalDate + ConfigBuilder builder = new ConfigBuilder(null, dataSourceConfigBuilder.build(), strategyConfig.build(), + null, globalConfig, null); + // 按照名字排序 + List tables = builder.getTableInfoList(); + tables.sort(Comparator.comparing(TableInfo::getName)); + return tables; + } + +} diff --git a/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/service/file/FileConfigService.java b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/service/file/FileConfigService.java new file mode 100644 index 0000000..d7e45f2 --- /dev/null +++ b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/service/file/FileConfigService.java @@ -0,0 +1,86 @@ +package com.tashow.cloud.infra.service.file; + +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.infra.controller.admin.file.vo.config.FileConfigPageReqVO; +import com.tashow.cloud.infra.controller.admin.file.vo.config.FileConfigSaveReqVO; +import com.tashow.cloud.infra.dal.dataobject.file.FileConfigDO; + +import com.tashow.cloud.infra.framework.file.core.client.FileClient; +import jakarta.validation.Valid; + +/** + * 文件配置 Service 接口 + * + * @author 芋道源码 + */ +public interface FileConfigService { + + /** + * 创建文件配置 + * + * @param createReqVO 创建信息 + * @return 编号 + */ + Long createFileConfig(@Valid FileConfigSaveReqVO createReqVO); + + /** + * 更新文件配置 + * + * @param updateReqVO 更新信息 + */ + void updateFileConfig(@Valid FileConfigSaveReqVO updateReqVO); + + /** + * 更新文件配置为 Master + * + * @param id 编号 + */ + void updateFileConfigMaster(Long id); + + /** + * 删除文件配置 + * + * @param id 编号 + */ + void deleteFileConfig(Long id); + + /** + * 获得文件配置 + * + * @param id 编号 + * @return 文件配置 + */ + FileConfigDO getFileConfig(Long id); + + /** + * 获得文件配置分页 + * + * @param pageReqVO 分页查询 + * @return 文件配置分页 + */ + PageResult getFileConfigPage(FileConfigPageReqVO pageReqVO); + + /** + * 测试文件配置是否正确,通过上传文件 + * + * @param id 编号 + * @return 文件 URL + */ + String testFileConfig(Long id) throws Exception; + + /** + * 获得指定编号的文件客户端 + * + * @param id 配置编号 + * @return 文件客户端 + */ + FileClient getFileClient(Long id); + + /** + * 获得 Master 文件客户端 + * + * @return 文件客户端 + */ + FileClient getMasterFileClient(); + +} diff --git a/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/service/file/FileConfigServiceImpl.java b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/service/file/FileConfigServiceImpl.java new file mode 100644 index 0000000..56b27c0 --- /dev/null +++ b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/service/file/FileConfigServiceImpl.java @@ -0,0 +1,189 @@ +package com.tashow.cloud.infra.service.file; + +import cn.hutool.core.io.resource.ResourceUtil; +import cn.hutool.core.util.IdUtil; +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.common.util.json.JsonUtils; +import com.tashow.cloud.common.util.validation.ValidationUtils; +import com.tashow.cloud.infra.convert.file.FileConfigConvert; +import com.tashow.cloud.infra.controller.admin.file.vo.config.FileConfigPageReqVO; +import com.tashow.cloud.infra.controller.admin.file.vo.config.FileConfigSaveReqVO; +import com.tashow.cloud.infra.dal.dataobject.file.FileConfigDO; +import com.tashow.cloud.infra.dal.mysql.file.FileConfigMapper; +import com.tashow.cloud.infra.framework.file.core.client.FileClient; +import com.tashow.cloud.infra.framework.file.core.client.FileClientConfig; +import com.tashow.cloud.infra.framework.file.core.client.FileClientFactory; +import com.tashow.cloud.infra.framework.file.core.enums.FileStorageEnum; +import com.google.common.cache.CacheLoader; +import com.google.common.cache.LoadingCache; +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.validation.annotation.Validated; + +import jakarta.annotation.Resource; +import jakarta.validation.Validator; +import java.time.Duration; +import java.util.Map; +import java.util.Objects; + +import static com.tashow.cloud.common.exception.util.ServiceExceptionUtil.exception; +import static com.tashow.cloud.common.util.cache.CacheUtils.buildAsyncReloadingCache; +import static com.tashow.cloud.infraapi.enums.ErrorCodeConstants.FILE_CONFIG_DELETE_FAIL_MASTER; +import static com.tashow.cloud.infraapi.enums.ErrorCodeConstants.FILE_CONFIG_NOT_EXISTS; + +/** + * 文件配置 Service 实现类 + * + * @author 芋道源码 + */ +@Service +@Validated +@Slf4j +public class FileConfigServiceImpl implements FileConfigService { + + private static final Long CACHE_MASTER_ID = 0L; + + /** + * {@link FileClient} 缓存,通过它异步刷新 fileClientFactory + */ + @Getter + private final LoadingCache clientCache = buildAsyncReloadingCache(Duration.ofSeconds(10L), + new CacheLoader() { + + @Override + public FileClient load(Long id) { + FileConfigDO config = Objects.equals(CACHE_MASTER_ID, id) ? + fileConfigMapper.selectByMaster() : fileConfigMapper.selectById(id); + if (config != null) { + fileClientFactory.createOrUpdateFileClient(config.getId(), config.getStorage(), config.getConfig()); + } + return fileClientFactory.getFileClient(null == config ? id : config.getId()); + } + + }); + + @Resource + private FileClientFactory fileClientFactory; + + @Resource + private FileConfigMapper fileConfigMapper; + + @Resource + private Validator validator; + + @Override + public Long createFileConfig(FileConfigSaveReqVO createReqVO) { + FileConfigDO fileConfig = FileConfigConvert.INSTANCE.convert(createReqVO) + .setConfig(parseClientConfig(createReqVO.getStorage(), createReqVO.getConfig())) + .setMaster(false); // 默认非 master + fileConfigMapper.insert(fileConfig); + return fileConfig.getId(); + } + + @Override + public void updateFileConfig(FileConfigSaveReqVO updateReqVO) { + // 校验存在 + FileConfigDO config = validateFileConfigExists(updateReqVO.getId()); + // 更新 + FileConfigDO updateObj = FileConfigConvert.INSTANCE.convert(updateReqVO) + .setConfig(parseClientConfig(config.getStorage(), updateReqVO.getConfig())); + fileConfigMapper.updateById(updateObj); + + // 清空缓存 + clearCache(config.getId(), null); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void updateFileConfigMaster(Long id) { + // 校验存在 + validateFileConfigExists(id); + // 更新其它为非 master + fileConfigMapper.updateBatch(new FileConfigDO().setMaster(false)); + // 更新 + fileConfigMapper.updateById(new FileConfigDO().setId(id).setMaster(true)); + + // 清空缓存 + clearCache(null, true); + } + + private FileClientConfig parseClientConfig(Integer storage, Map config) { + // 获取配置类 + Class configClass = FileStorageEnum.getByStorage(storage) + .getConfigClass(); + FileClientConfig clientConfig = JsonUtils.parseObject2(JsonUtils.toJsonString(config), configClass); + // 参数校验 + ValidationUtils.validate(validator, clientConfig); + // 设置参数 + return clientConfig; + } + + @Override + public void deleteFileConfig(Long id) { + // 校验存在 + FileConfigDO config = validateFileConfigExists(id); + if (Boolean.TRUE.equals(config.getMaster())) { + throw exception(FILE_CONFIG_DELETE_FAIL_MASTER); + } + // 删除 + fileConfigMapper.deleteById(id); + + // 清空缓存 + clearCache(id, null); + } + + /** + * 清空指定文件配置 + * + * @param id 配置编号 + * @param master 是否主配置 + */ + private void clearCache(Long id, Boolean master) { + if (id != null) { + clientCache.invalidate(id); + } + if (Boolean.TRUE.equals(master)) { + clientCache.invalidate(CACHE_MASTER_ID); + } + } + + private FileConfigDO validateFileConfigExists(Long id) { + FileConfigDO config = fileConfigMapper.selectById(id); + if (config == null) { + throw exception(FILE_CONFIG_NOT_EXISTS); + } + return config; + } + + @Override + public FileConfigDO getFileConfig(Long id) { + return fileConfigMapper.selectById(id); + } + + @Override + public PageResult getFileConfigPage(FileConfigPageReqVO pageReqVO) { + return fileConfigMapper.selectPage(pageReqVO); + } + + @Override + public String testFileConfig(Long id) throws Exception { + // 校验存在 + validateFileConfigExists(id); + // 上传文件 + byte[] content = ResourceUtil.readBytes("file/erweima.jpg"); + return getFileClient(id).upload(content, IdUtil.fastSimpleUUID() + ".jpg", "image/jpeg"); + } + + @Override + public FileClient getFileClient(Long id) { + return clientCache.getUnchecked(id); + } + + @Override + public FileClient getMasterFileClient() { + return clientCache.getUnchecked(CACHE_MASTER_ID); + } + +} diff --git a/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/service/file/FileService.java b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/service/file/FileService.java new file mode 100644 index 0000000..dfd26ba --- /dev/null +++ b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/service/file/FileService.java @@ -0,0 +1,66 @@ +package com.tashow.cloud.infra.service.file; + +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.infra.controller.admin.file.vo.file.FileCreateReqVO; +import com.tashow.cloud.infra.controller.admin.file.vo.file.FilePageReqVO; +import com.tashow.cloud.infra.controller.admin.file.vo.file.FilePresignedUrlRespVO; +import com.tashow.cloud.infra.dal.dataobject.file.FileDO; + +/** + * 文件 Service 接口 + * + * @author 芋道源码 + */ +public interface FileService { + + /** + * 获得文件分页 + * + * @param pageReqVO 分页查询 + * @return 文件分页 + */ + PageResult getFilePage(FilePageReqVO pageReqVO); + + /** + * 保存文件,并返回文件的访问路径 + * + * @param name 文件名称 + * @param path 文件路径 + * @param content 文件内容 + * @return 文件路径 + */ + String createFile(String name, String path, byte[] content); + + /** + * 创建文件 + * + * @param createReqVO 创建信息 + * @return 编号 + */ + Long createFile(FileCreateReqVO createReqVO); + + /** + * 删除文件 + * + * @param id 编号 + */ + void deleteFile(Long id) throws Exception; + + /** + * 获得文件内容 + * + * @param configId 配置编号 + * @param path 文件路径 + * @return 文件内容 + */ + byte[] getFileContent(Long configId, String path) throws Exception; + + /** + * 生成文件预签名地址信息 + * + * @param path 文件路径 + * @return 预签名地址信息 + */ + FilePresignedUrlRespVO getFilePresignedUrl(String path) throws Exception; + +} diff --git a/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/service/file/FileServiceImpl.java b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/service/file/FileServiceImpl.java new file mode 100644 index 0000000..ace0d22 --- /dev/null +++ b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/service/file/FileServiceImpl.java @@ -0,0 +1,116 @@ +package com.tashow.cloud.infra.service.file; + +import cn.hutool.core.lang.Assert; +import cn.hutool.core.util.StrUtil; +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.common.util.io.FileUtils; +import com.tashow.cloud.common.util.object.BeanUtils; +import com.tashow.cloud.infra.controller.admin.file.vo.file.FileCreateReqVO; +import com.tashow.cloud.infra.controller.admin.file.vo.file.FilePageReqVO; +import com.tashow.cloud.infra.controller.admin.file.vo.file.FilePresignedUrlRespVO; +import com.tashow.cloud.infra.dal.dataobject.file.FileDO; +import com.tashow.cloud.infra.dal.mysql.file.FileMapper; +import com.tashow.cloud.infra.framework.file.core.client.FileClient; +import com.tashow.cloud.infra.framework.file.core.client.s3.FilePresignedUrlRespDTO; +import com.tashow.cloud.infra.framework.file.core.utils.FileTypeUtils; +import jakarta.annotation.Resource; +import lombok.SneakyThrows; +import org.springframework.stereotype.Service; + +import static com.tashow.cloud.common.exception.util.ServiceExceptionUtil.exception; +import static com.tashow.cloud.infraapi.enums.ErrorCodeConstants.FILE_NOT_EXISTS; + +/** + * 文件 Service 实现类 + * + * @author 芋道源码 + */ +@Service +public class FileServiceImpl implements FileService { + + @Resource + private FileConfigService fileConfigService; + + @Resource + private FileMapper fileMapper; + + @Override + public PageResult getFilePage(FilePageReqVO pageReqVO) { + return fileMapper.selectPage(pageReqVO); + } + + @Override + @SneakyThrows + public String createFile(String name, String path, byte[] content) { + // 计算默认的 path 名 + String type = FileTypeUtils.getMineType(content, name); + if (StrUtil.isEmpty(path)) { + path = FileUtils.generatePath(content, name); + } + // 如果 name 为空,则使用 path 填充 + if (StrUtil.isEmpty(name)) { + name = path; + } + + // 上传到文件存储器 + FileClient client = fileConfigService.getMasterFileClient(); + Assert.notNull(client, "客户端(master) 不能为空"); + String url = client.upload(content, path, type); + + // 保存到数据库 + FileDO file = new FileDO(); + file.setConfigId(client.getId()); + file.setName(name); + file.setPath(path); + file.setUrl(url); + file.setType(type); + file.setSize(content.length); + fileMapper.insert(file); + return url; + } + + @Override + public Long createFile(FileCreateReqVO createReqVO) { + FileDO file = BeanUtils.toBean(createReqVO, FileDO.class); + fileMapper.insert(file); + return file.getId(); + } + + @Override + public void deleteFile(Long id) throws Exception { + // 校验存在 + FileDO file = validateFileExists(id); + + // 从文件存储器中删除 + FileClient client = fileConfigService.getFileClient(file.getConfigId()); + Assert.notNull(client, "客户端({}) 不能为空", file.getConfigId()); + client.delete(file.getPath()); + + // 删除记录 + fileMapper.deleteById(id); + } + + private FileDO validateFileExists(Long id) { + FileDO fileDO = fileMapper.selectById(id); + if (fileDO == null) { + throw exception(FILE_NOT_EXISTS); + } + return fileDO; + } + + @Override + public byte[] getFileContent(Long configId, String path) throws Exception { + FileClient client = fileConfigService.getFileClient(configId); + Assert.notNull(client, "客户端({}) 不能为空", configId); + return client.getContent(path); + } + + @Override + public FilePresignedUrlRespVO getFilePresignedUrl(String path) throws Exception { + FileClient fileClient = fileConfigService.getMasterFileClient(); + FilePresignedUrlRespDTO presignedObjectUrl = fileClient.getPresignedObjectUrl(path); + return BeanUtils.toBean(presignedObjectUrl, FilePresignedUrlRespVO.class, + object -> object.setConfigId(fileClient.getId())); + } + +} diff --git a/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/service/logger/ApiAccessLogService.java b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/service/logger/ApiAccessLogService.java new file mode 100644 index 0000000..278086c --- /dev/null +++ b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/service/logger/ApiAccessLogService.java @@ -0,0 +1,40 @@ +package com.tashow.cloud.infra.service.logger; + +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.infra.dal.dataobject.logger.ApiAccessLogDO; +import com.tashow.cloud.infraapi.api.logger.dto.ApiAccessLogCreateReqDTO; +import com.tashow.cloud.infra.controller.admin.logger.vo.apiaccesslog.ApiAccessLogPageReqVO; +import com.tashow.cloud.infra.dal.dataobject.logger.ApiAccessLogDO; +import com.tashow.cloud.infra.dal.dataobject.logger.ApiAccessLogDO; + +/** + * API 访问日志 Service 接口 + * + * @author 芋道源码 + */ +public interface ApiAccessLogService { + + /** + * 创建 API 访问日志 + * + * @param createReqDTO API 访问日志 + */ + void createApiAccessLog(ApiAccessLogCreateReqDTO createReqDTO); + + /** + * 获得 API 访问日志分页 + * + * @param pageReqVO 分页查询 + * @return API 访问日志分页 + */ + PageResult getApiAccessLogPage(ApiAccessLogPageReqVO pageReqVO); + + /** + * 清理 exceedDay 天前的访问日志 + * + * @param exceedDay 超过多少天就进行清理 + * @param deleteLimit 清理的间隔条数 + */ + Integer cleanAccessLog(Integer exceedDay, Integer deleteLimit); + +} diff --git a/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/service/logger/ApiAccessLogServiceImpl.java b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/service/logger/ApiAccessLogServiceImpl.java new file mode 100644 index 0000000..c20221b --- /dev/null +++ b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/service/logger/ApiAccessLogServiceImpl.java @@ -0,0 +1,68 @@ +package com.tashow.cloud.infra.service.logger; + +import cn.hutool.core.util.StrUtil; +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.common.util.object.BeanUtils; +import com.tashow.cloud.infra.dal.dataobject.logger.ApiAccessLogDO; +import com.tashow.cloud.infraapi.api.logger.dto.ApiAccessLogCreateReqDTO; +import com.tashow.cloud.infra.controller.admin.logger.vo.apiaccesslog.ApiAccessLogPageReqVO; +import com.tashow.cloud.infra.dal.mysql.logger.ApiAccessLogMapper; +import com.tashow.cloud.tenant.core.context.TenantContextHolder; +import com.tashow.cloud.tenant.core.util.TenantUtils; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.validation.annotation.Validated; + +import java.time.LocalDateTime; + + +/** + * API 访问日志 Service 实现类 + * + * @author 芋道源码 + */ +@Slf4j +@Service +@Validated +public class ApiAccessLogServiceImpl implements ApiAccessLogService { + + @Resource + private ApiAccessLogMapper apiAccessLogMapper; + + @Override + public void createApiAccessLog(ApiAccessLogCreateReqDTO createDTO) { + ApiAccessLogDO apiAccessLog = BeanUtils.toBean(createDTO, ApiAccessLogDO.class); + apiAccessLog.setRequestParams(StrUtil.maxLength(apiAccessLog.getRequestParams(), ApiAccessLogDO.REQUEST_PARAMS_MAX_LENGTH)); + apiAccessLog.setResultMsg(StrUtil.maxLength(apiAccessLog.getResultMsg(), ApiAccessLogDO.RESULT_MSG_MAX_LENGTH)); + if (TenantContextHolder.getTenantId() != null) { + apiAccessLogMapper.insert(apiAccessLog); + } else { + // 极端情况下,上下文中没有租户时,此时忽略租户上下文,避免插入失败! + TenantUtils.executeIgnore(() -> apiAccessLogMapper.insert(apiAccessLog)); + } + } + + @Override + public PageResult getApiAccessLogPage(ApiAccessLogPageReqVO pageReqVO) { + return apiAccessLogMapper.selectPage(pageReqVO); + } + + @Override + @SuppressWarnings("DuplicatedCode") + public Integer cleanAccessLog(Integer exceedDay, Integer deleteLimit) { + int count = 0; + LocalDateTime expireDate = LocalDateTime.now().minusDays(exceedDay); + // 循环删除,直到没有满足条件的数据 + for (int i = 0; i < Short.MAX_VALUE; i++) { + int deleteCount = apiAccessLogMapper.deleteByCreateTimeLt(expireDate, deleteLimit); + count += deleteCount; + // 达到删除预期条数,说明到底了 + if (deleteCount < deleteLimit) { + break; + } + } + return count; + } + +} diff --git a/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/service/logger/ApiErrorLogService.java b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/service/logger/ApiErrorLogService.java new file mode 100644 index 0000000..ac17012 --- /dev/null +++ b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/service/logger/ApiErrorLogService.java @@ -0,0 +1,49 @@ +package com.tashow.cloud.infra.service.logger; + +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.infra.dal.dataobject.logger.ApiErrorLogDO; +import com.tashow.cloud.infraapi.api.logger.dto.ApiErrorLogCreateReqDTO; +import com.tashow.cloud.infra.controller.admin.logger.vo.apierrorlog.ApiErrorLogPageReqVO; +import com.tashow.cloud.infra.dal.dataobject.logger.ApiErrorLogDO; +import com.tashow.cloud.infra.dal.dataobject.logger.ApiErrorLogDO; + +/** + * API 错误日志 Service 接口 + * + * @author 芋道源码 + */ +public interface ApiErrorLogService { + + /** + * 创建 API 错误日志 + * + * @param createReqDTO API 错误日志 + */ + void createApiErrorLog(ApiErrorLogCreateReqDTO createReqDTO); + + /** + * 获得 API 错误日志分页 + * + * @param pageReqVO 分页查询 + * @return API 错误日志分页 + */ + PageResult getApiErrorLogPage(ApiErrorLogPageReqVO pageReqVO); + + /** + * 更新 API 错误日志已处理 + * + * @param id API 日志编号 + * @param processStatus 处理结果 + * @param processUserId 处理人 + */ + void updateApiErrorLogProcess(Long id, Integer processStatus, Long processUserId); + + /** + * 清理 exceedDay 天前的错误日志 + * + * @param exceedDay 超过多少天就进行清理 + * @param deleteLimit 清理的间隔条数 + */ + Integer cleanErrorLog(Integer exceedDay, Integer deleteLimit); + +} diff --git a/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/service/logger/ApiErrorLogServiceImpl.java b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/service/logger/ApiErrorLogServiceImpl.java new file mode 100644 index 0000000..b24bff8 --- /dev/null +++ b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/service/logger/ApiErrorLogServiceImpl.java @@ -0,0 +1,86 @@ +package com.tashow.cloud.infra.service.logger; + +import cn.hutool.core.util.StrUtil; +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.common.util.object.BeanUtils; +import com.tashow.cloud.infra.dal.dataobject.logger.ApiErrorLogDO; +import com.tashow.cloud.infra.enums.logger.ApiErrorLogProcessStatusEnum; +import com.tashow.cloud.infraapi.api.logger.dto.ApiErrorLogCreateReqDTO; +import com.tashow.cloud.infra.controller.admin.logger.vo.apierrorlog.ApiErrorLogPageReqVO; +import com.tashow.cloud.infra.dal.mysql.logger.ApiErrorLogMapper; +import com.tashow.cloud.tenant.core.context.TenantContextHolder; +import com.tashow.cloud.tenant.core.util.TenantUtils; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.validation.annotation.Validated; + +import java.time.LocalDateTime; + +import static com.tashow.cloud.common.exception.util.ServiceExceptionUtil.exception; +import static com.tashow.cloud.infraapi.enums.ErrorCodeConstants.API_ERROR_LOG_NOT_FOUND; +import static com.tashow.cloud.infraapi.enums.ErrorCodeConstants.API_ERROR_LOG_PROCESSED; + +/** + * API 错误日志 Service 实现类 + * + * @author 芋道源码 + */ +@Service +@Validated +@Slf4j +public class ApiErrorLogServiceImpl implements ApiErrorLogService { + + @Resource + private ApiErrorLogMapper apiErrorLogMapper; + + @Override + public void createApiErrorLog(ApiErrorLogCreateReqDTO createDTO) { + ApiErrorLogDO apiErrorLog = BeanUtils.toBean(createDTO, ApiErrorLogDO.class) + .setProcessStatus(ApiErrorLogProcessStatusEnum.INIT.getStatus()); + apiErrorLog.setRequestParams(StrUtil.maxLength(apiErrorLog.getRequestParams(), ApiErrorLogDO.REQUEST_PARAMS_MAX_LENGTH)); + if (TenantContextHolder.getTenantId() != null) { + apiErrorLogMapper.insert(apiErrorLog); + } else { + // 极端情况下,上下文中没有租户时,此时忽略租户上下文,避免插入失败! + TenantUtils.executeIgnore(() -> apiErrorLogMapper.insert(apiErrorLog)); + } + } + + @Override + public PageResult getApiErrorLogPage(ApiErrorLogPageReqVO pageReqVO) { + return apiErrorLogMapper.selectPage(pageReqVO); + } + + @Override + public void updateApiErrorLogProcess(Long id, Integer processStatus, Long processUserId) { + ApiErrorLogDO errorLog = apiErrorLogMapper.selectById(id); + if (errorLog == null) { + throw exception(API_ERROR_LOG_NOT_FOUND); + } + if (!ApiErrorLogProcessStatusEnum.INIT.getStatus().equals(errorLog.getProcessStatus())) { + throw exception(API_ERROR_LOG_PROCESSED); + } + // 标记处理 + apiErrorLogMapper.updateById(ApiErrorLogDO.builder().id(id).processStatus(processStatus) + .processUserId(processUserId).processTime(LocalDateTime.now()).build()); + } + + @Override + @SuppressWarnings("DuplicatedCode") + public Integer cleanErrorLog(Integer exceedDay, Integer deleteLimit) { + int count = 0; + LocalDateTime expireDate = LocalDateTime.now().minusDays(exceedDay); + // 循环删除,直到没有满足条件的数据 + for (int i = 0; i < Short.MAX_VALUE; i++) { + int deleteCount = apiErrorLogMapper.deleteByCreateTimeLt(expireDate, deleteLimit); + count += deleteCount; + // 达到删除预期条数,说明到底了 + if (deleteCount < deleteLimit) { + break; + } + } + return count; + } + +} diff --git a/tashow-module/tashow-module-infra/src/main/resources/application-local.yaml b/tashow-module/tashow-module-infra/src/main/resources/application-local.yaml new file mode 100644 index 0000000..15b2156 --- /dev/null +++ b/tashow-module/tashow-module-infra/src/main/resources/application-local.yaml @@ -0,0 +1,17 @@ +--- #################### 注册中心 + 配置中心相关配置 #################### + +spring: + cloud: + nacos: + server-addr: 43.139.42.137:8848 # Nacos 服务器地址 + username: nacos # Nacos 账号 + password: nacos # Nacos 密码 + discovery: # 【配置中心】配置项 + namespace: 76667956-2ac2-4e05-906b-4bca4ebcc5f0 # 命名空间。这里使用 dev 开发环境 + group: DEFAULT_GROUP # 使用的 Nacos 配置分组,默认为 DEFAULT_GROUP + metadata: + version: 1.0.0 # 服务实例的版本号,可用于灰度发布 + config: # 【注册中心】配置项 + namespace: 76667956-2ac2-4e05-906b-4bca4ebcc5f0 # 命名空间。这里使用 dev 开发环境 + group: DEFAULT_GROUP # 使用的 Nacos 配置分组,默认为 DEFAULT_GROUP + diff --git a/tashow-module/tashow-module-infra/src/main/resources/application.yaml b/tashow-module/tashow-module-infra/src/main/resources/application.yaml new file mode 100644 index 0000000..e4e14f6 --- /dev/null +++ b/tashow-module/tashow-module-infra/src/main/resources/application.yaml @@ -0,0 +1,13 @@ +server: + port: 48082 +spring: + application: + name: infra-server + profiles: + active: local + config: + import: + - optional:classpath:application-${spring.profiles.active}.yaml # 加载【本地】配置 + - optional:nacos:application.yaml # 加载【Nacos】的配置 + - optional:nacos:tenant.yaml # 加载【Nacos】的配置 + - optional:nacos:${spring.application.name}-${spring.profiles.active}.yaml # 加载【Nacos】的配置 diff --git a/tashow-module/tashow-module-infra/src/main/resources/codegen/java/controller/controller.vm b/tashow-module/tashow-module-infra/src/main/resources/codegen/java/controller/controller.vm new file mode 100644 index 0000000..5aa3bae --- /dev/null +++ b/tashow-module/tashow-module-infra/src/main/resources/codegen/java/controller/controller.vm @@ -0,0 +1,233 @@ +package ${basePackage}.module.${table.moduleName}.controller.${sceneEnum.basePackage}.${table.businessName}; + +import org.springframework.web.bind.annotation.*; +import ${jakartaPackage}.annotation.Resource; +import org.springframework.validation.annotation.Validated; +#if ($sceneEnum.scene == 1)import org.springframework.security.access.prepost.PreAuthorize;#end + +import io.swagger.v3.oas.annotations.tags.Tag; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.Operation; + +import ${jakartaPackage}.validation.constraints.*; +import ${jakartaPackage}.validation.*; +import ${jakartaPackage}.servlet.http.*; +import java.util.*; +import java.io.IOException; + +import ${PageParamClassName}; +import ${PageResultClassName}; +import ${CommonResultClassName}; +import ${BeanUtils}; +import static ${CommonResultClassName}.success; + +import ${ExcelUtilsClassName}; + +import ${ApiAccessLogClassName}; +import static ${OperateTypeEnumClassName}.*; + +import ${basePackage}.module.${table.moduleName}.controller.${sceneEnum.basePackage}.${table.businessName}.vo.*; +import ${basePackage}.module.${table.moduleName}.dal.dataobject.${table.businessName}.${table.className}DO; +## 特殊:主子表专属逻辑 +#foreach ($subTable in $subTables) +import ${basePackage}.module.${subTable.moduleName}.dal.dataobject.${subTable.businessName}.${subTable.className}DO; +#end +import ${basePackage}.module.${table.moduleName}.service.${table.businessName}.${table.className}Service; + +@Tag(name = "${sceneEnum.name} - ${table.classComment}") +@RestController +##二级的 businessName 暂时不算在 HTTP 路径上,可以根据需要写 +@RequestMapping("/${table.moduleName}/${simpleClassName_strikeCase}") +@Validated +public class ${sceneEnum.prefixClass}${table.className}Controller { + + @Resource + private ${table.className}Service ${classNameVar}Service; + + @PostMapping("/create") + @Operation(summary = "创建${table.classComment}") +#if ($sceneEnum.scene == 1) + @PreAuthorize("@ss.hasPermission('${permissionPrefix}:create')") +#end + public CommonResult<${primaryColumn.javaType}> create${simpleClassName}(@Valid @RequestBody ${sceneEnum.prefixClass}${table.className}SaveReqVO createReqVO) { + return success(${classNameVar}Service.create${simpleClassName}(createReqVO)); + } + + @PutMapping("/update") + @Operation(summary = "更新${table.classComment}") +#if ($sceneEnum.scene == 1) + @PreAuthorize("@ss.hasPermission('${permissionPrefix}:update')") +#end + public CommonResult update${simpleClassName}(@Valid @RequestBody ${sceneEnum.prefixClass}${table.className}SaveReqVO updateReqVO) { + ${classNameVar}Service.update${simpleClassName}(updateReqVO); + return success(true); + } + + @DeleteMapping("/delete") + @Operation(summary = "删除${table.classComment}") + @Parameter(name = "id", description = "编号", required = true) +#if ($sceneEnum.scene == 1) + @PreAuthorize("@ss.hasPermission('${permissionPrefix}:delete')") +#end + public CommonResult delete${simpleClassName}(@RequestParam("id") ${primaryColumn.javaType} id) { + ${classNameVar}Service.delete${simpleClassName}(id); + return success(true); + } + + @GetMapping("/get") + @Operation(summary = "获得${table.classComment}") + @Parameter(name = "id", description = "编号", required = true, example = "1024") +#if ($sceneEnum.scene == 1) + @PreAuthorize("@ss.hasPermission('${permissionPrefix}:query')") +#end + public CommonResult<${sceneEnum.prefixClass}${table.className}RespVO> get${simpleClassName}(@RequestParam("id") ${primaryColumn.javaType} id) { + ${table.className}DO ${classNameVar} = ${classNameVar}Service.get${simpleClassName}(id); + return success(BeanUtils.toBean(${classNameVar}, ${sceneEnum.prefixClass}${table.className}RespVO.class)); + } + +#if ( $table.templateType != 2 ) + @GetMapping("/page") + @Operation(summary = "获得${table.classComment}分页") +#if ($sceneEnum.scene == 1) + @PreAuthorize("@ss.hasPermission('${permissionPrefix}:query')") +#end + public CommonResult> get${simpleClassName}Page(@Valid ${sceneEnum.prefixClass}${table.className}PageReqVO pageReqVO) { + PageResult<${table.className}DO> pageResult = ${classNameVar}Service.get${simpleClassName}Page(pageReqVO); + return success(BeanUtils.toBean(pageResult, ${sceneEnum.prefixClass}${table.className}RespVO.class)); + } + +## 特殊:树表专属逻辑(树不需要分页接口) +#else + @GetMapping("/list") + @Operation(summary = "获得${table.classComment}列表") +#if ($sceneEnum.scene == 1) + @PreAuthorize("@ss.hasPermission('${permissionPrefix}:query')") +#end + public CommonResult> get${simpleClassName}List(@Valid ${sceneEnum.prefixClass}${table.className}ListReqVO listReqVO) { + List<${table.className}DO> list = ${classNameVar}Service.get${simpleClassName}List(listReqVO); + return success(BeanUtils.toBean(list, ${sceneEnum.prefixClass}${table.className}RespVO.class)); + } + +#end + @GetMapping("/export-excel") + @Operation(summary = "导出${table.classComment} Excel") +#if ($sceneEnum.scene == 1) + @PreAuthorize("@ss.hasPermission('${permissionPrefix}:export')") +#end + @ApiAccessLog(operateType = EXPORT) +#if ( $table.templateType != 2 ) + public void export${simpleClassName}Excel(@Valid ${sceneEnum.prefixClass}${table.className}PageReqVO pageReqVO, + HttpServletResponse response) throws IOException { + pageReqVO.setPageSize(PageParam.PAGE_SIZE_NONE); + List<${table.className}DO> list = ${classNameVar}Service.get${simpleClassName}Page(pageReqVO).getList(); + // 导出 Excel + ExcelUtils.write(response, "${table.classComment}.xls", "数据", ${sceneEnum.prefixClass}${table.className}RespVO.class, + BeanUtils.toBean(list, ${sceneEnum.prefixClass}${table.className}RespVO.class)); + } +## 特殊:树表专属逻辑(树不需要分页接口) +#else + public void export${simpleClassName}Excel(@Valid ${sceneEnum.prefixClass}${table.className}ListReqVO listReqVO, + HttpServletResponse response) throws IOException { + List<${table.className}DO> list = ${classNameVar}Service.get${simpleClassName}List(listReqVO); + // 导出 Excel + ExcelUtils.write(response, "${table.classComment}.xls", "数据", ${table.className}RespVO.class, + BeanUtils.toBean(list, ${table.className}RespVO.class)); + } +#end + +## 特殊:主子表专属逻辑 +#foreach ($subTable in $subTables) +#set ($index = $foreach.count - 1) +#set ($subSimpleClassName = $subSimpleClassNames.get($index)) +#set ($subPrimaryColumn = $subPrimaryColumns.get($index))##当前 primary 字段 +#set ($subJoinColumn = $subJoinColumns.get($index))##当前 join 字段 +#set ($SubJoinColumnName = $subJoinColumn.javaField.substring(0,1).toUpperCase() + ${subJoinColumn.javaField.substring(1)})##首字母大写 +#set ($subSimpleClassName_strikeCase = $subSimpleClassName_strikeCases.get($index)) +#set ($subJoinColumn_strikeCase = $subJoinColumn_strikeCases.get($index)) +#set ($subClassNameVar = $subClassNameVars.get($index)) + // ==================== 子表($subTable.classComment) ==================== + +## 情况一:MASTER_ERP 时,需要分查询页子表 +#if ( $table.templateType == 11 ) + @GetMapping("/${subSimpleClassName_strikeCase}/page") + @Operation(summary = "获得${subTable.classComment}分页") + @Parameter(name = "${subJoinColumn.javaField}", description = "${subJoinColumn.columnComment}") +#if ($sceneEnum.scene == 1) + @PreAuthorize("@ss.hasPermission('${permissionPrefix}:query')") +#end + public CommonResult> get${subSimpleClassName}Page(PageParam pageReqVO, + @RequestParam("${subJoinColumn.javaField}") ${subJoinColumn.javaType} ${subJoinColumn.javaField}) { + return success(${classNameVar}Service.get${subSimpleClassName}Page(pageReqVO, ${subJoinColumn.javaField})); + } + +## 情况二:非 MASTER_ERP 时,需要列表查询子表 +#else + #if ( $subTable.subJoinMany ) + @GetMapping("/${subSimpleClassName_strikeCase}/list-by-${subJoinColumn_strikeCase}") + @Operation(summary = "获得${subTable.classComment}列表") + @Parameter(name = "${subJoinColumn.javaField}", description = "${subJoinColumn.columnComment}") +#if ($sceneEnum.scene == 1) + @PreAuthorize("@ss.hasPermission('${permissionPrefix}:query')") +#end + public CommonResult> get${subSimpleClassName}ListBy${SubJoinColumnName}(@RequestParam("${subJoinColumn.javaField}") ${subJoinColumn.javaType} ${subJoinColumn.javaField}) { + return success(${classNameVar}Service.get${subSimpleClassName}ListBy${SubJoinColumnName}(${subJoinColumn.javaField})); + } + + #else + @GetMapping("/${subSimpleClassName_strikeCase}/get-by-${subJoinColumn_strikeCase}") + @Operation(summary = "获得${subTable.classComment}") + @Parameter(name = "${subJoinColumn.javaField}", description = "${subJoinColumn.columnComment}") +#if ($sceneEnum.scene == 1) + @PreAuthorize("@ss.hasPermission('${permissionPrefix}:query')") +#end + public CommonResult<${subTable.className}DO> get${subSimpleClassName}By${SubJoinColumnName}(@RequestParam("${subJoinColumn.javaField}") ${subJoinColumn.javaType} ${subJoinColumn.javaField}) { + return success(${classNameVar}Service.get${subSimpleClassName}By${SubJoinColumnName}(${subJoinColumn.javaField})); + } + + #end +#end +## 特殊:MASTER_ERP 时,支持单个的新增、修改、删除操作 +#if ( $table.templateType == 11 ) + @PostMapping("/${subSimpleClassName_strikeCase}/create") + @Operation(summary = "创建${subTable.classComment}") +#if ($sceneEnum.scene == 1) + @PreAuthorize("@ss.hasPermission('${permissionPrefix}:create')") +#end + public CommonResult<${subPrimaryColumn.javaType}> create${subSimpleClassName}(@Valid @RequestBody ${subTable.className}DO ${subClassNameVar}) { + return success(${classNameVar}Service.create${subSimpleClassName}(${subClassNameVar})); + } + + @PutMapping("/${subSimpleClassName_strikeCase}/update") + @Operation(summary = "更新${subTable.classComment}") +#if ($sceneEnum.scene == 1) + @PreAuthorize("@ss.hasPermission('${permissionPrefix}:update')") +#end + public CommonResult update${subSimpleClassName}(@Valid @RequestBody ${subTable.className}DO ${subClassNameVar}) { + ${classNameVar}Service.update${subSimpleClassName}(${subClassNameVar}); + return success(true); + } + + @DeleteMapping("/${subSimpleClassName_strikeCase}/delete") + @Parameter(name = "id", description = "编号", required = true) + @Operation(summary = "删除${subTable.classComment}") +#if ($sceneEnum.scene == 1) + @PreAuthorize("@ss.hasPermission('${permissionPrefix}:delete')") +#end + public CommonResult delete${subSimpleClassName}(@RequestParam("id") ${subPrimaryColumn.javaType} id) { + ${classNameVar}Service.delete${subSimpleClassName}(id); + return success(true); + } + + @GetMapping("/${subSimpleClassName_strikeCase}/get") + @Operation(summary = "获得${subTable.classComment}") + @Parameter(name = "id", description = "编号", required = true) +#if ($sceneEnum.scene == 1) + @PreAuthorize("@ss.hasPermission('${permissionPrefix}:query')") +#end + public CommonResult<${subTable.className}DO> get${subSimpleClassName}(@RequestParam("id") ${subPrimaryColumn.javaType} id) { + return success(${classNameVar}Service.get${subSimpleClassName}(id)); + } + +#end +#end +} \ No newline at end of file diff --git a/tashow-module/tashow-module-infra/src/main/resources/codegen/java/controller/vo/listReqVO.vm b/tashow-module/tashow-module-infra/src/main/resources/codegen/java/controller/vo/listReqVO.vm new file mode 100644 index 0000000..46b6a25 --- /dev/null +++ b/tashow-module/tashow-module-infra/src/main/resources/codegen/java/controller/vo/listReqVO.vm @@ -0,0 +1,45 @@ +package ${basePackage}.module.${table.moduleName}.controller.${sceneEnum.basePackage}.${table.businessName}.vo; + +import lombok.*; +import java.util.*; +import io.swagger.v3.oas.annotations.media.Schema; +import ${PageParamClassName}; +#foreach ($column in $columns) +#if (${column.javaType} == "BigDecimal") +import java.math.BigDecimal; +#break +#end +#end +## 处理 LocalDateTime 字段的引入 +#foreach ($column in $columns) +#if (${column.listOperation} && ${column.javaType} == "LocalDateTime") +import java.time.LocalDateTime; +import org.springframework.format.annotation.DateTimeFormat; + +import static ${DateUtilsClassName}.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND; +#break +#end +#end +## 字段模板 +#macro(columnTpl $prefix $prefixStr) + @Schema(description = "${prefixStr}${column.columnComment}"#if ("$!column.example" != ""), example = "${column.example}"#end) + private ${column.javaType}#if ("$!prefix" != "") ${prefix}${JavaField}#else ${column.javaField}#end; +#end + +@Schema(description = "${sceneEnum.name} - ${table.classComment}列表 Request VO") +@Data +public class ${sceneEnum.prefixClass}${table.className}ListReqVO { + +#foreach ($column in $columns) +#if (${column.listOperation})##查询操作 +#if (${column.listOperationCondition} == "BETWEEN")## 情况一,Between 的时候 + @Schema(description = "${column.columnComment}"#if ("$!column.example" != ""), example = "${column.example}"#end) + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) + private ${column.javaType}[] ${column.javaField}; +#else##情况二,非 Between 的时间 + #columnTpl('', '') +#end + +#end +#end +} \ No newline at end of file diff --git a/tashow-module/tashow-module-infra/src/main/resources/codegen/java/controller/vo/pageReqVO.vm b/tashow-module/tashow-module-infra/src/main/resources/codegen/java/controller/vo/pageReqVO.vm new file mode 100644 index 0000000..003bac9 --- /dev/null +++ b/tashow-module/tashow-module-infra/src/main/resources/codegen/java/controller/vo/pageReqVO.vm @@ -0,0 +1,47 @@ +package ${basePackage}.module.${table.moduleName}.controller.${sceneEnum.basePackage}.${table.businessName}.vo; + +import lombok.*; +import java.util.*; +import io.swagger.v3.oas.annotations.media.Schema; +import ${PageParamClassName}; +#foreach ($column in $columns) +#if (${column.javaType} == "BigDecimal") +import java.math.BigDecimal; +#break +#end +#end +## 处理 LocalDateTime 字段的引入 +#foreach ($column in $columns) +#if (${column.listOperationCondition} && ${column.javaType} == "LocalDateTime") +import org.springframework.format.annotation.DateTimeFormat; +import java.time.LocalDateTime; + +import static ${DateUtilsClassName}.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND; +#break +#end +#end +## 字段模板 +#macro(columnTpl $prefix $prefixStr) + @Schema(description = "${prefixStr}${column.columnComment}"#if ("$!column.example" != ""), example = "${column.example}"#end) + private ${column.javaType}#if ("$!prefix" != "") ${prefix}${JavaField}#else ${column.javaField}#end; +#end + +@Schema(description = "${sceneEnum.name} - ${table.classComment}分页 Request VO") +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class ${sceneEnum.prefixClass}${table.className}PageReqVO extends PageParam { + +#foreach ($column in $columns) +#if (${column.listOperation})##查询操作 +#if (${column.listOperationCondition} == "BETWEEN")## 情况一,Between 的时候 + @Schema(description = "${column.columnComment}"#if ("$!column.example" != ""), example = "${column.example}"#end) + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) + private ${column.javaType}[] ${column.javaField}; +#else##情况二,非 Between 的时间 + #columnTpl('', '') +#end + +#end +#end +} \ No newline at end of file diff --git a/tashow-module/tashow-module-infra/src/main/resources/codegen/java/controller/vo/respVO.vm b/tashow-module/tashow-module-infra/src/main/resources/codegen/java/controller/vo/respVO.vm new file mode 100644 index 0000000..24c3519 --- /dev/null +++ b/tashow-module/tashow-module-infra/src/main/resources/codegen/java/controller/vo/respVO.vm @@ -0,0 +1,53 @@ +package ${basePackage}.module.${table.moduleName}.controller.${sceneEnum.basePackage}.${table.businessName}.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.*; +import java.util.*; +## 处理 BigDecimal 字段的引入 +#foreach ($column in $columns) +#if (${column.javaType} == "BigDecimal") +import java.math.BigDecimal; +#break +#end +#end +## 处理 LocalDateTime 字段的引入 +#foreach ($column in $columns) +#if (${column.listOperationResult} && ${column.javaType} == "LocalDateTime") +import org.springframework.format.annotation.DateTimeFormat; +import java.time.LocalDateTime; +#break +#end +#end +## 处理 Excel 导出 +import com.alibaba.excel.annotation.*; +#foreach ($column in $columns) +#if ("$!column.dictType" != "")## 有设置数据字典 +import ${DictFormatClassName}; +import ${DictConvertClassName}; +#break +#end +#end + +@Schema(description = "${sceneEnum.name} - ${table.classComment} Response VO") +@Data +@ExcelIgnoreUnannotated +public class ${sceneEnum.prefixClass}${table.className}RespVO { + +## 逐个处理字段 +#foreach ($column in $columns) +#if (${column.listOperationResult}) +## 1. 处理 Swagger 注解 + @Schema(description = "${column.columnComment}"#if (!${column.nullable}), requiredMode = Schema.RequiredMode.REQUIRED#end#if ("$!column.example" != ""), example = "${column.example}"#end) +## 2. 处理 Excel 导出 +#if ("$!column.dictType" != "")##处理枚举值 + @ExcelProperty(value = "${column.columnComment}", converter = DictConvert.class) + @DictFormat("${column.dictType}") // TODO 代码优化:建议设置到对应的 DictTypeConstants 枚举类中 +#else + @ExcelProperty("${column.columnComment}") +#end +## 3. 处理字段定义 + private ${column.javaType} ${column.javaField}; + +#end +#end +} \ No newline at end of file diff --git a/tashow-module/tashow-module-infra/src/main/resources/codegen/java/controller/vo/saveReqVO.vm b/tashow-module/tashow-module-infra/src/main/resources/codegen/java/controller/vo/saveReqVO.vm new file mode 100644 index 0000000..b432c75 --- /dev/null +++ b/tashow-module/tashow-module-infra/src/main/resources/codegen/java/controller/vo/saveReqVO.vm @@ -0,0 +1,64 @@ +package ${basePackage}.module.${table.moduleName}.controller.${sceneEnum.basePackage}.${table.businessName}.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.*; +import java.util.*; +import ${jakartaPackage}.validation.constraints.*; +## 处理 BigDecimal 字段的引入 +#foreach ($column in $columns) +#if (${column.javaType} == "BigDecimal") +import java.math.BigDecimal; +#break +#end +#end +## 处理 LocalDateTime 字段的引入 +#foreach ($column in $columns) +#if ((${column.createOperation} || ${column.updateOperation}) && ${column.javaType} == "LocalDateTime") +import org.springframework.format.annotation.DateTimeFormat; +import java.time.LocalDateTime; +#break +#end +#end +## 特殊:主子表专属逻辑 +#foreach ($subTable in $subTables) +import ${basePackage}.module.${subTable.moduleName}.dal.dataobject.${subTable.businessName}.${subTable.className}DO; +#end + +@Schema(description = "${sceneEnum.name} - ${table.classComment}新增/修改 Request VO") +@Data +public class ${sceneEnum.prefixClass}${table.className}SaveReqVO { + +## 逐个处理字段 +#foreach ($column in $columns) +#if (${column.createOperation} || ${column.updateOperation}) +## 1. 处理 Swagger 注解 + @Schema(description = "${column.columnComment}"#if (!${column.nullable}), requiredMode = Schema.RequiredMode.REQUIRED#end#if ("$!column.example" != ""), example = "${column.example}"#end) +## 2. 处理 Validator 参数校验 +#if (!${column.nullable} && !${column.primaryKey}) +#if (${column.javaType} == 'String') + @NotEmpty(message = "${column.columnComment}不能为空") +#else + @NotNull(message = "${column.columnComment}不能为空") +#end +#end +## 3. 处理字段定义 + private ${column.javaType} ${column.javaField}; + +#end +#end +## 特殊:主子表专属逻辑(非 ERP 模式) +#if ( $subTables && $subTables.size() > 0 && $table.templateType != 11 ) +#foreach ($subTable in $subTables) +#set ($index = $foreach.count - 1) + #if ( $subTable.subJoinMany) + @Schema(description = "${subTable.classComment}列表") + private List<${subTable.className}DO> ${subClassNameVars.get($index)}s; + + #else + @Schema(description = "${subTable.classComment}") + private ${subTable.className}DO ${subClassNameVars.get($index)}; + + #end +#end +#end +} \ No newline at end of file diff --git a/tashow-module/tashow-module-infra/src/main/resources/codegen/java/dal/do.vm b/tashow-module/tashow-module-infra/src/main/resources/codegen/java/dal/do.vm new file mode 100644 index 0000000..b019d6e --- /dev/null +++ b/tashow-module/tashow-module-infra/src/main/resources/codegen/java/dal/do.vm @@ -0,0 +1,52 @@ +package ${basePackage}.module.${table.moduleName}.dal.dataobject.${table.businessName}; + +import lombok.*; +import java.util.*; +#foreach ($column in $columns) +#if (${column.javaType} == "BigDecimal") +import java.math.BigDecimal; +#end +#if (${column.javaType} == "LocalDateTime") +import java.time.LocalDateTime; +#end +#end +import com.baomidou.mybatisplus.annotation.*; +import ${BaseDOClassName}; + +/** + * ${table.classComment} DO + * + * @author ${table.author} + */ +@TableName("${table.tableName.toLowerCase()}") +@KeySequence("${table.tableName.toLowerCase()}_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class ${table.className}DO extends BaseDO { + +## 特殊:树表专属逻辑 +#if ( $table.templateType == 2 ) + public static final Long ${treeParentColumn_javaField_underlineCase.toUpperCase()}_ROOT = 0L; + +#end +#foreach ($column in $columns) +#if (!${baseDOFields.contains(${column.javaField})})##排除 BaseDO 的字段 + /** + * ${column.columnComment} + #if ("$!column.dictType" != "")##处理枚举值 + * + * 枚举 {@link TODO ${column.dictType} 对应的类} + #end + */ + #if (${column.primaryKey})##处理主键 + @TableId#if (${column.javaType} == 'String')(type = IdType.INPUT)#end + #end + private ${column.javaType} ${column.javaField}; +#end +#end + +} \ No newline at end of file diff --git a/tashow-module/tashow-module-infra/src/main/resources/codegen/java/dal/do_sub.vm b/tashow-module/tashow-module-infra/src/main/resources/codegen/java/dal/do_sub.vm new file mode 100644 index 0000000..16be55e --- /dev/null +++ b/tashow-module/tashow-module-infra/src/main/resources/codegen/java/dal/do_sub.vm @@ -0,0 +1,49 @@ +#set ($subTable = $subTables.get($subIndex))##当前表 +#set ($subColumns = $subColumnsList.get($subIndex))##当前字段数组 +package ${basePackage}.module.${subTable.moduleName}.dal.dataobject.${subTable.businessName}; + +import lombok.*; +import java.util.*; +#foreach ($column in $subColumns) +#if (${column.javaType} == "BigDecimal") +import java.math.BigDecimal; +#end +#if (${column.javaType} == "LocalDateTime") +import java.time.LocalDateTime; +#end +#end +import com.baomidou.mybatisplus.annotation.*; +import ${BaseDOClassName}; + +/** + * ${subTable.classComment} DO + * + * @author ${subTable.author} + */ +@TableName("${subTable.tableName.toLowerCase()}") +@KeySequence("${subTable.tableName.toLowerCase()}_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class ${subTable.className}DO extends BaseDO { + +#foreach ($column in $subColumns) +#if (!${baseDOFields.contains(${column.javaField})})##排除 BaseDO 的字段 + /** + * ${column.columnComment} + #if ("$!column.dictType" != "")##处理枚举值 + * + * 枚举 {@link TODO ${column.dictType} 对应的类} + #end + */ + #if (${column.primaryKey})##处理主键 + @TableId#if (${column.javaType} == 'String')(type = IdType.INPUT)#end + #end + private ${column.javaType} ${column.javaField}; +#end +#end + +} \ No newline at end of file diff --git a/tashow-module/tashow-module-infra/src/main/resources/codegen/java/dal/mapper.vm b/tashow-module/tashow-module-infra/src/main/resources/codegen/java/dal/mapper.vm new file mode 100644 index 0000000..b98b471 --- /dev/null +++ b/tashow-module/tashow-module-infra/src/main/resources/codegen/java/dal/mapper.vm @@ -0,0 +1,82 @@ +package ${basePackage}.module.${table.moduleName}.dal.mysql.${table.businessName}; + +import java.util.*; + +import ${PageResultClassName}; +import ${QueryWrapperClassName}; +import ${BaseMapperClassName}; +import ${basePackage}.module.${table.moduleName}.dal.dataobject.${table.businessName}.${table.className}DO; +import org.apache.ibatis.annotations.Mapper; +import ${basePackage}.module.${table.moduleName}.controller.${sceneEnum.basePackage}.${table.businessName}.vo.*; + +## 字段模板 +#macro(listCondition) +#foreach ($column in $columns) +#if (${column.listOperation}) +#set ($JavaField = $column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)})##首字母大写 +#if (${column.listOperationCondition} == "=")##情况一,= 的时候 + .eqIfPresent(${table.className}DO::get${JavaField}, reqVO.get${JavaField}()) +#end +#if (${column.listOperationCondition} == "!=")##情况二,!= 的时候 + .neIfPresent(${table.className}DO::get${JavaField}, reqVO.get${JavaField}()) +#end +#if (${column.listOperationCondition} == ">")##情况三,> 的时候 + .gtIfPresent(${table.className}DO::get${JavaField}, reqVO.get${JavaField}()) +#end +#if (${column.listOperationCondition} == ">=")##情况四,>= 的时候 + .geIfPresent(${table.className}DO::get${JavaField}, reqVO.get${JavaField}()) +#end +#if (${column.listOperationCondition} == "<")##情况五,< 的时候 + .ltIfPresent(${table.className}DO::get${JavaField}, reqVO.get${JavaField}()) +#end +#if (${column.listOperationCondition} == "<=")##情况五,<= 的时候 + .leIfPresent(${table.className}DO::get${JavaField}, reqVO.get${JavaField}()) +#end +#if (${column.listOperationCondition} == "LIKE")##情况七,Like 的时候 + .likeIfPresent(${table.className}DO::get${JavaField}, reqVO.get${JavaField}()) +#end +#if (${column.listOperationCondition} == "BETWEEN")##情况八,Between 的时候 + .betweenIfPresent(${table.className}DO::get${JavaField}, reqVO.get${JavaField}()) +#end +#end +#end +#end +/** + * ${table.classComment} Mapper + * + * @author ${table.author} + */ +@Mapper +public interface ${table.className}Mapper extends BaseMapperX<${table.className}DO> { + +## 特殊:树表专属逻辑(树不需要分页接口) +#if ( $table.templateType != 2 ) + default PageResult<${table.className}DO> selectPage(${sceneEnum.prefixClass}${table.className}PageReqVO reqVO) { + return selectPage(reqVO, new LambdaQueryWrapperX<${table.className}DO>() + #listCondition() + .orderByDesc(${table.className}DO::getId));## 大多数情况下,id 倒序 + + } +#else + default List<${table.className}DO> selectList(${sceneEnum.prefixClass}${table.className}ListReqVO reqVO) { + return selectList(new LambdaQueryWrapperX<${table.className}DO>() + #listCondition() + .orderByDesc(${table.className}DO::getId));## 大多数情况下,id 倒序 + + } +#end + +## 特殊:树表专属逻辑 +#if ( $table.templateType == 2 ) +#set ($TreeParentJavaField = $treeParentColumn.javaField.substring(0,1).toUpperCase() + ${treeParentColumn.javaField.substring(1)})##首字母大写 +#set ($TreeNameJavaField = $treeNameColumn.javaField.substring(0,1).toUpperCase() + ${treeNameColumn.javaField.substring(1)})##首字母大写 + default ${table.className}DO selectBy${TreeParentJavaField}And${TreeNameJavaField}(Long ${treeParentColumn.javaField}, String ${treeNameColumn.javaField}) { + return selectOne(${table.className}DO::get${TreeParentJavaField}, ${treeParentColumn.javaField}, ${table.className}DO::get${TreeNameJavaField}, ${treeNameColumn.javaField}); + } + + default Long selectCountBy${TreeParentJavaField}(${treeParentColumn.javaType} ${treeParentColumn.javaField}) { + return selectCount(${table.className}DO::get${TreeParentJavaField}, ${treeParentColumn.javaField}); + } + +#end +} \ No newline at end of file diff --git a/tashow-module/tashow-module-infra/src/main/resources/codegen/java/dal/mapper.xml.vm b/tashow-module/tashow-module-infra/src/main/resources/codegen/java/dal/mapper.xml.vm new file mode 100644 index 0000000..290378d --- /dev/null +++ b/tashow-module/tashow-module-infra/src/main/resources/codegen/java/dal/mapper.xml.vm @@ -0,0 +1,12 @@ + + + + + + + \ No newline at end of file diff --git a/tashow-module/tashow-module-infra/src/main/resources/codegen/java/dal/mapper_sub.vm b/tashow-module/tashow-module-infra/src/main/resources/codegen/java/dal/mapper_sub.vm new file mode 100644 index 0000000..6ccaea7 --- /dev/null +++ b/tashow-module/tashow-module-infra/src/main/resources/codegen/java/dal/mapper_sub.vm @@ -0,0 +1,57 @@ +#set ($subTable = $subTables.get($subIndex))##当前表 +#set ($subColumns = $subJoinColumnsList.get($subIndex))##当前字段数组 +#set ($subJoinColumn = $subJoinColumns.get($subIndex))##当前 join 字段 +#set ($SubJoinColumnName = $subJoinColumn.javaField.substring(0,1).toUpperCase() + ${subJoinColumn.javaField.substring(1)})##首字母大写 +package ${basePackage}.module.${subTable.moduleName}.dal.mysql.${subTable.businessName}; + +import java.util.*; + +import ${PageResultClassName}; +import ${PageParamClassName}; +import ${QueryWrapperClassName}; +import ${BaseMapperClassName}; +import ${basePackage}.module.${subTable.moduleName}.dal.dataobject.${subTable.businessName}.${subTable.className}DO; +import org.apache.ibatis.annotations.Mapper; + +/** + * ${subTable.classComment} Mapper + * + * @author ${subTable.author} + */ +@Mapper +public interface ${subTable.className}Mapper extends BaseMapperX<${subTable.className}DO> { + +## 情况一:MASTER_ERP 时,需要分查询页子表 +#if ( $table.templateType == 11 ) + default PageResult<${subTable.className}DO> selectPage(PageParam reqVO, ${subJoinColumn.javaType} ${subJoinColumn.javaField}) { + return selectPage(reqVO, new LambdaQueryWrapperX<${subTable.className}DO>() + .eq(${subTable.className}DO::get${SubJoinColumnName}, ${subJoinColumn.javaField}) + .orderByDesc(${subTable.className}DO::getId));## 大多数情况下,id 倒序 + + } +## 主表与子表是一对一时 + #if (!$subTable.subJoinMany) + default ${subTable.className}DO selectBy${SubJoinColumnName}(${subJoinColumn.javaType} ${subJoinColumn.javaField}) { + return selectOne(${subTable.className}DO::get${SubJoinColumnName}, ${subJoinColumn.javaField}); + } + #end + +## 情况二:非 MASTER_ERP 时,需要列表查询子表 +#else + #if ( $subTable.subJoinMany) + default List<${subTable.className}DO> selectListBy${SubJoinColumnName}(${subJoinColumn.javaType} ${subJoinColumn.javaField}) { + return selectList(${subTable.className}DO::get${SubJoinColumnName}, ${subJoinColumn.javaField}); + } + + #else + default ${subTable.className}DO selectBy${SubJoinColumnName}(${subJoinColumn.javaType} ${subJoinColumn.javaField}) { + return selectOne(${subTable.className}DO::get${SubJoinColumnName}, ${subJoinColumn.javaField}); + } + + #end + #end + default int deleteBy${SubJoinColumnName}(${subJoinColumn.javaType} ${subJoinColumn.javaField}) { + return delete(${subTable.className}DO::get${SubJoinColumnName}, ${subJoinColumn.javaField}); + } + +} diff --git a/tashow-module/tashow-module-infra/src/main/resources/codegen/java/enums/errorcode.vm b/tashow-module/tashow-module-infra/src/main/resources/codegen/java/enums/errorcode.vm new file mode 100644 index 0000000..b7e21e6 --- /dev/null +++ b/tashow-module/tashow-module-infra/src/main/resources/codegen/java/enums/errorcode.vm @@ -0,0 +1,22 @@ +// TODO 待办:请将下面的错误码复制到 yudao-module-${table.moduleName}-api 模块的 ErrorCodeConstants 类中。注意,请给“TODO 补充编号”设置一个错误码编号!!! +// ========== ${table.classComment} TODO 补充编号 ========== +ErrorCode ${simpleClassName_underlineCase.toUpperCase()}_NOT_EXISTS = new ErrorCode(TODO 补充编号, "${table.classComment}不存在"); +## 特殊:树表专属逻辑 +#if ( $table.templateType == 2 ) +ErrorCode ${simpleClassName_underlineCase.toUpperCase()}_EXITS_CHILDREN = new ErrorCode(TODO 补充编号, "存在存在子${table.classComment},无法删除"); +ErrorCode ${simpleClassName_underlineCase.toUpperCase()}_PARENT_NOT_EXITS = new ErrorCode(TODO 补充编号,"父级${table.classComment}不存在"); +ErrorCode ${simpleClassName_underlineCase.toUpperCase()}_PARENT_ERROR = new ErrorCode(TODO 补充编号, "不能设置自己为父${table.classComment}"); +ErrorCode ${simpleClassName_underlineCase.toUpperCase()}_${treeNameColumn_javaField_underlineCase.toUpperCase()}_DUPLICATE = new ErrorCode(TODO 补充编号, "已经存在该${treeNameColumn.columnComment}的${table.classComment}"); +ErrorCode ${simpleClassName_underlineCase.toUpperCase()}_PARENT_IS_CHILD = new ErrorCode(TODO 补充编号, "不能设置自己的子${table.className}为父${table.className}"); +#end +## 特殊:主子表专属逻辑 +#if ( $table.templateType == 11 )## 特殊:ERP 情况 +#foreach ($subTable in $subTables) +#set ($index = $foreach.count - 1) +#set ($simpleClassNameUnderlineCase = $simpleClassNameUnderlineCases.get($index)) +ErrorCode ${simpleClassNameUnderlineCase.toUpperCase()}_NOT_EXISTS = new ErrorCode(TODO 补充编号, "${subTable.classComment}不存在"); +#if ( !$subTable.subJoinMany ) +ErrorCode ${simpleClassNameUnderlineCase.toUpperCase()}_EXISTS = new ErrorCode(TODO 补充编号, "${subTable.classComment}已存在"); +#end +#end +#end \ No newline at end of file diff --git a/tashow-module/tashow-module-infra/src/main/resources/codegen/java/service/service.vm b/tashow-module/tashow-module-infra/src/main/resources/codegen/java/service/service.vm new file mode 100644 index 0000000..c4ee4f0 --- /dev/null +++ b/tashow-module/tashow-module-infra/src/main/resources/codegen/java/service/service.vm @@ -0,0 +1,147 @@ +package ${basePackage}.module.${table.moduleName}.service.${table.businessName}; + +import java.util.*; +import ${jakartaPackage}.validation.*; +import ${basePackage}.module.${table.moduleName}.controller.${sceneEnum.basePackage}.${table.businessName}.vo.*; +import ${basePackage}.module.${table.moduleName}.dal.dataobject.${table.businessName}.${table.className}DO; +## 特殊:主子表专属逻辑 +#foreach ($subTable in $subTables) +import ${basePackage}.module.${subTable.moduleName}.dal.dataobject.${subTable.businessName}.${subTable.className}DO; +#end +import ${PageResultClassName}; +import ${PageParamClassName}; + +/** + * ${table.classComment} Service 接口 + * + * @author ${table.author} + */ +public interface ${table.className}Service { + + /** + * 创建${table.classComment} + * + * @param createReqVO 创建信息 + * @return 编号 + */ + ${primaryColumn.javaType} create${simpleClassName}(@Valid ${sceneEnum.prefixClass}${table.className}SaveReqVO createReqVO); + + /** + * 更新${table.classComment} + * + * @param updateReqVO 更新信息 + */ + void update${simpleClassName}(@Valid ${sceneEnum.prefixClass}${table.className}SaveReqVO updateReqVO); + + /** + * 删除${table.classComment} + * + * @param id 编号 + */ + void delete${simpleClassName}(${primaryColumn.javaType} id); + + /** + * 获得${table.classComment} + * + * @param id 编号 + * @return ${table.classComment} + */ + ${table.className}DO get${simpleClassName}(${primaryColumn.javaType} id); + +## 特殊:树表专属逻辑(树不需要分页接口) +#if ( $table.templateType != 2 ) + /** + * 获得${table.classComment}分页 + * + * @param pageReqVO 分页查询 + * @return ${table.classComment}分页 + */ + PageResult<${table.className}DO> get${simpleClassName}Page(${sceneEnum.prefixClass}${table.className}PageReqVO pageReqVO); +#else + /** + * 获得${table.classComment}列表 + * + * @param listReqVO 查询条件 + * @return ${table.classComment}列表 + */ + List<${table.className}DO> get${simpleClassName}List(${sceneEnum.prefixClass}${table.className}ListReqVO listReqVO); +#end + +## 特殊:主子表专属逻辑 +#foreach ($subTable in $subTables) +#set ($index = $foreach.count - 1) +#set ($subSimpleClassName = $subSimpleClassNames.get($index)) +#set ($subPrimaryColumn = $subPrimaryColumns.get($index))##当前 primary 字段 +#set ($subJoinColumn = $subJoinColumns.get($index))##当前 join 字段 +#set ($SubJoinColumnName = $subJoinColumn.javaField.substring(0,1).toUpperCase() + ${subJoinColumn.javaField.substring(1)})##首字母大写 +#set ($subClassNameVar = $subClassNameVars.get($index)) + // ==================== 子表($subTable.classComment) ==================== + +## 情况一:MASTER_ERP 时,需要分查询页子表 +#if ( $table.templateType == 11 ) + /** + * 获得${subTable.classComment}分页 + * + * @param pageReqVO 分页查询 + * @param ${subJoinColumn.javaField} ${subJoinColumn.columnComment} + * @return ${subTable.classComment}分页 + */ + PageResult<${subTable.className}DO> get${subSimpleClassName}Page(PageParam pageReqVO, ${subJoinColumn.javaType} ${subJoinColumn.javaField}); + +## 情况二:非 MASTER_ERP 时,需要列表查询子表 +#else + #if ( $subTable.subJoinMany ) + /** + * 获得${subTable.classComment}列表 + * + * @param ${subJoinColumn.javaField} ${subJoinColumn.columnComment} + * @return ${subTable.classComment}列表 + */ + List<${subTable.className}DO> get${subSimpleClassName}ListBy${SubJoinColumnName}(${subJoinColumn.javaType} ${subJoinColumn.javaField}); + + #else + /** + * 获得${subTable.classComment} + * + * @param ${subJoinColumn.javaField} ${subJoinColumn.columnComment} + * @return ${subTable.classComment} + */ + ${subTable.className}DO get${subSimpleClassName}By${SubJoinColumnName}(${subJoinColumn.javaType} ${subJoinColumn.javaField}); + + #end +#end +## 特殊:MASTER_ERP 时,支持单个的新增、修改、删除操作 +#if ( $table.templateType == 11 ) + /** + * 创建${subTable.classComment} + * + * @param ${subClassNameVar} 创建信息 + * @return 编号 + */ + ${subPrimaryColumn.javaType} create${subSimpleClassName}(@Valid ${subTable.className}DO ${subClassNameVar}); + + /** + * 更新${subTable.classComment} + * + * @param ${subClassNameVar} 更新信息 + */ + void update${subSimpleClassName}(@Valid ${subTable.className}DO ${subClassNameVar}); + + /** + * 删除${subTable.classComment} + * + * @param id 编号 + */ + void delete${subSimpleClassName}(${subPrimaryColumn.javaType} id); + + /** + * 获得${subTable.classComment} + * + * @param id 编号 + * @return ${subTable.classComment} + */ + ${subTable.className}DO get${subSimpleClassName}(${subPrimaryColumn.javaType} id); + +#end +#end +} \ No newline at end of file diff --git a/tashow-module/tashow-module-infra/src/main/resources/codegen/java/service/serviceImpl.vm b/tashow-module/tashow-module-infra/src/main/resources/codegen/java/service/serviceImpl.vm new file mode 100644 index 0000000..80bc71b --- /dev/null +++ b/tashow-module/tashow-module-infra/src/main/resources/codegen/java/service/serviceImpl.vm @@ -0,0 +1,351 @@ +package ${basePackage}.module.${table.moduleName}.service.${table.businessName}; + +import org.springframework.stereotype.Service; +import ${jakartaPackage}.annotation.Resource; +import org.springframework.validation.annotation.Validated; +import org.springframework.transaction.annotation.Transactional; + +import java.util.*; +import ${basePackage}.module.${table.moduleName}.controller.${sceneEnum.basePackage}.${table.businessName}.vo.*; +import ${basePackage}.module.${table.moduleName}.dal.dataobject.${table.businessName}.${table.className}DO; +## 特殊:主子表专属逻辑 +#foreach ($subTable in $subTables) +import ${basePackage}.module.${subTable.moduleName}.dal.dataobject.${subTable.businessName}.${subTable.className}DO; +#end +import ${PageResultClassName}; +import ${PageParamClassName}; +import ${BeanUtils}; + +import ${basePackage}.module.${table.moduleName}.dal.mysql.${table.businessName}.${table.className}Mapper; +## 特殊:主子表专属逻辑 +#foreach ($subTable in $subTables) +#set ($index = $foreach.count - 1) +import ${basePackage}.module.${subTable.moduleName}.dal.mysql.${subTable.businessName}.${subTable.className}Mapper; +#end + +import static ${ServiceExceptionUtilClassName}.exception; +import static ${basePackage}.module.${table.moduleName}.enums.ErrorCodeConstants.*; + +/** + * ${table.classComment} Service 实现类 + * + * @author ${table.author} + */ +@Service +@Validated +public class ${table.className}ServiceImpl implements ${table.className}Service { + + @Resource + private ${table.className}Mapper ${classNameVar}Mapper; +## 特殊:主子表专属逻辑 +#foreach ($subTable in $subTables) +#set ($index = $foreach.count - 1) + @Resource + private ${subTable.className}Mapper ${subClassNameVars.get($index)}Mapper; +#end + + @Override +## 特殊:主子表专属逻辑(非 ERP 模式) +#if ( $subTables && $subTables.size() > 0 && $table.templateType != 11 ) + @Transactional(rollbackFor = Exception.class) +#end + public ${primaryColumn.javaType} create${simpleClassName}(${sceneEnum.prefixClass}${table.className}SaveReqVO createReqVO) { +## 特殊:树表专属逻辑 +#if ( $table.templateType == 2 ) +#set ($TreeParentJavaField = $treeParentColumn.javaField.substring(0,1).toUpperCase() + ${treeParentColumn.javaField.substring(1)})##首字母大写 +#set ($TreeNameJavaField = $treeNameColumn.javaField.substring(0,1).toUpperCase() + ${treeNameColumn.javaField.substring(1)})##首字母大写 + // 校验${treeParentColumn.columnComment}的有效性 + validateParent${simpleClassName}(null, createReqVO.get${TreeParentJavaField}()); + // 校验${treeNameColumn.columnComment}的唯一性 + validate${simpleClassName}${TreeNameJavaField}Unique(null, createReqVO.get${TreeParentJavaField}(), createReqVO.get${TreeNameJavaField}()); + +#end + // 插入 + ${table.className}DO ${classNameVar} = BeanUtils.toBean(createReqVO, ${table.className}DO.class); + ${classNameVar}Mapper.insert(${classNameVar}); +## 特殊:主子表专属逻辑(非 ERP 模式) +#if ( $subTables && $subTables.size() > 0 && $table.templateType != 11 ) + + // 插入子表 +#foreach ($subTable in $subTables) +#set ($index = $foreach.count - 1) +#set ($subSimpleClassName = $subSimpleClassNames.get($index)) +#set ($subJoinColumn = $subJoinColumns.get($index))##当前 join 字段 +#set ($SubJoinColumnName = $subJoinColumn.javaField.substring(0,1).toUpperCase() + ${subJoinColumn.javaField.substring(1)})##首字母大写 + #if ( $subTable.subJoinMany) + create${subSimpleClassName}List(${classNameVar}.getId(), createReqVO.get${subSimpleClassNames.get($index)}s()); + #else + create${subSimpleClassName}(${classNameVar}.getId(), createReqVO.get${subSimpleClassNames.get($index)}()); + #end +#end +#end + // 返回 + return ${classNameVar}.getId(); + } + + @Override +## 特殊:主子表专属逻辑(非 ERP 模式) +#if ( $subTables && $subTables.size() > 0 && $table.templateType != 11 ) + @Transactional(rollbackFor = Exception.class) +#end + public void update${simpleClassName}(${sceneEnum.prefixClass}${table.className}SaveReqVO updateReqVO) { + // 校验存在 + validate${simpleClassName}Exists(updateReqVO.getId()); +## 特殊:树表专属逻辑 +#if ( $table.templateType == 2 ) +#set ($TreeParentJavaField = $treeParentColumn.javaField.substring(0,1).toUpperCase() + ${treeParentColumn.javaField.substring(1)})##首字母大写 +#set ($TreeNameJavaField = $treeNameColumn.javaField.substring(0,1).toUpperCase() + ${treeNameColumn.javaField.substring(1)})##首字母大写 + // 校验${treeParentColumn.columnComment}的有效性 + validateParent${simpleClassName}(updateReqVO.getId(), updateReqVO.get${TreeParentJavaField}()); + // 校验${treeNameColumn.columnComment}的唯一性 + validate${simpleClassName}${TreeNameJavaField}Unique(updateReqVO.getId(), updateReqVO.get${TreeParentJavaField}(), updateReqVO.get${TreeNameJavaField}()); + +#end + // 更新 + ${table.className}DO updateObj = BeanUtils.toBean(updateReqVO, ${table.className}DO.class); + ${classNameVar}Mapper.updateById(updateObj); +## 特殊:主子表专属逻辑(非 ERP 模式) +#if ( $subTables && $subTables.size() > 0 && $table.templateType != 11) + + // 更新子表 +#foreach ($subTable in $subTables) +#set ($index = $foreach.count - 1) +#set ($subSimpleClassName = $subSimpleClassNames.get($index)) +#set ($subJoinColumn = $subJoinColumns.get($index))##当前 join 字段 +#set ($SubJoinColumnName = $subJoinColumn.javaField.substring(0,1).toUpperCase() + ${subJoinColumn.javaField.substring(1)})##首字母大写 + #if ( $subTable.subJoinMany) + update${subSimpleClassName}List(updateReqVO.getId(), updateReqVO.get${subSimpleClassNames.get($index)}s()); + #else + update${subSimpleClassName}(updateReqVO.getId(), updateReqVO.get${subSimpleClassNames.get($index)}()); + #end +#end +#end + } + + @Override +## 特殊:主子表专属逻辑 +#if ( $subTables && $subTables.size() > 0) + @Transactional(rollbackFor = Exception.class) +#end + public void delete${simpleClassName}(${primaryColumn.javaType} id) { + // 校验存在 + validate${simpleClassName}Exists(id); +## 特殊:树表专属逻辑 +#if ( $table.templateType == 2 ) +#set ($ParentJavaField = $treeParentColumn.javaField.substring(0,1).toUpperCase() + ${treeParentColumn.javaField.substring(1)})##首字母大写 + // 校验是否有子${table.classComment} + if (${classNameVar}Mapper.selectCountBy${ParentJavaField}(id) > 0) { + throw exception(${simpleClassName_underlineCase.toUpperCase()}_EXITS_CHILDREN); + } +#end + // 删除 + ${classNameVar}Mapper.deleteById(id); +## 特殊:主子表专属逻辑 +#if ( $subTables && $subTables.size() > 0) + + // 删除子表 +#foreach ($subTable in $subTables) +#set ($index = $foreach.count - 1) +#set ($subSimpleClassName = $subSimpleClassNames.get($index)) +#set ($subJoinColumn = $subJoinColumns.get($index))##当前 join 字段 +#set ($SubJoinColumnName = $subJoinColumn.javaField.substring(0,1).toUpperCase() + ${subJoinColumn.javaField.substring(1)})##首字母大写 + delete${subSimpleClassName}By${SubJoinColumnName}(id); +#end +#end + } + + private void validate${simpleClassName}Exists(${primaryColumn.javaType} id) { + if (${classNameVar}Mapper.selectById(id) == null) { + throw exception(${simpleClassName_underlineCase.toUpperCase()}_NOT_EXISTS); + } + } + +## 特殊:树表专属逻辑 +#if ( $table.templateType == 2 ) +#set ($TreeParentJavaField = $treeParentColumn.javaField.substring(0,1).toUpperCase() + ${treeParentColumn.javaField.substring(1)})##首字母大写 +#set ($TreeNameJavaField = $treeNameColumn.javaField.substring(0,1).toUpperCase() + ${treeNameColumn.javaField.substring(1)})##首字母大写 + private void validateParent${simpleClassName}(Long id, Long ${treeParentColumn.javaField}) { + if (${treeParentColumn.javaField} == null || ${simpleClassName}DO.${treeParentColumn_javaField_underlineCase.toUpperCase()}_ROOT.equals(${treeParentColumn.javaField})) { + return; + } + // 1. 不能设置自己为父${table.classComment} + if (Objects.equals(id, ${treeParentColumn.javaField})) { + throw exception(${simpleClassName_underlineCase.toUpperCase()}_PARENT_ERROR); + } + // 2. 父${table.classComment}不存在 + ${simpleClassName}DO parent${simpleClassName} = ${classNameVar}Mapper.selectById(${treeParentColumn.javaField}); + if (parent${simpleClassName} == null) { + throw exception(${simpleClassName_underlineCase.toUpperCase()}_PARENT_NOT_EXITS); + } + // 3. 递归校验父${table.classComment},如果父${table.classComment}是自己的子${table.classComment},则报错,避免形成环路 + if (id == null) { // id 为空,说明新增,不需要考虑环路 + return; + } + for (int i = 0; i < Short.MAX_VALUE; i++) { + // 3.1 校验环路 + ${treeParentColumn.javaField} = parent${simpleClassName}.get${TreeParentJavaField}(); + if (Objects.equals(id, ${treeParentColumn.javaField})) { + throw exception(${simpleClassName_underlineCase.toUpperCase()}_PARENT_IS_CHILD); + } + // 3.2 继续递归下一级父${table.classComment} + if (${treeParentColumn.javaField} == null || ${simpleClassName}DO.${treeParentColumn_javaField_underlineCase.toUpperCase()}_ROOT.equals(${treeParentColumn.javaField})) { + break; + } + parent${simpleClassName} = ${classNameVar}Mapper.selectById(${treeParentColumn.javaField}); + if (parent${simpleClassName} == null) { + break; + } + } + } + + private void validate${simpleClassName}${TreeNameJavaField}Unique(Long id, Long ${treeParentColumn.javaField}, String ${treeNameColumn.javaField}) { + ${simpleClassName}DO ${classNameVar} = ${classNameVar}Mapper.selectBy${TreeParentJavaField}And${TreeNameJavaField}(${treeParentColumn.javaField}, ${treeNameColumn.javaField}); + if (${classNameVar} == null) { + return; + } + // 如果 id 为空,说明不用比较是否为相同 id 的${table.classComment} + if (id == null) { + throw exception(${simpleClassName_underlineCase.toUpperCase()}_${treeNameColumn_javaField_underlineCase.toUpperCase()}_DUPLICATE); + } + if (!Objects.equals(${classNameVar}.getId(), id)) { + throw exception(${simpleClassName_underlineCase.toUpperCase()}_${treeNameColumn_javaField_underlineCase.toUpperCase()}_DUPLICATE); + } + } + +#end + @Override + public ${table.className}DO get${simpleClassName}(${primaryColumn.javaType} id) { + return ${classNameVar}Mapper.selectById(id); + } + +## 特殊:树表专属逻辑(树不需要分页接口) +#if ( $table.templateType != 2 ) + @Override + public PageResult<${table.className}DO> get${simpleClassName}Page(${sceneEnum.prefixClass}${table.className}PageReqVO pageReqVO) { + return ${classNameVar}Mapper.selectPage(pageReqVO); + } +#else + @Override + public List<${table.className}DO> get${simpleClassName}List(${sceneEnum.prefixClass}${table.className}ListReqVO listReqVO) { + return ${classNameVar}Mapper.selectList(listReqVO); + } +#end + +## 特殊:主子表专属逻辑 +#foreach ($subTable in $subTables) +#set ($index = $foreach.count - 1) +#set ($subSimpleClassName = $subSimpleClassNames.get($index)) +#set ($simpleClassNameUnderlineCase = $simpleClassNameUnderlineCases.get($index)) +#set ($subPrimaryColumn = $subPrimaryColumns.get($index))##当前 primary 字段 +#set ($subJoinColumn = $subJoinColumns.get($index))##当前 join 字段 +#set ($SubJoinColumnName = $subJoinColumn.javaField.substring(0,1).toUpperCase() + ${subJoinColumn.javaField.substring(1)})##首字母大写 +#set ($subClassNameVar = $subClassNameVars.get($index)) + // ==================== 子表($subTable.classComment) ==================== + +## 情况一:MASTER_ERP 时,需要分查询页子表 +#if ( $table.templateType == 11 ) + @Override + public PageResult<${subTable.className}DO> get${subSimpleClassName}Page(PageParam pageReqVO, ${subJoinColumn.javaType} ${subJoinColumn.javaField}) { + return ${subClassNameVars.get($index)}Mapper.selectPage(pageReqVO, ${subJoinColumn.javaField}); + } + +## 情况二:非 MASTER_ERP 时,需要列表查询子表 +#else + #if ( $subTable.subJoinMany ) + @Override + public List<${subTable.className}DO> get${subSimpleClassName}ListBy${SubJoinColumnName}(${subJoinColumn.javaType} ${subJoinColumn.javaField}) { + return ${subClassNameVars.get($index)}Mapper.selectListBy${SubJoinColumnName}(${subJoinColumn.javaField}); + } + + #else + @Override + public ${subTable.className}DO get${subSimpleClassName}By${SubJoinColumnName}(${subJoinColumn.javaType} ${subJoinColumn.javaField}) { + return ${subClassNameVars.get($index)}Mapper.selectBy${SubJoinColumnName}(${subJoinColumn.javaField}); + } + + #end +#end +## 情况一:MASTER_ERP 时,支持单个的新增、修改、删除操作 +#if ( $table.templateType == 11 ) + @Override + public ${subPrimaryColumn.javaType} create${subSimpleClassName}(${subTable.className}DO ${subClassNameVar}) { +## 特殊:一对一时,需要保证只有一条,不能重复插入 +#if ( !$subTable.subJoinMany) + // 校验是否已经存在 + if (${subClassNameVars.get($index)}Mapper.selectBy${SubJoinColumnName}(${subClassNameVar}.get${SubJoinColumnName}()) != null) { + throw exception(${simpleClassNameUnderlineCase.toUpperCase()}_EXISTS); + } + // 插入 +#end + ${subClassNameVars.get($index)}Mapper.insert(${subClassNameVar}); + return ${subClassNameVar}.getId(); + } + + @Override + public void update${subSimpleClassName}(${subTable.className}DO ${subClassNameVar}) { + // 校验存在 + validate${subSimpleClassName}Exists(${subClassNameVar}.getId()); + // 更新 + ${subClassNameVar}.setUpdater(null).setUpdateTime(null); // 解决更新情况下:updateTime 不更新 + ${subClassNameVars.get($index)}Mapper.updateById(${subClassNameVar}); + } + + @Override + public void delete${subSimpleClassName}(${subPrimaryColumn.javaType} id) { + // 校验存在 + validate${subSimpleClassName}Exists(id); + // 删除 + ${subClassNameVars.get($index)}Mapper.deleteById(id); + } + + @Override + public ${subTable.className}DO get${subSimpleClassName}(${subPrimaryColumn.javaType} id) { + return ${subClassNameVars.get($index)}Mapper.selectById(id); + } + + private void validate${subSimpleClassName}Exists(${subPrimaryColumn.javaType} id) { + if (${subClassNameVar}Mapper.selectById(id) == null) { + throw exception(${simpleClassNameUnderlineCase.toUpperCase()}_NOT_EXISTS); + } + } + +## 情况二:非 MASTER_ERP 时,支持批量的新增、修改操作 +#else + #if ( $subTable.subJoinMany) + private void create${subSimpleClassName}List(${primaryColumn.javaType} ${subJoinColumn.javaField}, List<${subTable.className}DO> list) { + list.forEach(o -> o.set$SubJoinColumnName(${subJoinColumn.javaField})); + ${subClassNameVars.get($index)}Mapper.insertBatch(list); + } + + private void update${subSimpleClassName}List(${primaryColumn.javaType} ${subJoinColumn.javaField}, List<${subTable.className}DO> list) { + delete${subSimpleClassName}By${SubJoinColumnName}(${subJoinColumn.javaField}); + list.forEach(o -> o.setId(null).setUpdater(null).setUpdateTime(null)); // 解决更新情况下:1)id 冲突;2)updateTime 不更新 + create${subSimpleClassName}List(${subJoinColumn.javaField}, list); + } + + #else + private void create${subSimpleClassName}(${primaryColumn.javaType} ${subJoinColumn.javaField}, ${subTable.className}DO ${subClassNameVar}) { + if (${subClassNameVar} == null) { + return; + } + ${subClassNameVar}.set$SubJoinColumnName(${subJoinColumn.javaField}); + ${subClassNameVars.get($index)}Mapper.insert(${subClassNameVar}); + } + + private void update${subSimpleClassName}(${primaryColumn.javaType} ${subJoinColumn.javaField}, ${subTable.className}DO ${subClassNameVar}) { + if (${subClassNameVar} == null) { + return; + } + ${subClassNameVar}.set$SubJoinColumnName(${subJoinColumn.javaField}); + ${subClassNameVar}.setUpdater(null).setUpdateTime(null); // 解决更新情况下:updateTime 不更新 + ${subClassNameVars.get($index)}Mapper.insertOrUpdate(${subClassNameVar}); + } + + #end +#end + private void delete${subSimpleClassName}By${SubJoinColumnName}(${primaryColumn.javaType} ${subJoinColumn.javaField}) { + ${subClassNameVars.get($index)}Mapper.deleteBy${SubJoinColumnName}(${subJoinColumn.javaField}); + } + +#end +} \ No newline at end of file diff --git a/tashow-module/tashow-module-infra/src/main/resources/codegen/java/test/serviceTest.vm b/tashow-module/tashow-module-infra/src/main/resources/codegen/java/test/serviceTest.vm new file mode 100644 index 0000000..bfd4600 --- /dev/null +++ b/tashow-module/tashow-module-infra/src/main/resources/codegen/java/test/serviceTest.vm @@ -0,0 +1,168 @@ +package ${basePackage}.module.${table.moduleName}.service.${table.businessName}; + +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.mock.mockito.MockBean; + +import ${jakartaPackage}.annotation.Resource; + +import ${baseFrameworkPackage}.test.core.ut.BaseDbUnitTest; + +import ${basePackage}.module.${table.moduleName}.controller.${sceneEnum.basePackage}.${table.businessName}.vo.*; +import ${basePackage}.module.${table.moduleName}.dal.dataobject.${table.businessName}.${table.className}DO; +import ${basePackage}.module.${table.moduleName}.dal.mysql.${table.businessName}.${table.className}Mapper; +import ${PageResultClassName}; + +import ${jakartaPackage}.annotation.Resource; +import org.springframework.context.annotation.Import; +import java.util.*; +import java.time.LocalDateTime; + +import static cn.hutool.core.util.RandomUtil.*; +import static ${basePackage}.module.${table.moduleName}.enums.ErrorCodeConstants.*; +import static ${baseFrameworkPackage}.test.core.util.AssertUtils.*; +import static ${baseFrameworkPackage}.test.core.util.RandomUtils.*; +import static ${LocalDateTimeUtilsClassName}.*; +import static ${ObjectUtilsClassName}.*; +import static ${DateUtilsClassName}.*; +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; + +## 字段模板 +#macro(getPageCondition $VO) + // mock 数据 + ${table.className}DO db${simpleClassName} = randomPojo(${table.className}DO.class, o -> { // 等会查询到 + #foreach ($column in $columns) + #if (${column.listOperation}) + #set ($JavaField = $column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)})##首字母大写 + o.set$JavaField(null); + #end + #end + }); + ${classNameVar}Mapper.insert(db${simpleClassName}); + #foreach ($column in $columns) + #if (${column.listOperation}) + #set ($JavaField = $column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)})##首字母大写 + // 测试 ${column.javaField} 不匹配 + ${classNameVar}Mapper.insert(cloneIgnoreId(db${simpleClassName}, o -> o.set$JavaField(null))); + #end + #end + // 准备参数 + ${sceneEnum.prefixClass}${table.className}${VO} reqVO = new ${sceneEnum.prefixClass}${table.className}${VO}(); + #foreach ($column in $columns) + #if (${column.listOperation}) + #set ($JavaField = $column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)})##首字母大写 + #if (${column.listOperationCondition} == "BETWEEN")## BETWEEN 的情况 + reqVO.set${JavaField}(buildBetweenTime(2023, 2, 1, 2023, 2, 28)); + #else + reqVO.set$JavaField(null); + #end + #end + #end +#end +/** + * {@link ${table.className}ServiceImpl} 的单元测试类 + * + * @author ${table.author} + */ +@Import(${table.className}ServiceImpl.class) +public class ${table.className}ServiceImplTest extends BaseDbUnitTest { + + @Resource + private ${table.className}ServiceImpl ${classNameVar}Service; + + @Resource + private ${table.className}Mapper ${classNameVar}Mapper; + + @Test + public void testCreate${simpleClassName}_success() { + // 准备参数 + ${sceneEnum.prefixClass}${table.className}SaveReqVO createReqVO = randomPojo(${sceneEnum.prefixClass}${table.className}SaveReqVO.class).setId(null); + + // 调用 + ${primaryColumn.javaType} ${classNameVar}Id = ${classNameVar}Service.create${simpleClassName}(createReqVO); + // 断言 + assertNotNull(${classNameVar}Id); + // 校验记录的属性是否正确 + ${table.className}DO ${classNameVar} = ${classNameVar}Mapper.selectById(${classNameVar}Id); + assertPojoEquals(createReqVO, ${classNameVar}, "id"); + } + + @Test + public void testUpdate${simpleClassName}_success() { + // mock 数据 + ${table.className}DO db${simpleClassName} = randomPojo(${table.className}DO.class); + ${classNameVar}Mapper.insert(db${simpleClassName});// @Sql: 先插入出一条存在的数据 + // 准备参数 + ${sceneEnum.prefixClass}${table.className}SaveReqVO updateReqVO = randomPojo(${sceneEnum.prefixClass}${table.className}SaveReqVO.class, o -> { + o.setId(db${simpleClassName}.getId()); // 设置更新的 ID + }); + + // 调用 + ${classNameVar}Service.update${simpleClassName}(updateReqVO); + // 校验是否更新正确 + ${table.className}DO ${classNameVar} = ${classNameVar}Mapper.selectById(updateReqVO.getId()); // 获取最新的 + assertPojoEquals(updateReqVO, ${classNameVar}); + } + + @Test + public void testUpdate${simpleClassName}_notExists() { + // 准备参数 + ${sceneEnum.prefixClass}${table.className}SaveReqVO updateReqVO = randomPojo(${sceneEnum.prefixClass}${table.className}SaveReqVO.class); + + // 调用, 并断言异常 + assertServiceException(() -> ${classNameVar}Service.update${simpleClassName}(updateReqVO), ${simpleClassName_underlineCase.toUpperCase()}_NOT_EXISTS); + } + + @Test + public void testDelete${simpleClassName}_success() { + // mock 数据 + ${table.className}DO db${simpleClassName} = randomPojo(${table.className}DO.class); + ${classNameVar}Mapper.insert(db${simpleClassName});// @Sql: 先插入出一条存在的数据 + // 准备参数 + ${primaryColumn.javaType} id = db${simpleClassName}.getId(); + + // 调用 + ${classNameVar}Service.delete${simpleClassName}(id); + // 校验数据不存在了 + assertNull(${classNameVar}Mapper.selectById(id)); + } + + @Test + public void testDelete${simpleClassName}_notExists() { + // 准备参数 + ${primaryColumn.javaType} id = random${primaryColumn.javaType}Id(); + + // 调用, 并断言异常 + assertServiceException(() -> ${classNameVar}Service.delete${simpleClassName}(id), ${simpleClassName_underlineCase.toUpperCase()}_NOT_EXISTS); + } + +## 特殊:树表专属逻辑(树不需要分页接口) +#if ( $table.templateType != 2 ) + @Test + @Disabled // TODO 请修改 null 为需要的值,然后删除 @Disabled 注解 + public void testGet${simpleClassName}Page() { + #getPageCondition("PageReqVO") + + // 调用 + PageResult<${table.className}DO> pageResult = ${classNameVar}Service.get${simpleClassName}Page(reqVO); + // 断言 + assertEquals(1, pageResult.getTotal()); + assertEquals(1, pageResult.getList().size()); + assertPojoEquals(db${simpleClassName}, pageResult.getList().get(0)); + } +#else + @Test + @Disabled // TODO 请修改 null 为需要的值,然后删除 @Disabled 注解 + public void testGet${simpleClassName}List() { + #getPageCondition("ListReqVO") + + // 调用 + List<${table.className}DO> list = ${classNameVar}Service.get${simpleClassName}List(reqVO); + // 断言 + assertEquals(1, list.size()); + assertPojoEquals(db${simpleClassName}, list.get(0)); + } +#end + +} \ No newline at end of file diff --git a/tashow-module/tashow-module-infra/src/main/resources/codegen/sql/h2.vm b/tashow-module/tashow-module-infra/src/main/resources/codegen/sql/h2.vm new file mode 100644 index 0000000..a073fdb --- /dev/null +++ b/tashow-module/tashow-module-infra/src/main/resources/codegen/sql/h2.vm @@ -0,0 +1,37 @@ +-- 将该建表 SQL 语句,添加到 yudao-module-${table.moduleName}-biz 模块的 test/resources/sql/create_tables.sql 文件里 +CREATE TABLE IF NOT EXISTS "${table.tableName.toLowerCase()}" ( +#foreach ($column in $columns) +#if (${column.javaType} == 'Long') + #set ($dataType='bigint') +#elseif (${column.javaType} == 'Integer') + #set ($dataType='int') +#elseif (${column.javaType} == 'Boolean') + #set ($dataType='bit') +#elseif (${column.javaType} == 'Date') + #set ($dataType='datetime') +#else + #set ($dataType='varchar') +#end + #if (${column.primaryKey})##处理主键 + "${column.javaField}"#if (${column.javaType} == 'String') ${dataType} NOT NULL#else ${dataType} NOT NULL GENERATED BY DEFAULT AS IDENTITY#end, + #else + #if (${column.columnName} == 'create_time') + "create_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, + #elseif (${column.columnName} == 'update_time') + "update_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + #elseif (${column.columnName} == 'creator' || ${column.columnName} == 'updater') + "${column.columnName}" ${dataType} DEFAULT '', + #elseif (${column.columnName} == 'deleted') + "deleted" bit NOT NULL DEFAULT FALSE, + #elseif (${column.columnName} == 'tenant_id') + "tenant_id" bigint NOT NULL DEFAULT 0, + #else + "${column.columnName.toLowerCase()}" ${dataType}#if (${column.nullable} == false) NOT NULL#end, + #end + #end +#end + PRIMARY KEY ("${primaryColumn.columnName.toLowerCase()}") +) COMMENT '${table.tableComment}'; + +-- 将该删表 SQL 语句,添加到 yudao-module-${table.moduleName}-biz 模块的 test/resources/sql/clean.sql 文件里 +DELETE FROM "${table.tableName}"; \ No newline at end of file diff --git a/tashow-module/tashow-module-infra/src/main/resources/codegen/sql/sql.vm b/tashow-module/tashow-module-infra/src/main/resources/codegen/sql/sql.vm new file mode 100644 index 0000000..41b107d --- /dev/null +++ b/tashow-module/tashow-module-infra/src/main/resources/codegen/sql/sql.vm @@ -0,0 +1,28 @@ +-- 菜单 SQL +INSERT INTO system_menu( + name, permission, type, sort, parent_id, + path, icon, component, status, component_name +) +VALUES ( + '${table.classComment}管理', '', 2, 0, ${table.parentMenuId}, + '${simpleClassName_strikeCase}', '', '${table.moduleName}/${table.businessName}/index', 0, '${table.className}' +); + +-- 按钮父菜单ID +-- 暂时只支持 MySQL。如果你是 Oracle、PostgreSQL、SQLServer 的话,需要手动修改 @parentId 的部分的代码 +SELECT @parentId := LAST_INSERT_ID(); + +-- 按钮 SQL +#set ($functionNames = ['查询', '创建', '更新', '删除', '导出']) +#set ($functionOps = ['query', 'create', 'update', 'delete', 'export']) +#foreach ($functionName in $functionNames) +#set ($index = $foreach.count - 1) +INSERT INTO system_menu( + name, permission, type, sort, parent_id, + path, icon, component, status +) +VALUES ( + '${table.classComment}${functionName}', '${permissionPrefix}:${functionOps.get($index)}', 3, $foreach.count, @parentId, + '', '', '', 0 +); +#end \ No newline at end of file diff --git a/tashow-module/tashow-module-infra/src/main/resources/codegen/vue/api/api.js.vm b/tashow-module/tashow-module-infra/src/main/resources/codegen/vue/api/api.js.vm new file mode 100644 index 0000000..835c019 --- /dev/null +++ b/tashow-module/tashow-module-infra/src/main/resources/codegen/vue/api/api.js.vm @@ -0,0 +1,141 @@ +import request from '@/utils/request' +#set ($baseURL = "/${table.moduleName}/${simpleClassName_strikeCase}") + +// 创建${table.classComment} +export function create${simpleClassName}(data) { + return request({ + url: '${baseURL}/create', + method: 'post', + data: data + }) +} + +// 更新${table.classComment} +export function update${simpleClassName}(data) { + return request({ + url: '${baseURL}/update', + method: 'put', + data: data + }) +} + +// 删除${table.classComment} +export function delete${simpleClassName}(id) { + return request({ + url: '${baseURL}/delete?id=' + id, + method: 'delete' + }) +} + +// 获得${table.classComment} +export function get${simpleClassName}(id) { + return request({ + url: '${baseURL}/get?id=' + id, + method: 'get' + }) +} + +#if ( $table.templateType != 2 ) +// 获得${table.classComment}分页 +export function get${simpleClassName}Page(params) { + return request({ + url: '${baseURL}/page', + method: 'get', + params + }) +} +#else +// 获得${table.classComment}列表 +export function get${simpleClassName}List(params) { + return request({ + url: '${baseURL}/list', + method: 'get', + params + }) +} +#end +// 导出${table.classComment} Excel +export function export${simpleClassName}Excel(params) { + return request({ + url: '${baseURL}/export-excel', + method: 'get', + params, + responseType: 'blob' + }) +} +## 特殊:主子表专属逻辑 +#foreach ($subTable in $subTables) + #set ($index = $foreach.count - 1) + #set ($subSimpleClassName = $subSimpleClassNames.get($index)) + #set ($subPrimaryColumn = $subPrimaryColumns.get($index))##当前 primary 字段 + #set ($subJoinColumn = $subJoinColumns.get($index))##当前 join 字段 + #set ($SubJoinColumnName = $subJoinColumn.javaField.substring(0,1).toUpperCase() + ${subJoinColumn.javaField.substring(1)})##首字母大写 + #set ($subSimpleClassName_strikeCase = $subSimpleClassName_strikeCases.get($index)) + #set ($subJoinColumn_strikeCase = $subJoinColumn_strikeCases.get($index)) + #set ($subClassNameVar = $subClassNameVars.get($index)) + +// ==================== 子表($subTable.classComment) ==================== + ## 情况一:MASTER_ERP 时,需要分查询页子表 + #if ($table.templateType == 11) + // 获得${subTable.classComment}分页 + export function get${subSimpleClassName}Page(params) { + return request({ + url: '${baseURL}/${subSimpleClassName_strikeCase}/page', + method: 'get', + params + }) + } + ## 情况二:非 MASTER_ERP 时,需要列表查询子表 + #else + #if ($subTable.subJoinMany) + // 获得${subTable.classComment}列表 + export function get${subSimpleClassName}ListBy${SubJoinColumnName}(${subJoinColumn.javaField}) { + return request({ + url: '${baseURL}/${subSimpleClassName_strikeCase}/list-by-${subJoinColumn_strikeCase}?${subJoinColumn.javaField}=' + ${subJoinColumn.javaField}, + method: 'get' + }) + } + #else + // 获得${subTable.classComment} + export function get${subSimpleClassName}By${SubJoinColumnName}(${subJoinColumn.javaField}) { + return request({ + url: '${baseURL}/${subSimpleClassName_strikeCase}/get-by-${subJoinColumn_strikeCase}?${subJoinColumn.javaField}=' + ${subJoinColumn.javaField}, + method: 'get' + }) + } + #end + #end + ## 特殊:MASTER_ERP 时,支持单个的新增、修改、删除操作 + #if ($table.templateType == 11) + // 新增${subTable.classComment} + export function create${subSimpleClassName}(data) { + return request({ + url: '${baseURL}/${subSimpleClassName_strikeCase}/create', + method: 'post', + data + }) + } + // 修改${subTable.classComment} + export function update${subSimpleClassName}(data) { + return request({ + url: '${baseURL}/${subSimpleClassName_strikeCase}/update', + method: 'post', + data + }) + } + // 删除${subTable.classComment} + export function delete${subSimpleClassName}(id) { + return request({ + url: '${baseURL}/${subSimpleClassName_strikeCase}/delete?id=' + id, + method: 'delete' + }) + } + // 获得${subTable.classComment} + export function get${subSimpleClassName}(id) { + return request({ + url: '${baseURL}/${subSimpleClassName_strikeCase}/get?id=' + id, + method: 'get' + }) + } + #end +#end \ No newline at end of file diff --git a/tashow-module/tashow-module-infra/src/main/resources/codegen/vue/views/components/form_sub_erp.vue.vm b/tashow-module/tashow-module-infra/src/main/resources/codegen/vue/views/components/form_sub_erp.vue.vm new file mode 100644 index 0000000..99aa91a --- /dev/null +++ b/tashow-module/tashow-module-infra/src/main/resources/codegen/vue/views/components/form_sub_erp.vue.vm @@ -0,0 +1,205 @@ +#set ($subTable = $subTables.get($subIndex))##当前表 +#set ($subColumns = $subColumnsList.get($subIndex))##当前字段数组 +#set ($subSimpleClassName = $subSimpleClassNames.get($subIndex)) +#set ($subJoinColumn = $subJoinColumns.get($subIndex))##当前 join 字段 + + + diff --git a/tashow-module/tashow-module-infra/src/main/resources/codegen/vue/views/components/form_sub_inner.vue.vm b/tashow-module/tashow-module-infra/src/main/resources/codegen/vue/views/components/form_sub_inner.vue.vm new file mode 100644 index 0000000..ca266be --- /dev/null +++ b/tashow-module/tashow-module-infra/src/main/resources/codegen/vue/views/components/form_sub_inner.vue.vm @@ -0,0 +1,2 @@ +## 主表的 normal 和 inner 使用相同的 form 表单 +#parse("codegen/vue/views/components/form_sub_normal.vue.vm") \ No newline at end of file diff --git a/tashow-module/tashow-module-infra/src/main/resources/codegen/vue/views/components/form_sub_normal.vue.vm b/tashow-module/tashow-module-infra/src/main/resources/codegen/vue/views/components/form_sub_normal.vue.vm new file mode 100644 index 0000000..48a404a --- /dev/null +++ b/tashow-module/tashow-module-infra/src/main/resources/codegen/vue/views/components/form_sub_normal.vue.vm @@ -0,0 +1,347 @@ +#set ($subTable = $subTables.get($subIndex))##当前表 +#set ($subColumns = $subColumnsList.get($subIndex))##当前字段数组 +#set ($subJoinColumn = $subJoinColumns.get($subIndex))##当前 join 字段 +#set ($subSimpleClassName = $subSimpleClassNames.get($subIndex)) +#set ($subJoinColumn = $subJoinColumns.get($subIndex))##当前 join 字段 +#set ($SubJoinColumnName = $subJoinColumn.javaField.substring(0,1).toUpperCase() + ${subJoinColumn.javaField.substring(1)})##首字母大写 + + + diff --git a/tashow-module/tashow-module-infra/src/main/resources/codegen/vue/views/components/list_sub_erp.vue.vm b/tashow-module/tashow-module-infra/src/main/resources/codegen/vue/views/components/list_sub_erp.vue.vm new file mode 100644 index 0000000..589736b --- /dev/null +++ b/tashow-module/tashow-module-infra/src/main/resources/codegen/vue/views/components/list_sub_erp.vue.vm @@ -0,0 +1,165 @@ +#set ($subTable = $subTables.get($subIndex))##当前表 +#set ($subColumns = $subColumnsList.get($subIndex))##当前字段数组 +#set ($subJoinColumn = $subJoinColumns.get($subIndex))##当前 join 字段 +#set ($subSimpleClassName = $subSimpleClassNames.get($subIndex)) +#set ($subJoinColumn = $subJoinColumns.get($subIndex))##当前 join 字段 +#set ($SubJoinColumnName = $subJoinColumn.javaField.substring(0,1).toUpperCase() + ${subJoinColumn.javaField.substring(1)})##首字母大写 + + + diff --git a/tashow-module/tashow-module-infra/src/main/resources/codegen/vue/views/components/list_sub_inner.vue.vm b/tashow-module/tashow-module-infra/src/main/resources/codegen/vue/views/components/list_sub_inner.vue.vm new file mode 100644 index 0000000..90b8e41 --- /dev/null +++ b/tashow-module/tashow-module-infra/src/main/resources/codegen/vue/views/components/list_sub_inner.vue.vm @@ -0,0 +1,4 @@ +## 子表的 erp 和 inner 使用相似的 list 列表,差异主要两点: +## 1)inner 使用 list 不分页,erp 使用 page 分页 +## 2)erp 支持单个子表的新增、修改、删除,inner 不支持 +#parse("codegen/vue/views/components/list_sub_erp.vue.vm") \ No newline at end of file diff --git a/tashow-module/tashow-module-infra/src/main/resources/codegen/vue/views/form.vue.vm b/tashow-module/tashow-module-infra/src/main/resources/codegen/vue/views/form.vue.vm new file mode 100644 index 0000000..634d05d --- /dev/null +++ b/tashow-module/tashow-module-infra/src/main/resources/codegen/vue/views/form.vue.vm @@ -0,0 +1,320 @@ + + + diff --git a/tashow-module/tashow-module-infra/src/main/resources/codegen/vue/views/index.vue.vm b/tashow-module/tashow-module-infra/src/main/resources/codegen/vue/views/index.vue.vm new file mode 100644 index 0000000..9c1e124 --- /dev/null +++ b/tashow-module/tashow-module-infra/src/main/resources/codegen/vue/views/index.vue.vm @@ -0,0 +1,340 @@ + + + diff --git a/tashow-module/tashow-module-infra/src/main/resources/codegen/vue3/api/api.ts.vm b/tashow-module/tashow-module-infra/src/main/resources/codegen/vue3/api/api.ts.vm new file mode 100644 index 0000000..c3044fb --- /dev/null +++ b/tashow-module/tashow-module-infra/src/main/resources/codegen/vue3/api/api.ts.vm @@ -0,0 +1,115 @@ +import request from '@/config/axios' +#set ($baseURL = "/${table.moduleName}/${simpleClassName_strikeCase}") + +// ${table.classComment} VO +export interface ${simpleClassName}VO { +#foreach ($column in $columns) +#if ($column.createOperation || $column.updateOperation) +#if(${column.javaType.toLowerCase()} == "long" || ${column.javaType.toLowerCase()} == "integer" || ${column.javaType.toLowerCase()} == "short" || ${column.javaType.toLowerCase()} == "double" || ${column.javaType.toLowerCase()} == "bigdecimal") + ${column.javaField}: number // ${column.columnComment} +#elseif(${column.javaType.toLowerCase()} == "date" || ${column.javaType.toLowerCase()} == "localdate" || ${column.javaType.toLowerCase()} == "localdatetime") + ${column.javaField}: Date // ${column.columnComment} +#else + ${column.javaField}: ${column.javaType.toLowerCase()} // ${column.columnComment} +#end +#end +#end +} + +// ${table.classComment} API +export const ${simpleClassName}Api = { +#if ( $table.templateType != 2 ) + // 查询${table.classComment}分页 + get${simpleClassName}Page: async (params: any) => { + return await request.get({ url: `${baseURL}/page`, params }) + }, +#else + // 查询${table.classComment}列表 + get${simpleClassName}List: async (params) => { + return await request.get({ url: `${baseURL}/list`, params }) + }, +#end + + // 查询${table.classComment}详情 + get${simpleClassName}: async (id: number) => { + return await request.get({ url: `${baseURL}/get?id=` + id }) + }, + + // 新增${table.classComment} + create${simpleClassName}: async (data: ${simpleClassName}VO) => { + return await request.post({ url: `${baseURL}/create`, data }) + }, + + // 修改${table.classComment} + update${simpleClassName}: async (data: ${simpleClassName}VO) => { + return await request.put({ url: `${baseURL}/update`, data }) + }, + + // 删除${table.classComment} + delete${simpleClassName}: async (id: number) => { + return await request.delete({ url: `${baseURL}/delete?id=` + id }) + }, + + // 导出${table.classComment} Excel + export${simpleClassName}: async (params) => { + return await request.download({ url: `${baseURL}/export-excel`, params }) + }, +## 特殊:主子表专属逻辑 +#foreach ($subTable in $subTables) +#set ($index = $foreach.count - 1) +#set ($subSimpleClassName = $subSimpleClassNames.get($index)) +#set ($subPrimaryColumn = $subPrimaryColumns.get($index))##当前 primary 字段 +#set ($subJoinColumn = $subJoinColumns.get($index))##当前 join 字段 +#set ($SubJoinColumnName = $subJoinColumn.javaField.substring(0,1).toUpperCase() + ${subJoinColumn.javaField.substring(1)})##首字母大写 +#set ($subSimpleClassName_strikeCase = $subSimpleClassName_strikeCases.get($index)) +#set ($subJoinColumn_strikeCase = $subJoinColumn_strikeCases.get($index)) +#set ($subClassNameVar = $subClassNameVars.get($index)) + +// ==================== 子表($subTable.classComment) ==================== +## 情况一:MASTER_ERP 时,需要分查询页子表 +#if ( $table.templateType == 11 ) + + // 获得${subTable.classComment}分页 + get${subSimpleClassName}Page: async (params) => { + return await request.get({ url: `${baseURL}/${subSimpleClassName_strikeCase}/page`, params }) + }, +## 情况二:非 MASTER_ERP 时,需要列表查询子表 +#else + #if ( $subTable.subJoinMany ) + + // 获得${subTable.classComment}列表 + get${subSimpleClassName}ListBy${SubJoinColumnName}: async (${subJoinColumn.javaField}) => { + return await request.get({ url: `${baseURL}/${subSimpleClassName_strikeCase}/list-by-${subJoinColumn_strikeCase}?${subJoinColumn.javaField}=` + ${subJoinColumn.javaField} }) + }, + #else + + // 获得${subTable.classComment} + get${subSimpleClassName}By${SubJoinColumnName}: async (${subJoinColumn.javaField}) => { + return await request.get({ url: `${baseURL}/${subSimpleClassName_strikeCase}/get-by-${subJoinColumn_strikeCase}?${subJoinColumn.javaField}=` + ${subJoinColumn.javaField} }) + }, + #end +#end +## 特殊:MASTER_ERP 时,支持单个的新增、修改、删除操作 +#if ( $table.templateType == 11 ) + // 新增${subTable.classComment} + create${subSimpleClassName}: async (data) => { + return await request.post({ url: `${baseURL}/${subSimpleClassName_strikeCase}/create`, data }) + }, + + // 修改${subTable.classComment} + update${subSimpleClassName}: async (data) => { + return await request.put({ url: `${baseURL}/${subSimpleClassName_strikeCase}/update`, data }) + }, + + // 删除${subTable.classComment} + delete${subSimpleClassName}: async (id: number) => { + return await request.delete({ url: `${baseURL}/${subSimpleClassName_strikeCase}/delete?id=` + id }) + }, + + // 获得${subTable.classComment} + get${subSimpleClassName}: async (id: number) => { + return await request.get({ url: `${baseURL}/${subSimpleClassName_strikeCase}/get?id=` + id }) + }, +#end +#end +} diff --git a/tashow-module/tashow-module-infra/src/main/resources/codegen/vue3/views/components/form_sub_erp.vue.vm b/tashow-module/tashow-module-infra/src/main/resources/codegen/vue3/views/components/form_sub_erp.vue.vm new file mode 100644 index 0000000..81cd977 --- /dev/null +++ b/tashow-module/tashow-module-infra/src/main/resources/codegen/vue3/views/components/form_sub_erp.vue.vm @@ -0,0 +1,204 @@ +#set ($subColumns = $subColumnsList.get($subIndex))##当前字段数组 +#set ($subSimpleClassName = $subSimpleClassNames.get($subIndex)) +#set ($subJoinColumn = $subJoinColumns.get($subIndex))##当前 join 字段 + + \ No newline at end of file diff --git a/tashow-module/tashow-module-infra/src/main/resources/codegen/vue3/views/components/form_sub_inner.vue.vm b/tashow-module/tashow-module-infra/src/main/resources/codegen/vue3/views/components/form_sub_inner.vue.vm new file mode 100644 index 0000000..d8542c3 --- /dev/null +++ b/tashow-module/tashow-module-infra/src/main/resources/codegen/vue3/views/components/form_sub_inner.vue.vm @@ -0,0 +1,2 @@ +## 主表的 normal 和 inner 使用相同的 form 表单 +#parse("codegen/vue3/views/components/form_sub_normal.vue.vm") \ No newline at end of file diff --git a/tashow-module/tashow-module-infra/src/main/resources/codegen/vue3/views/components/form_sub_normal.vue.vm b/tashow-module/tashow-module-infra/src/main/resources/codegen/vue3/views/components/form_sub_normal.vue.vm new file mode 100644 index 0000000..3fa1eff --- /dev/null +++ b/tashow-module/tashow-module-infra/src/main/resources/codegen/vue3/views/components/form_sub_normal.vue.vm @@ -0,0 +1,360 @@ +#set ($subTable = $subTables.get($subIndex))##当前表 +#set ($subColumns = $subColumnsList.get($subIndex))##当前字段数组 +#set ($subJoinColumn = $subJoinColumns.get($subIndex))##当前 join 字段 +#set ($subSimpleClassName = $subSimpleClassNames.get($subIndex)) +#set ($subJoinColumn = $subJoinColumns.get($subIndex))##当前 join 字段 +#set ($SubJoinColumnName = $subJoinColumn.javaField.substring(0,1).toUpperCase() + ${subJoinColumn.javaField.substring(1)})##首字母大写 + + \ No newline at end of file diff --git a/tashow-module/tashow-module-infra/src/main/resources/codegen/vue3/views/components/list_sub_erp.vue.vm b/tashow-module/tashow-module-infra/src/main/resources/codegen/vue3/views/components/list_sub_erp.vue.vm new file mode 100644 index 0000000..3f0710e --- /dev/null +++ b/tashow-module/tashow-module-infra/src/main/resources/codegen/vue3/views/components/list_sub_erp.vue.vm @@ -0,0 +1,184 @@ +#set ($subTable = $subTables.get($subIndex))##当前表 +#set ($subColumns = $subColumnsList.get($subIndex))##当前字段数组 +#set ($subJoinColumn = $subJoinColumns.get($subIndex))##当前 join 字段 +#set ($subSimpleClassName = $subSimpleClassNames.get($subIndex)) +#set ($subJoinColumn = $subJoinColumns.get($subIndex))##当前 join 字段 +#set ($SubJoinColumnName = $subJoinColumn.javaField.substring(0,1).toUpperCase() + ${subJoinColumn.javaField.substring(1)})##首字母大写 + + \ No newline at end of file diff --git a/tashow-module/tashow-module-infra/src/main/resources/codegen/vue3/views/components/list_sub_inner.vue.vm b/tashow-module/tashow-module-infra/src/main/resources/codegen/vue3/views/components/list_sub_inner.vue.vm new file mode 100644 index 0000000..3fe6488 --- /dev/null +++ b/tashow-module/tashow-module-infra/src/main/resources/codegen/vue3/views/components/list_sub_inner.vue.vm @@ -0,0 +1,4 @@ +## 子表的 erp 和 inner 使用相似的 list 列表,差异主要两点: +## 1)inner 使用 list 不分页,erp 使用 page 分页 +## 2)erp 支持单个子表的新增、修改、删除,inner 不支持 +#parse("codegen/vue3/views/components/list_sub_erp.vue.vm") \ No newline at end of file diff --git a/tashow-module/tashow-module-infra/src/main/resources/codegen/vue3/views/form.vue.vm b/tashow-module/tashow-module-infra/src/main/resources/codegen/vue3/views/form.vue.vm new file mode 100644 index 0000000..e37474b --- /dev/null +++ b/tashow-module/tashow-module-infra/src/main/resources/codegen/vue3/views/form.vue.vm @@ -0,0 +1,300 @@ + + \ No newline at end of file diff --git a/tashow-module/tashow-module-infra/src/main/resources/codegen/vue3/views/index.vue.vm b/tashow-module/tashow-module-infra/src/main/resources/codegen/vue3/views/index.vue.vm new file mode 100644 index 0000000..399b58e --- /dev/null +++ b/tashow-module/tashow-module-infra/src/main/resources/codegen/vue3/views/index.vue.vm @@ -0,0 +1,374 @@ + + + \ No newline at end of file diff --git a/tashow-module/tashow-module-infra/src/main/resources/codegen/vue3_vben/api/api.ts.vm b/tashow-module/tashow-module-infra/src/main/resources/codegen/vue3_vben/api/api.ts.vm new file mode 100644 index 0000000..b7f2651 --- /dev/null +++ b/tashow-module/tashow-module-infra/src/main/resources/codegen/vue3_vben/api/api.ts.vm @@ -0,0 +1,32 @@ +import { defHttp } from '@/utils/http/axios' +#set ($baseURL = "/${table.moduleName}/${simpleClassName_strikeCase}") + +// 查询${table.classComment}列表 +export function get${simpleClassName}Page(params) { + return defHttp.get({ url: '${baseURL}/page', params }) +} + +// 查询${table.classComment}详情 +export function get${simpleClassName}(id: number) { + return defHttp.get({ url: `${baseURL}/get?id=${id}` }) +} + +// 新增${table.classComment} +export function create${simpleClassName}(data) { + return defHttp.post({ url: '${baseURL}/create', data }) +} + +// 修改${table.classComment} +export function update${simpleClassName}(data) { + return defHttp.put({ url: '${baseURL}/update', data }) +} + +// 删除${table.classComment} +export function delete${simpleClassName}(id: number) { + return defHttp.delete({ url: `${baseURL}/delete?id=${id}` }) +} + +// 导出${table.classComment} Excel +export function export${simpleClassName}(params) { + return defHttp.download({ url: '${baseURL}/export-excel', params }, '${table.classComment}.xls') +} diff --git a/tashow-module/tashow-module-infra/src/main/resources/codegen/vue3_vben/views/data.ts.vm b/tashow-module/tashow-module-infra/src/main/resources/codegen/vue3_vben/views/data.ts.vm new file mode 100644 index 0000000..56f4e82 --- /dev/null +++ b/tashow-module/tashow-module-infra/src/main/resources/codegen/vue3_vben/views/data.ts.vm @@ -0,0 +1,260 @@ +import type {BasicColumn, FormSchema} from '@/components/Table' +import {useRender} from '@/components/Table' +import {DICT_TYPE, getDictOptions} from '@/utils/dict' + +export const columns: BasicColumn[] = [ +#foreach($column in $columns) +#if ($column.listOperationResult) + #set ($dictType=$column.dictType) + #set ($javaField = $column.javaField) + #set ($AttrName=$column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)}) + #set ($comment=$column.columnComment) +#if ($column.javaType == "LocalDateTime")## 时间类型 + { + title: '${comment}', + dataIndex: '${javaField}', + width: 180, + customRender: ({ text }) => { + return useRender.renderDate(text) + }, + }, +#elseif("" != $column.dictType)## 数据字典 + { + title: '${comment}', + dataIndex: '${javaField}', + width: 180, + customRender: ({ text }) => { + return useRender.renderDict(text, DICT_TYPE.$dictType.toUpperCase()) + }, + }, +#else + { + title: '${comment}', + dataIndex: '${javaField}', + width: 160, + }, +#end +#end +#end +] + +export const searchFormSchema: FormSchema[] = [ +#foreach($column in $columns) +#if ($column.listOperation) + #set ($dictType=$column.dictType) + #set ($javaType = $column.javaType) + #set ($javaField = $column.javaField) + #set ($AttrName=$column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)}) + #set ($comment=$column.columnComment) + #if ($javaType == "Integer" || $javaType == "Long" || $javaType == "Byte" || $javaType == "Short") + #set ($dictMethod = "number") + #elseif ($javaType == "String") + #set ($dictMethod = "string") + #elseif ($javaType == "Boolean") + #set ($dictMethod = "boolean") + #end + { + label: '${comment}', + field: '${javaField}', + #if ($column.htmlType == "input") + component: 'Input', + #elseif ($column.htmlType == "select") + component: 'Select', + componentProps: { + #if ("" != $dictType)## 设置了 dictType 数据字典的情况 + options: getDictOptions(DICT_TYPE.$dictType.toUpperCase(), '$dictMethod'), + #else## 未设置 dictType 数据字典的情况 + options: [], + #end + }, + #elseif ($column.htmlType == "radio") + component: 'RadioButtonGroup', + componentProps: { + #if ("" != $dictType)## 设置了 dictType 数据字典的情况 + options: getDictOptions(DICT_TYPE.$dictType.toUpperCase(), '$dictMethod'), + #else## 未设置 dictType 数据字典的情况 + options: [], + #end + }, + #elseif($column.htmlType == "datetime") + component: 'RangePicker', + #end + colProps: { span: 8 }, + }, +#end +#end +] + +export const createFormSchema: FormSchema[] = [ + { + label: '编号', + field: 'id', + show: false, + component: 'Input', + }, +#foreach($column in $columns) +#if ($column.createOperation) + #set ($dictType = $column.dictType) + #set ($javaType = $column.javaType) + #set ($javaField = $column.javaField) + #set ($AttrName = $column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)}) + #set ($comment = $column.columnComment) + #if ($javaType == "Integer" || $javaType == "Long" || $javaType == "Byte" || $javaType == "Short") + #set ($dictMethod = "number") + #elseif ($javaType == "String") + #set ($dictMethod = "string") + #elseif ($javaType == "Boolean") + #set ($dictMethod = "boolean") + #end +#if (!$column.primaryKey)## 忽略主键,不用在表单里 + { + label: '${comment}', + field: '${javaField}', + #if (($column.createOperation || $column.updateOperation) && !$column.nullable && !${column.primaryKey})## 创建或者更新操作 && 要求非空 && 非主键 + required: true, + #end + #if ($column.htmlType == "input") + component: 'Input', + #elseif($column.htmlType == "imageUpload")## 图片上传 + component: 'FileUpload', + componentProps: { + fileType: 'image', + maxCount: 1, + }, + #elseif($column.htmlType == "fileUpload")## 文件上传 + component: 'FileUpload', + componentProps: { + fileType: 'file', + maxCount: 1, + }, + #elseif($column.htmlType == "editor")## 文本编辑器 + component: 'Editor', + #elseif($column.htmlType == "select")## 下拉框 + component: 'Select', + componentProps: { + #if ("" != $dictType)## 有数据字典 + options: getDictOptions(DICT_TYPE.$dictType.toUpperCase(), '$dictMethod'), + #else##没数据字典 + options:[], + #end + }, + #elseif($column.htmlType == "checkbox")## 多选框 + component: 'Checkbox', + componentProps: { + #if ("" != $dictType)## 有数据字典 + options: getDictOptions(DICT_TYPE.$dictType.toUpperCase(), '$dictMethod'), + #else##没数据字典 + options:[], + #end + }, + #elseif($column.htmlType == "radio")## 单选框 + component: 'RadioButtonGroup', + componentProps: { + #if ("" != $dictType)## 有数据字典 + options: getDictOptions(DICT_TYPE.$dictType.toUpperCase(), '$dictMethod'), + #else##没数据字典 + options:[], + #end + }, + #elseif($column.htmlType == "datetime")## 时间框 + component: 'DatePicker', + componentProps: { + showTime: true, + format: 'YYYY-MM-DD HH:mm:ss', + valueFormat: 'x', + }, + #elseif($column.htmlType == "textarea")## 文本域 + component: 'InputTextArea', + #end + }, +#end +#end +#end +] + +export const updateFormSchema: FormSchema[] = [ + { + label: '编号', + field: 'id', + show: false, + component: 'Input', + }, +#foreach($column in $columns) +#if ($column.updateOperation) +#set ($dictType = $column.dictType) +#set ($javaType = $column.javaType) +#set ($javaField = $column.javaField) +#set ($AttrName = $column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)}) +#set ($comment = $column.columnComment) +#if ($javaType == "Integer" || $javaType == "Long" || $javaType == "Byte" || $javaType == "Short") + #set ($dictMethod = "number") +#elseif ($javaType == "String") + #set ($dictMethod = "string") +#elseif ($javaType == "Boolean") + #set ($dictMethod = "boolean") +#end + #if (!$column.primaryKey)## 忽略主键,不用在表单里 + { + label: '${comment}', + field: '${javaField}', + #if (($column.createOperation || $column.updateOperation) && !$column.nullable && !${column.primaryKey})## 创建或者更新操作 && 要求非空 && 非主键 + required: true, + #end + #if ($column.htmlType == "input") + component: 'Input', + #elseif($column.htmlType == "imageUpload")## 图片上传 + component: 'FileUpload', + componentProps: { + fileType: 'image', + maxCount: 1, + }, + #elseif($column.htmlType == "fileUpload")## 文件上传 + component: 'FileUpload', + componentProps: { + fileType: 'file', + maxCount: 1, + }, + #elseif($column.htmlType == "editor")## 文本编辑器 + component: 'Editor', + #elseif($column.htmlType == "select")## 下拉框 + component: 'Select', + componentProps: { + #if ("" != $dictType)## 有数据字典 + options: getDictOptions(DICT_TYPE.$dictType.toUpperCase(), '$dictMethod'), + #else##没数据字典 + options:[], + #end + }, + #elseif($column.htmlType == "checkbox")## 多选框 + component: 'Checkbox', + componentProps: { + #if ("" != $dictType)## 有数据字典 + options: getDictOptions(DICT_TYPE.$dictType.toUpperCase(), '$dictMethod'), + #else##没数据字典 + options:[], + #end + }, + #elseif($column.htmlType == "radio")## 单选框 + component: 'RadioButtonGroup', + componentProps: { + #if ("" != $dictType)## 有数据字典 + options: getDictOptions(DICT_TYPE.$dictType.toUpperCase(), '$dictMethod'), + #else##没数据字典 + options:[], + #end + }, + #elseif($column.htmlType == "datetime")## 时间框 + component: 'DatePicker', + componentProps: { + showTime: true, + format: 'YYYY-MM-DD HH:mm:ss', + valueFormat: 'x', + }, + #elseif($column.htmlType == "textarea")## 文本域 + component: 'InputTextArea', + #end + }, + #end +#end +#end +] \ No newline at end of file diff --git a/tashow-module/tashow-module-infra/src/main/resources/codegen/vue3_vben/views/form.vue.vm b/tashow-module/tashow-module-infra/src/main/resources/codegen/vue3_vben/views/form.vue.vm new file mode 100644 index 0000000..1804365 --- /dev/null +++ b/tashow-module/tashow-module-infra/src/main/resources/codegen/vue3_vben/views/form.vue.vm @@ -0,0 +1,58 @@ + + \ No newline at end of file diff --git a/tashow-module/tashow-module-infra/src/main/resources/codegen/vue3_vben/views/index.vue.vm b/tashow-module/tashow-module-infra/src/main/resources/codegen/vue3_vben/views/index.vue.vm new file mode 100644 index 0000000..9e59b12 --- /dev/null +++ b/tashow-module/tashow-module-infra/src/main/resources/codegen/vue3_vben/views/index.vue.vm @@ -0,0 +1,92 @@ + + diff --git a/tashow-module/tashow-module-infra/src/main/resources/file/erweima.jpg b/tashow-module/tashow-module-infra/src/main/resources/file/erweima.jpg new file mode 100644 index 0000000..1447283 Binary files /dev/null and b/tashow-module/tashow-module-infra/src/main/resources/file/erweima.jpg differ diff --git a/tashow-module/tashow-module-infra/src/main/resources/logback-spring.xml b/tashow-module/tashow-module-infra/src/main/resources/logback-spring.xml new file mode 100644 index 0000000..b1b9f3f --- /dev/null +++ b/tashow-module/tashow-module-infra/src/main/resources/logback-spring.xml @@ -0,0 +1,76 @@ + + + + + + + + + +       + + + ${PATTERN_DEFAULT} + + + + + + + + + + ${PATTERN_DEFAULT} + + + + ${LOG_FILE} + + + ${LOGBACK_ROLLINGPOLICY_FILE_NAME_PATTERN:-${LOG_FILE}.%d{yyyy-MM-dd}.%i.gz} + + ${LOGBACK_ROLLINGPOLICY_CLEAN_HISTORY_ON_START:-false} + + ${LOGBACK_ROLLINGPOLICY_MAX_FILE_SIZE:-10MB} + + ${LOGBACK_ROLLINGPOLICY_TOTAL_SIZE_CAP:-0} + + ${LOGBACK_ROLLINGPOLICY_MAX_HISTORY:-30} + + + + + + 0 + + 256 + + + + + + + + ${PATTERN_DEFAULT} + + + + + + + + + + + + + + + + + + + + + + diff --git a/tashow-module/tashow-module-product/pom.xml b/tashow-module/tashow-module-product/pom.xml new file mode 100644 index 0000000..0639aa0 --- /dev/null +++ b/tashow-module/tashow-module-product/pom.xml @@ -0,0 +1,121 @@ + + 4.0.0 + + com.tashow.cloud + tashow-module + ${revision} + + + tashow-module-product + jar + + + + + com.alibaba.cloud + spring-cloud-starter-alibaba-nacos-discovery + + + + + com.alibaba.cloud + spring-cloud-starter-alibaba-nacos-config + + + + io.swagger + swagger-models + 1.6.2 + + + + io.swagger.core.v3 + swagger-core + 2.2.20 + + + + + io.swagger.core.v3 + swagger-models + 2.2.20 + + + com.tashow.cloud + tashow-framework-monitor + + + + + com.alibaba + easyexcel + 4.0.3 + + + + com.tashow.cloud + tashow-product-api + ${revision} + + + com.tashow.cloud + tashow-data-excel + ${revision} + + + + com.tashow.cloud + tashow-framework-rpc + + + com.tashow.cloud + tashow-data-mybatis + + + com.tashow.cloud + tashow-framework-web + + + com.tashow.cloud + tashow-framework-env + + + com.tashow.cloud + tashow-framework-websocket + + + com.tashow.cloud + tashow-data-redis + + + com.tashow.cloud + tashow-framework-security + + + org.springframework.boot + spring-boot-starter-actuator + + + + + ${project.artifactId} + + + org.springframework.boot + spring-boot-maven-plugin + ${spring.boot.version} + + com.tashow.cloud.product.ProductServerApplication + + + + + repackage + + + + + + + diff --git a/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/ProductServerApplication.java b/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/ProductServerApplication.java new file mode 100644 index 0000000..2bdf20a --- /dev/null +++ b/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/ProductServerApplication.java @@ -0,0 +1,19 @@ +package com.tashow.cloud.product; + +import org.mybatis.spring.annotation.MapperScan; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.scheduling.annotation.EnableScheduling; + +/** + * 应用服务启动类 + */ +@SpringBootApplication +public class ProductServerApplication { + + public static void main(String[] args) { + SpringApplication.run(ProductServerApplication.class, args); + System.out.println("产品启动成功"); + } +} diff --git a/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/api/CategoryApiImpl.java b/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/api/CategoryApiImpl.java new file mode 100644 index 0000000..399ae38 --- /dev/null +++ b/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/api/CategoryApiImpl.java @@ -0,0 +1,23 @@ +package com.tashow.cloud.product.api; + +import com.tashow.cloud.productapi.api.product.dto.CategoryDO; +import com.tashow.cloud.product.service.CategoryService; +import com.tashow.cloud.productapi.api.product.CategoryApi; +import com.tashow.cloud.productapi.api.product.dto.CategoryDto; +import jakarta.annotation.Resource; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.RestController; +import java.util.List; + +@RestController // 提供 RESTful API 接口,给 Feign 调用 +@Validated +public class CategoryApiImpl implements CategoryApi { + + @Resource + private CategoryService categoryService; + + @Override + public List categoryList(Integer grade, Long categoryId,String categoryName, Integer status) { + return categoryService.categoryList(grade, categoryId,categoryName, status); + } +} diff --git a/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/controller/admin/CategoryController.java b/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/controller/admin/CategoryController.java new file mode 100644 index 0000000..b06eee1 --- /dev/null +++ b/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/controller/admin/CategoryController.java @@ -0,0 +1,96 @@ +package com.tashow.cloud.product.controller.admin; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.tashow.cloud.common.pojo.CommonResult; +import com.tashow.cloud.productapi.api.product.dto.CategoryDO; +import com.tashow.cloud.product.service.CategoryService; +import com.tashow.cloud.productapi.api.product.dto.CategoryDto; +import com.tashow.cloud.productapi.api.product.vo.CategorySaveReqVO; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.annotation.Resource; +import jakarta.annotation.security.PermitAll; +import jakarta.validation.Valid; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +import static com.tashow.cloud.common.pojo.CommonResult.success; + +@Tag(name = "管理后台 - 产品类目") +@RestController +@RequestMapping("/product/category") +@Validated +public class CategoryController { + + @Resource + private CategoryService categoryService; + + /** + * 获取菜单页面的表 + * @return + */ + @PermitAll + @GetMapping("/categoryList") + public CommonResult> categoryList(@RequestParam(value = "grade", required = false) Integer grade, + @RequestParam(value = "categoryId", required = false) Long categoryId, + @RequestParam(value = "categoryName", required = false) String categoryName, + @RequestParam(value = "status", required = false) Integer status) { + return success(categoryService.categoryList(grade, categoryId,categoryName, status)); + } + @PostMapping("/create") + @Operation(summary = "创建产品类目") + @PermitAll + public CommonResult createCategory(@Valid @RequestBody CategorySaveReqVO createReqVO) { + return success(categoryService.createCategory(createReqVO)); + } + + @PutMapping("/update") + @Operation(summary = "更新产品类目") + @PermitAll + public CommonResult updateCategory(@RequestBody CategorySaveReqVO updateReqVO) { + categoryService.updateCategory(updateReqVO); + return success(true); + } +/* + @DeleteMapping("/delete") + @Operation(summary = "删除产品类目") + @Parameter(name = "id", description = "编号", required = true) + @PreAuthorize("@ss.hasPermission('tz:category:delete')") + public CommonResult deleteCategory(@RequestParam("id") Long id) { + categoryService.deleteCategory(id); + return success(true); + } + + @GetMapping("/get") + @Operation(summary = "获得产品类目") + @Parameter(name = "id", description = "编号", required = true, example = "1024") + @PreAuthorize("@ss.hasPermission('tz:category:query')") + public CommonResult getCategory(@RequestParam("id") Long id) { + CategoryDO category = categoryService.getCategory(id); + return success(BeanUtils.toBean(category, CategoryRespVO.class)); + } + + @GetMapping("/page") + @Operation(summary = "获得产品类目分页") + @PreAuthorize("@ss.hasPermission('tz:category:query')") + public CommonResult> getCategoryPage(@Valid CategoryPageReqVO pageReqVO) { + PageResult pageResult = categoryService.getCategoryPage(pageReqVO); + return success(BeanUtils.toBean(pageResult, CategoryRespVO.class)); + }*/ + +/* @GetMapping("/export-excel") + @Operation(summary = "导出产品类目 Excel") + @PreAuthorize("@ss.hasPermission('tz:category:export')") + @ApiAccessLog(operateType = EXPORT) + public void exportCategoryExcel(@Valid CategoryPageReqVO pageReqVO, + HttpServletResponse response) throws IOException { + pageReqVO.setPageSize(PageParam.PAGE_SIZE_NONE); + List list = categoryService.getCategoryPage(pageReqVO).getList(); + // 导出 Excel + ExcelUtils.write(response, "产品类目.xls", "数据", CategoryRespVO.class, + BeanUtils.toBean(list, CategoryRespVO.class)); + }*/ + +} \ No newline at end of file diff --git a/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/controller/admin/ProdAdditionalFeeDatesController.java b/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/controller/admin/ProdAdditionalFeeDatesController.java new file mode 100644 index 0000000..cac5ac5 --- /dev/null +++ b/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/controller/admin/ProdAdditionalFeeDatesController.java @@ -0,0 +1,94 @@ +package com.tashow.cloud.product.controller.admin; + +import com.tashow.cloud.common.pojo.CommonResult; +import com.tashow.cloud.common.pojo.PageParam; +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.common.util.object.BeanUtils; +import com.tashow.cloud.excel.excel.core.util.ExcelUtils; +import com.tashow.cloud.productapi.api.product.dto.ProdAdditionalFeeDatesDO; +import com.tashow.cloud.product.service.ProdAdditionalFeeDatesService; +import com.tashow.cloud.productapi.api.product.vo.prodadditionalfeedates.ProdAdditionalFeeDatesPageReqVO; +import com.tashow.cloud.productapi.api.product.vo.prodadditionalfeedates.ProdAdditionalFeeDatesRespVO; +import com.tashow.cloud.productapi.api.product.vo.prodadditionalfeedates.ProdAdditionalFeeDatesSaveReqVO; +import com.tashow.cloud.web.apilog.core.annotation.ApiAccessLog; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.annotation.Resource; +import jakarta.servlet.http.HttpServletResponse; +import jakarta.validation.Valid; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import java.io.IOException; +import java.util.List; + +import static com.tashow.cloud.common.pojo.CommonResult.success; +import static com.tashow.cloud.web.apilog.core.enums.OperateTypeEnum.EXPORT; + + +@Tag(name = "管理后台 - 特殊日期附加费用规则") +@RestController +@RequestMapping("/tz/prod-additional-fee-dates") +@Validated +public class ProdAdditionalFeeDatesController { + + @Resource + private ProdAdditionalFeeDatesService prodAdditionalFeeDatesService; + + @PostMapping("/create") + @Operation(summary = "创建特殊日期附加费用规则") + @PreAuthorize("@ss.hasPermission('tz:prod-additional-fee-dates:create')") + public CommonResult createProdAdditionalFeeDates(@Valid @RequestBody ProdAdditionalFeeDatesSaveReqVO createReqVO) { + return success(prodAdditionalFeeDatesService.createProdAdditionalFeeDates(createReqVO)); + } + + @PutMapping("/update") + @Operation(summary = "更新特殊日期附加费用规则") + @PreAuthorize("@ss.hasPermission('tz:prod-additional-fee-dates:update')") + public CommonResult updateProdAdditionalFeeDates(@Valid @RequestBody ProdAdditionalFeeDatesSaveReqVO updateReqVO) { + prodAdditionalFeeDatesService.updateProdAdditionalFeeDates(updateReqVO); + return success(true); + } + + @DeleteMapping("/delete") + @Operation(summary = "删除特殊日期附加费用规则") + @Parameter(name = "id", description = "编号", required = true) + @PreAuthorize("@ss.hasPermission('tz:prod-additional-fee-dates:delete')") + public CommonResult deleteProdAdditionalFeeDates(@RequestParam("id") Long id) { + prodAdditionalFeeDatesService.deleteProdAdditionalFeeDates(id); + return success(true); + } + + @GetMapping("/get") + @Operation(summary = "获得特殊日期附加费用规则") + @Parameter(name = "id", description = "编号", required = true, example = "1024") + @PreAuthorize("@ss.hasPermission('tz:prod-additional-fee-dates:query')") + public CommonResult getProdAdditionalFeeDates(@RequestParam("id") Long id) { + ProdAdditionalFeeDatesDO prodAdditionalFeeDates = prodAdditionalFeeDatesService.getProdAdditionalFeeDates(id); + return success(BeanUtils.toBean(prodAdditionalFeeDates, ProdAdditionalFeeDatesRespVO.class)); + } + + @GetMapping("/page") + @Operation(summary = "获得特殊日期附加费用规则分页") + @PreAuthorize("@ss.hasPermission('tz:prod-additional-fee-dates:query')") + public CommonResult> getProdAdditionalFeeDatesPage(@Valid ProdAdditionalFeeDatesPageReqVO pageReqVO) { + PageResult pageResult = prodAdditionalFeeDatesService.getProdAdditionalFeeDatesPage(pageReqVO); + return success(BeanUtils.toBean(pageResult, ProdAdditionalFeeDatesRespVO.class)); + } + + @GetMapping("/export-excel") + @Operation(summary = "导出特殊日期附加费用规则 Excel") + @PreAuthorize("@ss.hasPermission('tz:prod-additional-fee-dates:export')") + @ApiAccessLog(operateType = EXPORT) + public void exportProdAdditionalFeeDatesExcel(@Valid ProdAdditionalFeeDatesPageReqVO pageReqVO, + HttpServletResponse response) throws IOException { + pageReqVO.setPageSize(PageParam.PAGE_SIZE_NONE); + List list = prodAdditionalFeeDatesService.getProdAdditionalFeeDatesPage(pageReqVO).getList(); + // 导出 Excel + ExcelUtils.write(response, "特殊日期附加费用规则.xls", "数据", ProdAdditionalFeeDatesRespVO.class, + BeanUtils.toBean(list, ProdAdditionalFeeDatesRespVO.class)); + } + +} \ No newline at end of file diff --git a/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/controller/admin/ProdAdditionalFeePeriodsController.java b/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/controller/admin/ProdAdditionalFeePeriodsController.java new file mode 100644 index 0000000..ad729e6 --- /dev/null +++ b/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/controller/admin/ProdAdditionalFeePeriodsController.java @@ -0,0 +1,93 @@ +package com.tashow.cloud.product.controller.admin; + +import com.tashow.cloud.common.pojo.CommonResult; +import com.tashow.cloud.common.pojo.PageParam; +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.common.util.object.BeanUtils; +import com.tashow.cloud.excel.excel.core.util.ExcelUtils; +import com.tashow.cloud.productapi.api.product.dto.ProdAdditionalFeePeriodsDO; +import com.tashow.cloud.product.service.ProdAdditionalFeePeriodsService; +import com.tashow.cloud.productapi.api.product.vo.prodadditionalfeeperiods.ProdAdditionalFeePeriodsPageReqVO; +import com.tashow.cloud.productapi.api.product.vo.prodadditionalfeeperiods.ProdAdditionalFeePeriodsRespVO; +import com.tashow.cloud.productapi.api.product.vo.prodadditionalfeeperiods.ProdAdditionalFeePeriodsSaveReqVO; +import com.tashow.cloud.web.apilog.core.annotation.ApiAccessLog; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.annotation.Resource; +import jakarta.servlet.http.HttpServletResponse; +import jakarta.validation.Valid; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import java.io.IOException; +import java.util.List; + +import static com.tashow.cloud.common.pojo.CommonResult.success; +import static com.tashow.cloud.web.apilog.core.enums.OperateTypeEnum.EXPORT; + +@Tag(name = "管理后台 - 特殊时段附加费用规则") +@RestController +@RequestMapping("/tz/prod-additional-fee-periods") +@Validated +public class ProdAdditionalFeePeriodsController { + + @Resource + private ProdAdditionalFeePeriodsService prodAdditionalFeePeriodsService; + + @PostMapping("/create") + @Operation(summary = "创建特殊时段附加费用规则") + @PreAuthorize("@ss.hasPermission('tz:prod-additional-fee-periods:create')") + public CommonResult createProdAdditionalFeePeriods(@Valid @RequestBody ProdAdditionalFeePeriodsSaveReqVO createReqVO) { + return success(prodAdditionalFeePeriodsService.createProdAdditionalFeePeriods(createReqVO)); + } + + @PutMapping("/update") + @Operation(summary = "更新特殊时段附加费用规则") + @PreAuthorize("@ss.hasPermission('tz:prod-additional-fee-periods:update')") + public CommonResult updateProdAdditionalFeePeriods(@Valid @RequestBody ProdAdditionalFeePeriodsSaveReqVO updateReqVO) { + prodAdditionalFeePeriodsService.updateProdAdditionalFeePeriods(updateReqVO); + return success(true); + } + + @DeleteMapping("/delete") + @Operation(summary = "删除特殊时段附加费用规则") + @Parameter(name = "id", description = "编号", required = true) + @PreAuthorize("@ss.hasPermission('tz:prod-additional-fee-periods:delete')") + public CommonResult deleteProdAdditionalFeePeriods(@RequestParam("id") Long id) { + prodAdditionalFeePeriodsService.deleteProdAdditionalFeePeriods(id); + return success(true); + } + + @GetMapping("/get") + @Operation(summary = "获得特殊时段附加费用规则") + @Parameter(name = "id", description = "编号", required = true, example = "1024") + @PreAuthorize("@ss.hasPermission('tz:prod-additional-fee-periods:query')") + public CommonResult getProdAdditionalFeePeriods(@RequestParam("id") Long id) { + ProdAdditionalFeePeriodsDO prodAdditionalFeePeriods = prodAdditionalFeePeriodsService.getProdAdditionalFeePeriods(id); + return success(BeanUtils.toBean(prodAdditionalFeePeriods, ProdAdditionalFeePeriodsRespVO.class)); + } + + @GetMapping("/page") + @Operation(summary = "获得特殊时段附加费用规则分页") + @PreAuthorize("@ss.hasPermission('tz:prod-additional-fee-periods:query')") + public CommonResult> getProdAdditionalFeePeriodsPage(@Valid ProdAdditionalFeePeriodsPageReqVO pageReqVO) { + PageResult pageResult = prodAdditionalFeePeriodsService.getProdAdditionalFeePeriodsPage(pageReqVO); + return success(BeanUtils.toBean(pageResult, ProdAdditionalFeePeriodsRespVO.class)); + } + + @GetMapping("/export-excel") + @Operation(summary = "导出特殊时段附加费用规则 Excel") + @PreAuthorize("@ss.hasPermission('tz:prod-additional-fee-periods:export')") + @ApiAccessLog(operateType = EXPORT) + public void exportProdAdditionalFeePeriodsExcel(@Valid ProdAdditionalFeePeriodsPageReqVO pageReqVO, + HttpServletResponse response) throws IOException { + pageReqVO.setPageSize(PageParam.PAGE_SIZE_NONE); + List list = prodAdditionalFeePeriodsService.getProdAdditionalFeePeriodsPage(pageReqVO).getList(); + // 导出 Excel + ExcelUtils.write(response, "特殊时段附加费用规则.xls", "数据", ProdAdditionalFeePeriodsRespVO.class, + BeanUtils.toBean(list, ProdAdditionalFeePeriodsRespVO.class)); + } + +} \ No newline at end of file diff --git a/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/controller/admin/ProdController.java b/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/controller/admin/ProdController.java new file mode 100644 index 0000000..93a7716 --- /dev/null +++ b/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/controller/admin/ProdController.java @@ -0,0 +1,161 @@ +package com.tashow.cloud.product.controller.admin; + +import com.tashow.cloud.common.pojo.CommonResult; +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.product.mapper.ProdMapper; +import com.tashow.cloud.productapi.api.product.dto.ProdDO; +import com.tashow.cloud.product.service.ProdService; +import com.tashow.cloud.productapi.api.product.dto.SkuDO; +import com.tashow.cloud.productapi.api.product.vo.prod.*; +import com.tashow.cloud.productapi.api.product.vo.sku.SkuPageReqVO; +import com.tashow.cloud.productapi.api.product.vo.sku.SkuRecycleBinVO; +import com.tashow.cloud.productapi.enums.BaseEnum; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.annotation.Resource; +import jakarta.annotation.security.PermitAll; +import jakarta.validation.Valid; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import java.util.Date; +import java.util.List; + +import static com.tashow.cloud.common.pojo.CommonResult.success; + +@Tag(name = "管理后台 - 商品") +@RestController +@RequestMapping("/product/prod") +@Validated +public class ProdController { + + @Resource + private ProdService prodService; + @Resource + private ProdMapper prodMapper; + @PostMapping("/create") + @Operation(summary = "创建商品") + @PermitAll + public CommonResult createProd(@Valid @RequestBody ProdSaveReqVO createReqVO) { + return success(prodService.createProd(createReqVO)); + } + + + + @PostMapping("/createProdService") + @Operation(summary = "创建商品服务配置") + @PermitAll + public CommonResult createProdService(@Valid @RequestBody ProdServiceVO prodServiceVO) { + prodService.createProdService(prodServiceVO); + return success(true); + } + + @PostMapping("/uptateProdService") + @Operation(summary = "修改商品服务配置") + @PermitAll + public CommonResult uptateProdService(@Valid @RequestBody ProdServiceInfoVO prodServiceInfoVO) { + prodService.uptateProdService(prodServiceInfoVO); + return success(true); + } + + @GetMapping("/getProdService") + @Operation(summary = "获得商品服务信息") + @Parameter(name = "prodId", description = "商品id", required = true, example = "1024") + @PermitAll + public CommonResult getProd(@RequestParam("prodId") Long prodId) { + ProdServiceVO prodServiceVO = prodService.getProdService(prodId); + return success(prodServiceVO); + } + + + @PutMapping("/update") + @Operation(summary = "更新商品") + @PermitAll + public CommonResult updateProd(@RequestBody ProdSaveReqVO updateReqVO) { + prodService.updateProd(updateReqVO); + return success(true); + } + + @DeleteMapping("/delete") + @Operation(summary = "删除商品") + @PermitAll + @Parameter(name = "id", description = "编号", required = true) + public CommonResult deleteProd(@RequestParam("id") Long id) { + prodService.deleteProd(id); + return success(true); + } + + + + + @DeleteMapping("/deleteSkuList") + @Operation(summary = "批量删除商品") + @Parameter(name = "ids", description = "商品id", required = true) + @PermitAll + public CommonResult deleteSkuList(@RequestParam("ids") List ids) { + for(Long id:ids){ + ProdDO prod = new ProdDO(); + prod.setProdId(id); + prod.setDeleted(BaseEnum.YES_ONE.getKey()); + prod.setDeleteTime(new Date()); + // 删除 + prodMapper.deleteById(prod); + } + return success(true); + } + + @DeleteMapping("/updateSkuShelfList") + @Operation(summary = "批量上下架") + @Parameter(name = "status", description = "默认是1,正常状态(出售中), 0:下架(仓库中) 2:待审核", required = true) + @PermitAll + public CommonResult updateSkuShelfList(@RequestParam("ids") List ids,@RequestParam("status") Integer status) { + for(Long id:ids){ + ProdDO prod = new ProdDO(); + prod.setProdId(id); + prod.setStatus(status); + prodMapper.updateById(prod); + } + return success(true); + } + + + +/* + @GetMapping("/get") + @Operation(summary = "获得商品") + @Parameter(name = "id", description = "编号", required = true, example = "1024") + @PreAuthorize("@ss.hasPermission('tashow-module-product:prod:query')") + public CommonResult getProd(@RequestParam("id") Long id) { + ProdDO prod = prodService.getProd(id); + return success(BeanUtils.toBean(prod, ProdRespVO.class)); + }*/ + + @PermitAll + @GetMapping("/page") + @Operation(summary = "获得商品分页") + public CommonResult> getProdPage(ProdPageReqVO pageReqVO) { + PageResult pageResult = prodService.getProdPage(pageReqVO); + return success(pageResult); + } + + @PermitAll + @GetMapping("/getProdRecycleBinPageList") + @Operation(summary = "获得商品回收站分页列表") + public CommonResult> getProdRecycleBinPageList(ProdRecycleBinVO prodRecycleBinVO) { + PageResult pageResult = prodService.getProdRecycleBinPageList(prodRecycleBinVO); + return success(pageResult); + } + + + @PostMapping("/restoreProdList") + @Operation(summary = "恢复商品") + @Parameter(name = "ids", description = "商品id集合", required = true) + @PermitAll + public CommonResult restoreProdList(@RequestParam("ids") List ids) { + prodService.restoreProdList(ids); + return success(true); + } + + +} \ No newline at end of file diff --git a/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/controller/admin/ProdEmergencyResponseController.java b/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/controller/admin/ProdEmergencyResponseController.java new file mode 100644 index 0000000..c84de7f --- /dev/null +++ b/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/controller/admin/ProdEmergencyResponseController.java @@ -0,0 +1,94 @@ +package com.tashow.cloud.product.controller.admin; + +import com.tashow.cloud.common.pojo.CommonResult; +import com.tashow.cloud.common.pojo.PageParam; +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.common.util.object.BeanUtils; +import com.tashow.cloud.excel.excel.core.util.ExcelUtils; +import com.tashow.cloud.productapi.api.product.dto.ProdEmergencyResponseDO; +import com.tashow.cloud.product.service.ProdEmergencyResponseService; +import com.tashow.cloud.productapi.api.product.vo.prodemergencyresponse.ProdEmergencyResponsePageReqVO; +import com.tashow.cloud.productapi.api.product.vo.prodemergencyresponse.ProdEmergencyResponseRespVO; +import com.tashow.cloud.productapi.api.product.vo.prodemergencyresponse.ProdEmergencyResponseSaveReqVO; +import com.tashow.cloud.web.apilog.core.annotation.ApiAccessLog; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.annotation.Resource; +import jakarta.servlet.http.HttpServletResponse; +import jakarta.validation.Valid; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import java.io.IOException; +import java.util.List; + +import static com.tashow.cloud.common.pojo.CommonResult.success; +import static com.tashow.cloud.web.apilog.core.enums.OperateTypeEnum.EXPORT; + + +@Tag(name = "管理后台 - 商品紧急响应服务设置") +@RestController +@RequestMapping("/tz/prod-emergency-response") +@Validated +public class ProdEmergencyResponseController { + + @Resource + private ProdEmergencyResponseService prodEmergencyResponseService; + + @PostMapping("/create") + @Operation(summary = "创建商品紧急响应服务设置") + @PreAuthorize("@ss.hasPermission('tz:prod-emergency-response:create')") + public CommonResult createProdEmergencyResponse(@Valid @RequestBody ProdEmergencyResponseSaveReqVO createReqVO) { + return success(prodEmergencyResponseService.createProdEmergencyResponse(createReqVO)); + } + + @PutMapping("/update") + @Operation(summary = "更新商品紧急响应服务设置") + @PreAuthorize("@ss.hasPermission('tz:prod-emergency-response:update')") + public CommonResult updateProdEmergencyResponse(@Valid @RequestBody ProdEmergencyResponseSaveReqVO updateReqVO) { + prodEmergencyResponseService.updateProdEmergencyResponse(updateReqVO); + return success(true); + } + + @DeleteMapping("/delete") + @Operation(summary = "删除商品紧急响应服务设置") + @Parameter(name = "id", description = "编号", required = true) + @PreAuthorize("@ss.hasPermission('tz:prod-emergency-response:delete')") + public CommonResult deleteProdEmergencyResponse(@RequestParam("id") Long id) { + prodEmergencyResponseService.deleteProdEmergencyResponse(id); + return success(true); + } + + @GetMapping("/get") + @Operation(summary = "获得商品紧急响应服务设置") + @Parameter(name = "id", description = "编号", required = true, example = "1024") + @PreAuthorize("@ss.hasPermission('tz:prod-emergency-response:query')") + public CommonResult getProdEmergencyResponse(@RequestParam("id") Long id) { + ProdEmergencyResponseDO prodEmergencyResponse = prodEmergencyResponseService.getProdEmergencyResponse(id); + return success(BeanUtils.toBean(prodEmergencyResponse, ProdEmergencyResponseRespVO.class)); + } + + @GetMapping("/page") + @Operation(summary = "获得商品紧急响应服务设置分页") + @PreAuthorize("@ss.hasPermission('tz:prod-emergency-response:query')") + public CommonResult> getProdEmergencyResponsePage(@Valid ProdEmergencyResponsePageReqVO pageReqVO) { + PageResult pageResult = prodEmergencyResponseService.getProdEmergencyResponsePage(pageReqVO); + return success(BeanUtils.toBean(pageResult, ProdEmergencyResponseRespVO.class)); + } + + @GetMapping("/export-excel") + @Operation(summary = "导出商品紧急响应服务设置 Excel") + @PreAuthorize("@ss.hasPermission('tz:prod-emergency-response:export')") + @ApiAccessLog(operateType = EXPORT) + public void exportProdEmergencyResponseExcel(@Valid ProdEmergencyResponsePageReqVO pageReqVO, + HttpServletResponse response) throws IOException { + pageReqVO.setPageSize(PageParam.PAGE_SIZE_NONE); + List list = prodEmergencyResponseService.getProdEmergencyResponsePage(pageReqVO).getList(); + // 导出 Excel + ExcelUtils.write(response, "商品紧急响应服务设置.xls", "数据", ProdEmergencyResponseRespVO.class, + BeanUtils.toBean(list, ProdEmergencyResponseRespVO.class)); + } + +} \ No newline at end of file diff --git a/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/controller/admin/ProdEmergencyResponseIntervalsController.java b/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/controller/admin/ProdEmergencyResponseIntervalsController.java new file mode 100644 index 0000000..da364ac --- /dev/null +++ b/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/controller/admin/ProdEmergencyResponseIntervalsController.java @@ -0,0 +1,93 @@ +package com.tashow.cloud.product.controller.admin; + +import com.tashow.cloud.common.pojo.CommonResult; +import com.tashow.cloud.common.pojo.PageParam; +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.common.util.object.BeanUtils; +import com.tashow.cloud.excel.excel.core.util.ExcelUtils; +import com.tashow.cloud.productapi.api.product.dto.ProdEmergencyResponseIntervalsDO; +import com.tashow.cloud.product.service.ProdEmergencyResponseIntervalsService; +import com.tashow.cloud.productapi.api.product.vo.prodemergencyresponseintervals.ProdEmergencyResponseIntervalsPageReqVO; +import com.tashow.cloud.productapi.api.product.vo.prodemergencyresponseintervals.ProdEmergencyResponseIntervalsRespVO; +import com.tashow.cloud.productapi.api.product.vo.prodemergencyresponseintervals.ProdEmergencyResponseIntervalsSaveReqVO; +import com.tashow.cloud.web.apilog.core.annotation.ApiAccessLog; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.annotation.Resource; +import jakarta.servlet.http.HttpServletResponse; +import jakarta.validation.Valid; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import java.io.IOException; +import java.util.List; + +import static com.tashow.cloud.common.pojo.CommonResult.success; +import static com.tashow.cloud.web.apilog.core.enums.OperateTypeEnum.EXPORT; + +@Tag(name = "管理后台 - 紧急响应时间区间设置") +@RestController +@RequestMapping("/tz/prod-emergency-response-intervals") +@Validated +public class ProdEmergencyResponseIntervalsController { + + @Resource + private ProdEmergencyResponseIntervalsService prodEmergencyResponseIntervalsService; + + @PostMapping("/create") + @Operation(summary = "创建紧急响应时间区间设置") + @PreAuthorize("@ss.hasPermission('tz:prod-emergency-response-intervals:create')") + public CommonResult createProdEmergencyResponseIntervals(@Valid @RequestBody ProdEmergencyResponseIntervalsSaveReqVO createReqVO) { + return success(prodEmergencyResponseIntervalsService.createProdEmergencyResponseIntervals(createReqVO)); + } + + @PutMapping("/update") + @Operation(summary = "更新紧急响应时间区间设置") + @PreAuthorize("@ss.hasPermission('tz:prod-emergency-response-intervals:update')") + public CommonResult updateProdEmergencyResponseIntervals(@Valid @RequestBody ProdEmergencyResponseIntervalsSaveReqVO updateReqVO) { + prodEmergencyResponseIntervalsService.updateProdEmergencyResponseIntervals(updateReqVO); + return success(true); + } + + @DeleteMapping("/delete") + @Operation(summary = "删除紧急响应时间区间设置") + @Parameter(name = "id", description = "编号", required = true) + @PreAuthorize("@ss.hasPermission('tz:prod-emergency-response-intervals:delete')") + public CommonResult deleteProdEmergencyResponseIntervals(@RequestParam("id") Long id) { + prodEmergencyResponseIntervalsService.deleteProdEmergencyResponseIntervals(id); + return success(true); + } + + @GetMapping("/get") + @Operation(summary = "获得紧急响应时间区间设置") + @Parameter(name = "id", description = "编号", required = true, example = "1024") + @PreAuthorize("@ss.hasPermission('tz:prod-emergency-response-intervals:query')") + public CommonResult getProdEmergencyResponseIntervals(@RequestParam("id") Long id) { + ProdEmergencyResponseIntervalsDO prodEmergencyResponseIntervals = prodEmergencyResponseIntervalsService.getProdEmergencyResponseIntervals(id); + return success(BeanUtils.toBean(prodEmergencyResponseIntervals, ProdEmergencyResponseIntervalsRespVO.class)); + } + + @GetMapping("/page") + @Operation(summary = "获得紧急响应时间区间设置分页") + @PreAuthorize("@ss.hasPermission('tz:prod-emergency-response-intervals:query')") + public CommonResult> getProdEmergencyResponseIntervalsPage(@Valid ProdEmergencyResponseIntervalsPageReqVO pageReqVO) { + PageResult pageResult = prodEmergencyResponseIntervalsService.getProdEmergencyResponseIntervalsPage(pageReqVO); + return success(BeanUtils.toBean(pageResult, ProdEmergencyResponseIntervalsRespVO.class)); + } + + @GetMapping("/export-excel") + @Operation(summary = "导出紧急响应时间区间设置 Excel") + @PreAuthorize("@ss.hasPermission('tz:prod-emergency-response-intervals:export')") + @ApiAccessLog(operateType = EXPORT) + public void exportProdEmergencyResponseIntervalsExcel(@Valid ProdEmergencyResponseIntervalsPageReqVO pageReqVO, + HttpServletResponse response) throws IOException { + pageReqVO.setPageSize(PageParam.PAGE_SIZE_NONE); + List list = prodEmergencyResponseIntervalsService.getProdEmergencyResponseIntervalsPage(pageReqVO).getList(); + // 导出 Excel + ExcelUtils.write(response, "紧急响应时间区间设置.xls", "数据", ProdEmergencyResponseIntervalsRespVO.class, + BeanUtils.toBean(list, ProdEmergencyResponseIntervalsRespVO.class)); + } + +} \ No newline at end of file diff --git a/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/controller/admin/ProdPropController.java b/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/controller/admin/ProdPropController.java new file mode 100644 index 0000000..f9a4924 --- /dev/null +++ b/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/controller/admin/ProdPropController.java @@ -0,0 +1,121 @@ +package com.tashow.cloud.product.controller.admin; + +import cn.hutool.core.util.StrUtil; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.tashow.cloud.common.pojo.CommonResult; +import com.tashow.cloud.productapi.api.product.dto.ProdPropDO; +import com.tashow.cloud.productapi.api.product.dto.ProdPropValueDO; +import com.tashow.cloud.product.service.ProdPropService; +import com.tashow.cloud.product.service.ProdPropValueService; +import com.tashow.cloud.productapi.api.product.vo.prodprop.ProdPropRespVO; +import com.tashow.cloud.productapi.enums.ProdPropRule; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.annotation.Resource; +import jakarta.validation.Valid; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.util.List; + +import static com.tashow.cloud.common.pojo.CommonResult.success; + +@Tag(name = "管理后台 - 商品属性") +@RestController +@RequestMapping("/tz/prod-prop") +@Validated +public class ProdPropController { + + @Resource + private ProdPropService prodPropService; + @Resource + private ProdPropValueService prodPropValueService; + + /* @PostMapping("/create") + @Operation(summary = "创建商品属性") + @PreAuthorize("@ss.hasPermission('tz:prod-prop:create')") + public CommonResult createProdProp(@Valid @RequestBody ProdPropSaveReqVO createReqVO) { + prodPropService.saveProdPropAndValues(createReqVO); + return success(true); + }*/ + + + @GetMapping("/getProdPropList") + @Operation(summary = "获得商品属性列表") + public CommonResult> getProdPropList(@Valid ProdPropRespVO pageReqVO) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + // 处理规格名称模糊查询 + if (StrUtil.isNotBlank(pageReqVO.getPropName())) { + wrapper.like(ProdPropDO::getPropName, pageReqVO.getPropName()); + } + wrapper.eq(ProdPropDO::getRule, ProdPropRule.SPEC.value()); + //TODO 获取当前登录用户 + wrapper.eq(ProdPropDO::getShopId, 1L); + /*// 获取当前登录用户 + Long userId = SecurityUtils.getUserId(); + SysUser sysUser = sysUserService.selectUserById(userId); + + if (sysUser.getShopId() != 100) { + wrapper.eq(ProdProp::getShopId, sysUser.getShopId()); + }*/ + List list = prodPropService.list(wrapper); + list.forEach(prop -> { + List values = prodPropValueService.list( + new LambdaQueryWrapper() + .eq(ProdPropValueDO::getPropId, prop.getPropId()) + ); + prop.setProdPropValues(values); + }); + return success(list); + } + +/* @PutMapping("/update") + @Operation(summary = "更新商品属性") + @PreAuthorize("@ss.hasPermission('tz:prod-prop:update')") + public CommonResult updateProdProp(@Valid @RequestBody ProdPropSaveReqVO updateReqVO) { + prodPropService.updateProdProp(updateReqVO); + return success(true); + } + + @DeleteMapping("/delete") + @Operation(summary = "删除商品属性") + @Parameter(name = "id", description = "编号", required = true) + @PreAuthorize("@ss.hasPermission('tz:prod-prop:delete')") + public CommonResult deleteProdProp(@RequestParam("id") Long id) { + prodPropService.deleteProdProp(id); + return success(true); + } + + @GetMapping("/get") + @Operation(summary = "获得商品属性") + @Parameter(name = "id", description = "编号", required = true, example = "1024") + @PreAuthorize("@ss.hasPermission('tz:prod-prop:query')") + public CommonResult getProdProp(@RequestParam("id") Long id) { + ProdPropDO prodProp = prodPropService.getProdProp(id); + return success(BeanUtils.toBean(prodProp, ProdPropRespVO.class)); + } + + @GetMapping("/page") + @Operation(summary = "获得商品属性分页") + @PreAuthorize("@ss.hasPermission('tz:prod-prop:query')") + public CommonResult> getProdPropPage(@Valid ProdPropPageReqVO pageReqVO) { + PageResult pageResult = prodPropService.getProdPropPage(pageReqVO); + return success(BeanUtils.toBean(pageResult, ProdPropRespVO.class)); + } + + @GetMapping("/export-excel") + @Operation(summary = "导出商品属性 Excel") + @PreAuthorize("@ss.hasPermission('tz:prod-prop:export')") + @ApiAccessLog(operateType = EXPORT) + public void exportProdPropExcel(@Valid ProdPropPageReqVO pageReqVO, + HttpServletResponse response) throws IOException { + pageReqVO.setPageSize(PageParam.PAGE_SIZE_NONE); + List list = prodPropService.getProdPropPage(pageReqVO).getList(); + // 导出 Excel + ExcelUtils.write(response, "商品属性.xls", "数据", ProdPropRespVO.class, + BeanUtils.toBean(list, ProdPropRespVO.class)); + }*/ + +} \ No newline at end of file diff --git a/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/controller/admin/ProdPropValueController.java b/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/controller/admin/ProdPropValueController.java new file mode 100644 index 0000000..cd2e549 --- /dev/null +++ b/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/controller/admin/ProdPropValueController.java @@ -0,0 +1,94 @@ +package com.tashow.cloud.product.controller.admin; + +import com.tashow.cloud.common.pojo.CommonResult; +import com.tashow.cloud.common.pojo.PageParam; +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.common.util.object.BeanUtils; +import com.tashow.cloud.excel.excel.core.util.ExcelUtils; +import com.tashow.cloud.productapi.api.product.dto.ProdPropValueDO; +import com.tashow.cloud.product.service.ProdPropValueService; +import com.tashow.cloud.productapi.api.product.vo.prodpropvalue.ProdPropValuePageReqVO; +import com.tashow.cloud.productapi.api.product.vo.prodpropvalue.ProdPropValueRespVO; +import com.tashow.cloud.productapi.api.product.vo.prodpropvalue.ProdPropValueSaveReqVO; +import com.tashow.cloud.web.apilog.core.annotation.ApiAccessLog; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.annotation.Resource; +import jakarta.servlet.http.HttpServletResponse; +import jakarta.validation.Valid; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import java.io.IOException; +import java.util.List; + +import static com.tashow.cloud.common.pojo.CommonResult.success; +import static com.tashow.cloud.web.apilog.core.enums.OperateTypeEnum.EXPORT; + + +@Tag(name = "管理后台 - 属性规则") +@RestController +@RequestMapping("/tz/prod-prop-value") +@Validated +public class ProdPropValueController { + + @Resource + private ProdPropValueService prodPropValueService; + + @PostMapping("/create") + @Operation(summary = "创建属性规则") + @PreAuthorize("@ss.hasPermission('tz:prod-prop-value:create')") + public CommonResult createProdPropValue(@Valid @RequestBody ProdPropValueSaveReqVO createReqVO) { + return success(prodPropValueService.createProdPropValue(createReqVO)); + } + + @PutMapping("/update") + @Operation(summary = "更新属性规则") + @PreAuthorize("@ss.hasPermission('tz:prod-prop-value:update')") + public CommonResult updateProdPropValue(@Valid @RequestBody ProdPropValueSaveReqVO updateReqVO) { + prodPropValueService.updateProdPropValue(updateReqVO); + return success(true); + } + + @DeleteMapping("/delete") + @Operation(summary = "删除属性规则") + @Parameter(name = "id", description = "编号", required = true) + @PreAuthorize("@ss.hasPermission('tz:prod-prop-value:delete')") + public CommonResult deleteProdPropValue(@RequestParam("id") Long id) { + prodPropValueService.deleteProdPropValue(id); + return success(true); + } + + @GetMapping("/get") + @Operation(summary = "获得属性规则") + @Parameter(name = "id", description = "编号", required = true, example = "1024") + @PreAuthorize("@ss.hasPermission('tz:prod-prop-value:query')") + public CommonResult getProdPropValue(@RequestParam("id") Long id) { + ProdPropValueDO prodPropValue = prodPropValueService.getProdPropValue(id); + return success(BeanUtils.toBean(prodPropValue, ProdPropValueRespVO.class)); + } + + @GetMapping("/page") + @Operation(summary = "获得属性规则分页") + @PreAuthorize("@ss.hasPermission('tz:prod-prop-value:query')") + public CommonResult> getProdPropValuePage(@Valid ProdPropValuePageReqVO pageReqVO) { + PageResult pageResult = prodPropValueService.getProdPropValuePage(pageReqVO); + return success(BeanUtils.toBean(pageResult, ProdPropValueRespVO.class)); + } + + @GetMapping("/export-excel") + @Operation(summary = "导出属性规则 Excel") + @PreAuthorize("@ss.hasPermission('tz:prod-prop-value:export')") + @ApiAccessLog(operateType = EXPORT) + public void exportProdPropValueExcel(@Valid ProdPropValuePageReqVO pageReqVO, + HttpServletResponse response) throws IOException { + pageReqVO.setPageSize(PageParam.PAGE_SIZE_NONE); + List list = prodPropValueService.getProdPropValuePage(pageReqVO).getList(); + // 导出 Excel + ExcelUtils.write(response, "属性规则.xls", "数据", ProdPropValueRespVO.class, + BeanUtils.toBean(list, ProdPropValueRespVO.class)); + } + +} \ No newline at end of file diff --git a/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/controller/admin/ProdReservationConfigController.java b/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/controller/admin/ProdReservationConfigController.java new file mode 100644 index 0000000..99091c6 --- /dev/null +++ b/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/controller/admin/ProdReservationConfigController.java @@ -0,0 +1,93 @@ +package com.tashow.cloud.product.controller.admin; + +import com.tashow.cloud.common.pojo.CommonResult; +import com.tashow.cloud.common.pojo.PageParam; +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.common.util.object.BeanUtils; +import com.tashow.cloud.excel.excel.core.util.ExcelUtils; +import com.tashow.cloud.productapi.api.product.dto.ProdReservationConfigDO; +import com.tashow.cloud.product.service.ProdReservationConfigService; +import com.tashow.cloud.productapi.api.product.vo.prodreservationconfig.ProdReservationConfigPageReqVO; +import com.tashow.cloud.productapi.api.product.vo.prodreservationconfig.ProdReservationConfigRespVO; +import com.tashow.cloud.productapi.api.product.vo.prodreservationconfig.ProdReservationConfigSaveReqVO; +import com.tashow.cloud.web.apilog.core.annotation.ApiAccessLog; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.annotation.Resource; +import jakarta.servlet.http.HttpServletResponse; +import jakarta.validation.Valid; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import java.io.IOException; +import java.util.List; + +import static com.tashow.cloud.common.pojo.CommonResult.success; +import static com.tashow.cloud.web.apilog.core.enums.OperateTypeEnum.EXPORT; + +@Tag(name = "管理后台 - 商品预约配置") +@RestController +@RequestMapping("/tz/prod-reservation-config") +@Validated +public class ProdReservationConfigController { + + @Resource + private ProdReservationConfigService prodReservationConfigService; + + @PostMapping("/create") + @Operation(summary = "创建商品预约配置") + @PreAuthorize("@ss.hasPermission('tz:prod-reservation-config:create')") + public CommonResult createProdReservationConfig(@Valid @RequestBody ProdReservationConfigSaveReqVO createReqVO) { + return success(prodReservationConfigService.createProdReservationConfig(createReqVO)); + } + + @PutMapping("/update") + @Operation(summary = "更新商品预约配置") + @PreAuthorize("@ss.hasPermission('tz:prod-reservation-config:update')") + public CommonResult updateProdReservationConfig(@Valid @RequestBody ProdReservationConfigSaveReqVO updateReqVO) { + prodReservationConfigService.updateProdReservationConfig(updateReqVO); + return success(true); + } + + @DeleteMapping("/delete") + @Operation(summary = "删除商品预约配置") + @Parameter(name = "id", description = "编号", required = true) + @PreAuthorize("@ss.hasPermission('tz:prod-reservation-config:delete')") + public CommonResult deleteProdReservationConfig(@RequestParam("id") Long id) { + prodReservationConfigService.deleteProdReservationConfig(id); + return success(true); + } + + @GetMapping("/get") + @Operation(summary = "获得商品预约配置") + @Parameter(name = "id", description = "编号", required = true, example = "1024") + @PreAuthorize("@ss.hasPermission('tz:prod-reservation-config:query')") + public CommonResult getProdReservationConfig(@RequestParam("id") Long id) { + ProdReservationConfigDO prodReservationConfig = prodReservationConfigService.getProdReservationConfig(id); + return success(BeanUtils.toBean(prodReservationConfig, ProdReservationConfigRespVO.class)); + } + + @GetMapping("/page") + @Operation(summary = "获得商品预约配置分页") + @PreAuthorize("@ss.hasPermission('tz:prod-reservation-config:query')") + public CommonResult> getProdReservationConfigPage(@Valid ProdReservationConfigPageReqVO pageReqVO) { + PageResult pageResult = prodReservationConfigService.getProdReservationConfigPage(pageReqVO); + return success(BeanUtils.toBean(pageResult, ProdReservationConfigRespVO.class)); + } + + @GetMapping("/export-excel") + @Operation(summary = "导出商品预约配置 Excel") + @PreAuthorize("@ss.hasPermission('tz:prod-reservation-config:export')") + @ApiAccessLog(operateType = EXPORT) + public void exportProdReservationConfigExcel(@Valid ProdReservationConfigPageReqVO pageReqVO, + HttpServletResponse response) throws IOException { + pageReqVO.setPageSize(PageParam.PAGE_SIZE_NONE); + List list = prodReservationConfigService.getProdReservationConfigPage(pageReqVO).getList(); + // 导出 Excel + ExcelUtils.write(response, "商品预约配置.xls", "数据", ProdReservationConfigRespVO.class, + BeanUtils.toBean(list, ProdReservationConfigRespVO.class)); + } + +} \ No newline at end of file diff --git a/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/controller/admin/ProdServiceAreaRelevanceController.java b/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/controller/admin/ProdServiceAreaRelevanceController.java new file mode 100644 index 0000000..880eb54 --- /dev/null +++ b/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/controller/admin/ProdServiceAreaRelevanceController.java @@ -0,0 +1,93 @@ +package com.tashow.cloud.product.controller.admin; + +import com.tashow.cloud.common.pojo.CommonResult; +import com.tashow.cloud.common.pojo.PageParam; +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.common.util.object.BeanUtils; +import com.tashow.cloud.excel.excel.core.util.ExcelUtils; +import com.tashow.cloud.productapi.api.product.dto.ProdServiceAreaRelevanceDO; +import com.tashow.cloud.product.service.ProdServiceAreaRelevanceService; +import com.tashow.cloud.productapi.api.product.vo.prodservicearearelevance.ProdServiceAreaRelevancePageReqVO; +import com.tashow.cloud.productapi.api.product.vo.prodservicearearelevance.ProdServiceAreaRelevanceRespVO; +import com.tashow.cloud.productapi.api.product.vo.prodservicearearelevance.ProdServiceAreaRelevanceSaveReqVO; +import com.tashow.cloud.web.apilog.core.annotation.ApiAccessLog; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.annotation.Resource; +import jakarta.servlet.http.HttpServletResponse; +import jakarta.validation.Valid; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import java.io.IOException; +import java.util.List; + +import static com.tashow.cloud.common.pojo.CommonResult.success; +import static com.tashow.cloud.web.apilog.core.enums.OperateTypeEnum.EXPORT; + +@Tag(name = "管理后台 - 商品与服务区域关联") +@RestController +@RequestMapping("/tz/prod-service-area-relevance") +@Validated +public class ProdServiceAreaRelevanceController { + + @Resource + private ProdServiceAreaRelevanceService prodServiceAreaRelevanceService; + + @PostMapping("/create") + @Operation(summary = "创建商品与服务区域关联") + @PreAuthorize("@ss.hasPermission('tz:prod-service-area-relevance:create')") + public CommonResult createProdServiceAreaRelevance(@Valid @RequestBody ProdServiceAreaRelevanceSaveReqVO createReqVO) { + return success(prodServiceAreaRelevanceService.createProdServiceAreaRelevance(createReqVO)); + } + + @PutMapping("/update") + @Operation(summary = "更新商品与服务区域关联") + @PreAuthorize("@ss.hasPermission('tz:prod-service-area-relevance:update')") + public CommonResult updateProdServiceAreaRelevance(@Valid @RequestBody ProdServiceAreaRelevanceSaveReqVO updateReqVO) { + prodServiceAreaRelevanceService.updateProdServiceAreaRelevance(updateReqVO); + return success(true); + } + + @DeleteMapping("/delete") + @Operation(summary = "删除商品与服务区域关联") + @Parameter(name = "id", description = "编号", required = true) + @PreAuthorize("@ss.hasPermission('tz:prod-service-area-relevance:delete')") + public CommonResult deleteProdServiceAreaRelevance(@RequestParam("id") Long id) { + prodServiceAreaRelevanceService.deleteProdServiceAreaRelevance(id); + return success(true); + } + + @GetMapping("/get") + @Operation(summary = "获得商品与服务区域关联") + @Parameter(name = "id", description = "编号", required = true, example = "1024") + @PreAuthorize("@ss.hasPermission('tz:prod-service-area-relevance:query')") + public CommonResult getProdServiceAreaRelevance(@RequestParam("id") Long id) { + ProdServiceAreaRelevanceDO prodServiceAreaRelevance = prodServiceAreaRelevanceService.getProdServiceAreaRelevance(id); + return success(BeanUtils.toBean(prodServiceAreaRelevance, ProdServiceAreaRelevanceRespVO.class)); + } + + @GetMapping("/page") + @Operation(summary = "获得商品与服务区域关联分页") + @PreAuthorize("@ss.hasPermission('tz:prod-service-area-relevance:query')") + public CommonResult> getProdServiceAreaRelevancePage(@Valid ProdServiceAreaRelevancePageReqVO pageReqVO) { + PageResult pageResult = prodServiceAreaRelevanceService.getProdServiceAreaRelevancePage(pageReqVO); + return success(BeanUtils.toBean(pageResult, ProdServiceAreaRelevanceRespVO.class)); + } + + @GetMapping("/export-excel") + @Operation(summary = "导出商品与服务区域关联 Excel") + @PreAuthorize("@ss.hasPermission('tz:prod-service-area-relevance:export')") + @ApiAccessLog(operateType = EXPORT) + public void exportProdServiceAreaRelevanceExcel(@Valid ProdServiceAreaRelevancePageReqVO pageReqVO, + HttpServletResponse response) throws IOException { + pageReqVO.setPageSize(PageParam.PAGE_SIZE_NONE); + List list = prodServiceAreaRelevanceService.getProdServiceAreaRelevancePage(pageReqVO).getList(); + // 导出 Excel + ExcelUtils.write(response, "商品与服务区域关联.xls", "数据", ProdServiceAreaRelevanceRespVO.class, + BeanUtils.toBean(list, ProdServiceAreaRelevanceRespVO.class)); + } + +} \ No newline at end of file diff --git a/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/controller/admin/ProdServiceAreasController.java b/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/controller/admin/ProdServiceAreasController.java new file mode 100644 index 0000000..ba61dd9 --- /dev/null +++ b/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/controller/admin/ProdServiceAreasController.java @@ -0,0 +1,93 @@ +package com.tashow.cloud.product.controller.admin; + +import com.tashow.cloud.common.pojo.CommonResult; +import com.tashow.cloud.common.pojo.PageParam; +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.common.util.object.BeanUtils; +import com.tashow.cloud.excel.excel.core.util.ExcelUtils; +import com.tashow.cloud.productapi.api.product.dto.ProdServiceAreasDO; +import com.tashow.cloud.product.service.ProdServiceAreasService; +import com.tashow.cloud.productapi.api.product.vo.prodserviceareas.ProdServiceAreasPageReqVO; +import com.tashow.cloud.productapi.api.product.vo.prodserviceareas.ProdServiceAreasRespVO; +import com.tashow.cloud.productapi.api.product.vo.prodserviceareas.ProdServiceAreasSaveReqVO; +import com.tashow.cloud.web.apilog.core.annotation.ApiAccessLog; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.annotation.Resource; +import jakarta.servlet.http.HttpServletResponse; +import jakarta.validation.Valid; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import java.io.IOException; +import java.util.List; + +import static com.tashow.cloud.common.pojo.CommonResult.success; +import static com.tashow.cloud.web.apilog.core.enums.OperateTypeEnum.EXPORT; + +@Tag(name = "管理后台 - 服务区域") +@RestController +@RequestMapping("/tz/prod-service-areas") +@Validated +public class ProdServiceAreasController { + + @Resource + private ProdServiceAreasService prodServiceAreasService; + + @PostMapping("/create") + @Operation(summary = "创建服务区域") + @PreAuthorize("@ss.hasPermission('tz:prod-service-areas:create')") + public CommonResult createProdServiceAreas(@Valid @RequestBody ProdServiceAreasSaveReqVO createReqVO) { + return success(prodServiceAreasService.createProdServiceAreas(createReqVO)); + } + + @PutMapping("/update") + @Operation(summary = "更新服务区域") + @PreAuthorize("@ss.hasPermission('tz:prod-service-areas:update')") + public CommonResult updateProdServiceAreas(@Valid @RequestBody ProdServiceAreasSaveReqVO updateReqVO) { + prodServiceAreasService.updateProdServiceAreas(updateReqVO); + return success(true); + } + + @DeleteMapping("/delete") + @Operation(summary = "删除服务区域") + @Parameter(name = "id", description = "编号", required = true) + @PreAuthorize("@ss.hasPermission('tz:prod-service-areas:delete')") + public CommonResult deleteProdServiceAreas(@RequestParam("id") Long id) { + prodServiceAreasService.deleteProdServiceAreas(id); + return success(true); + } + + @GetMapping("/get") + @Operation(summary = "获得服务区域") + @Parameter(name = "id", description = "编号", required = true, example = "1024") + @PreAuthorize("@ss.hasPermission('tz:prod-service-areas:query')") + public CommonResult getProdServiceAreas(@RequestParam("id") Long id) { + ProdServiceAreasDO prodServiceAreas = prodServiceAreasService.getProdServiceAreas(id); + return success(BeanUtils.toBean(prodServiceAreas, ProdServiceAreasRespVO.class)); + } + + @GetMapping("/page") + @Operation(summary = "获得服务区域分页") + @PreAuthorize("@ss.hasPermission('tz:prod-service-areas:query')") + public CommonResult> getProdServiceAreasPage(@Valid ProdServiceAreasPageReqVO pageReqVO) { + PageResult pageResult = prodServiceAreasService.getProdServiceAreasPage(pageReqVO); + return success(BeanUtils.toBean(pageResult, ProdServiceAreasRespVO.class)); + } + + @GetMapping("/export-excel") + @Operation(summary = "导出服务区域 Excel") + @PreAuthorize("@ss.hasPermission('tz:prod-service-areas:export')") + @ApiAccessLog(operateType = EXPORT) + public void exportProdServiceAreasExcel(@Valid ProdServiceAreasPageReqVO pageReqVO, + HttpServletResponse response) throws IOException { + pageReqVO.setPageSize(PageParam.PAGE_SIZE_NONE); + List list = prodServiceAreasService.getProdServiceAreasPage(pageReqVO).getList(); + // 导出 Excel + ExcelUtils.write(response, "服务区域.xls", "数据", ProdServiceAreasRespVO.class, + BeanUtils.toBean(list, ProdServiceAreasRespVO.class)); + } + +} \ No newline at end of file diff --git a/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/controller/admin/ProdServiceOverAreaRulesController.java b/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/controller/admin/ProdServiceOverAreaRulesController.java new file mode 100644 index 0000000..11ff7de --- /dev/null +++ b/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/controller/admin/ProdServiceOverAreaRulesController.java @@ -0,0 +1,94 @@ +package com.tashow.cloud.product.controller.admin; + +import com.tashow.cloud.common.pojo.CommonResult; +import com.tashow.cloud.common.pojo.PageParam; +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.common.util.object.BeanUtils; +import com.tashow.cloud.excel.excel.core.util.ExcelUtils; +import com.tashow.cloud.productapi.api.product.dto.ProdServiceOverAreaRulesDO; +import com.tashow.cloud.product.service.ProdServiceOverAreaRulesService; +import com.tashow.cloud.productapi.api.product.vo.prodserviceoverarearules.ProdServiceOverAreaRulesPageReqVO; +import com.tashow.cloud.productapi.api.product.vo.prodserviceoverarearules.ProdServiceOverAreaRulesRespVO; +import com.tashow.cloud.productapi.api.product.vo.prodserviceoverarearules.ProdServiceOverAreaRulesSaveReqVO; +import com.tashow.cloud.web.apilog.core.annotation.ApiAccessLog; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.annotation.Resource; +import jakarta.servlet.http.HttpServletResponse; +import jakarta.validation.Valid; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import java.io.IOException; +import java.util.List; + +import static com.tashow.cloud.common.pojo.CommonResult.success; +import static com.tashow.cloud.web.apilog.core.enums.OperateTypeEnum.EXPORT; + + +@Tag(name = "管理后台 - 超区规则") +@RestController +@RequestMapping("/tz/prod-service-over-area-rules") +@Validated +public class ProdServiceOverAreaRulesController { + + @Resource + private ProdServiceOverAreaRulesService prodServiceOverAreaRulesService; + + @PostMapping("/create") + @Operation(summary = "创建超区规则") + @PreAuthorize("@ss.hasPermission('tz:prod-service-over-area-rules:create')") + public CommonResult createProdServiceOverAreaRules(@Valid @RequestBody ProdServiceOverAreaRulesSaveReqVO createReqVO) { + return success(prodServiceOverAreaRulesService.createProdServiceOverAreaRules(createReqVO)); + } + + @PutMapping("/update") + @Operation(summary = "更新超区规则") + @PreAuthorize("@ss.hasPermission('tz:prod-service-over-area-rules:update')") + public CommonResult updateProdServiceOverAreaRules(@Valid @RequestBody ProdServiceOverAreaRulesSaveReqVO updateReqVO) { + prodServiceOverAreaRulesService.updateProdServiceOverAreaRules(updateReqVO); + return success(true); + } + + @DeleteMapping("/delete") + @Operation(summary = "删除超区规则") + @Parameter(name = "id", description = "编号", required = true) + @PreAuthorize("@ss.hasPermission('tz:prod-service-over-area-rules:delete')") + public CommonResult deleteProdServiceOverAreaRules(@RequestParam("id") Long id) { + prodServiceOverAreaRulesService.deleteProdServiceOverAreaRules(id); + return success(true); + } + + @GetMapping("/get") + @Operation(summary = "获得超区规则") + @Parameter(name = "id", description = "编号", required = true, example = "1024") + @PreAuthorize("@ss.hasPermission('tz:prod-service-over-area-rules:query')") + public CommonResult getProdServiceOverAreaRules(@RequestParam("id") Long id) { + ProdServiceOverAreaRulesDO prodServiceOverAreaRules = prodServiceOverAreaRulesService.getProdServiceOverAreaRules(id); + return success(BeanUtils.toBean(prodServiceOverAreaRules, ProdServiceOverAreaRulesRespVO.class)); + } + + @GetMapping("/page") + @Operation(summary = "获得超区规则分页") + @PreAuthorize("@ss.hasPermission('tz:prod-service-over-area-rules:query')") + public CommonResult> getProdServiceOverAreaRulesPage(@Valid ProdServiceOverAreaRulesPageReqVO pageReqVO) { + PageResult pageResult = prodServiceOverAreaRulesService.getProdServiceOverAreaRulesPage(pageReqVO); + return success(BeanUtils.toBean(pageResult, ProdServiceOverAreaRulesRespVO.class)); + } + + @GetMapping("/export-excel") + @Operation(summary = "导出超区规则 Excel") + @PreAuthorize("@ss.hasPermission('tz:prod-service-over-area-rules:export')") + @ApiAccessLog(operateType = EXPORT) + public void exportProdServiceOverAreaRulesExcel(@Valid ProdServiceOverAreaRulesPageReqVO pageReqVO, + HttpServletResponse response) throws IOException { + pageReqVO.setPageSize(PageParam.PAGE_SIZE_NONE); + List list = prodServiceOverAreaRulesService.getProdServiceOverAreaRulesPage(pageReqVO).getList(); + // 导出 Excel + ExcelUtils.write(response, "超区规则.xls", "数据", ProdServiceOverAreaRulesRespVO.class, + BeanUtils.toBean(list, ProdServiceOverAreaRulesRespVO.class)); + } + +} \ No newline at end of file diff --git a/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/controller/admin/ProdTagsController.java b/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/controller/admin/ProdTagsController.java new file mode 100644 index 0000000..e0e1d06 --- /dev/null +++ b/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/controller/admin/ProdTagsController.java @@ -0,0 +1,93 @@ +package com.tashow.cloud.product.controller.admin; + +import com.tashow.cloud.common.pojo.CommonResult; +import com.tashow.cloud.common.pojo.PageParam; +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.common.util.object.BeanUtils; +import com.tashow.cloud.excel.excel.core.util.ExcelUtils; +import com.tashow.cloud.productapi.api.product.dto.ProdTagsDO; +import com.tashow.cloud.product.service.ProdTagsService; +import com.tashow.cloud.productapi.api.product.vo.prodtags.ProdTagsPageReqVO; +import com.tashow.cloud.productapi.api.product.vo.prodtags.ProdTagsRespVO; +import com.tashow.cloud.productapi.api.product.vo.prodtags.ProdTagsSaveReqVO; +import com.tashow.cloud.web.apilog.core.annotation.ApiAccessLog; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.annotation.Resource; +import jakarta.servlet.http.HttpServletResponse; +import jakarta.validation.Valid; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import java.io.IOException; +import java.util.List; + +import static com.tashow.cloud.common.pojo.CommonResult.success; +import static com.tashow.cloud.web.apilog.core.enums.OperateTypeEnum.EXPORT; + +@Tag(name = "管理后台 - 商品和标签管理") +@RestController +@RequestMapping("/tz/prod-tags") +@Validated +public class ProdTagsController { + + @Resource + private ProdTagsService prodTagsService; + + @PostMapping("/create") + @Operation(summary = "创建商品和标签管理") + @PreAuthorize("@ss.hasPermission('tz:prod-tags:create')") + public CommonResult createProdTags(@Valid @RequestBody ProdTagsSaveReqVO createReqVO) { + return success(prodTagsService.createProdTags(createReqVO)); + } + + @PutMapping("/update") + @Operation(summary = "更新商品和标签管理") + @PreAuthorize("@ss.hasPermission('tz:prod-tags:update')") + public CommonResult updateProdTags(@Valid @RequestBody ProdTagsSaveReqVO updateReqVO) { + prodTagsService.updateProdTags(updateReqVO); + return success(true); + } + + @DeleteMapping("/delete") + @Operation(summary = "删除商品和标签管理") + @Parameter(name = "id", description = "编号", required = true) + @PreAuthorize("@ss.hasPermission('tz:prod-tags:delete')") + public CommonResult deleteProdTags(@RequestParam("id") Long id) { + prodTagsService.deleteProdTags(id); + return success(true); + } + + @GetMapping("/get") + @Operation(summary = "获得商品和标签管理") + @Parameter(name = "id", description = "编号", required = true, example = "1024") + @PreAuthorize("@ss.hasPermission('tz:prod-tags:query')") + public CommonResult getProdTags(@RequestParam("id") Long id) { + ProdTagsDO prodTags = prodTagsService.getProdTags(id); + return success(BeanUtils.toBean(prodTags, ProdTagsRespVO.class)); + } + + @GetMapping("/page") + @Operation(summary = "获得商品和标签管理分页") + @PreAuthorize("@ss.hasPermission('tz:prod-tags:query')") + public CommonResult> getProdTagsPage(@Valid ProdTagsPageReqVO pageReqVO) { + PageResult pageResult = prodTagsService.getProdTagsPage(pageReqVO); + return success(BeanUtils.toBean(pageResult, ProdTagsRespVO.class)); + } + + @GetMapping("/export-excel") + @Operation(summary = "导出商品和标签管理 Excel") + @PreAuthorize("@ss.hasPermission('tz:prod-tags:export')") + @ApiAccessLog(operateType = EXPORT) + public void exportProdTagsExcel(@Valid ProdTagsPageReqVO pageReqVO, + HttpServletResponse response) throws IOException { + pageReqVO.setPageSize(PageParam.PAGE_SIZE_NONE); + List list = prodTagsService.getProdTagsPage(pageReqVO).getList(); + // 导出 Excel + ExcelUtils.write(response, "商品和标签管理.xls", "数据", ProdTagsRespVO.class, + BeanUtils.toBean(list, ProdTagsRespVO.class)); + } + +} \ No newline at end of file diff --git a/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/controller/admin/ProdWeightRangePricesController.java b/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/controller/admin/ProdWeightRangePricesController.java new file mode 100644 index 0000000..a3e91f5 --- /dev/null +++ b/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/controller/admin/ProdWeightRangePricesController.java @@ -0,0 +1,93 @@ +package com.tashow.cloud.product.controller.admin; + +import com.tashow.cloud.common.pojo.CommonResult; +import com.tashow.cloud.common.pojo.PageParam; +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.common.util.object.BeanUtils; +import com.tashow.cloud.excel.excel.core.util.ExcelUtils; +import com.tashow.cloud.productapi.api.product.dto.ProdWeightRangePricesDO; +import com.tashow.cloud.product.service.ProdWeightRangePricesService; +import com.tashow.cloud.productapi.api.product.vo.prodweightrangeprices.ProdWeightRangePricesPageReqVO; +import com.tashow.cloud.productapi.api.product.vo.prodweightrangeprices.ProdWeightRangePricesRespVO; +import com.tashow.cloud.productapi.api.product.vo.prodweightrangeprices.ProdWeightRangePricesSaveReqVO; +import com.tashow.cloud.web.apilog.core.annotation.ApiAccessLog; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.annotation.Resource; +import jakarta.servlet.http.HttpServletResponse; +import jakarta.validation.Valid; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import java.io.IOException; +import java.util.List; + +import static com.tashow.cloud.common.pojo.CommonResult.success; +import static com.tashow.cloud.web.apilog.core.enums.OperateTypeEnum.EXPORT; + +@Tag(name = "管理后台 - 体重区间价格") +@RestController +@RequestMapping("/tz/prod-weight-range-prices") +@Validated +public class ProdWeightRangePricesController { + + @Resource + private ProdWeightRangePricesService prodWeightRangePricesService; + + @PostMapping("/create") + @Operation(summary = "创建体重区间价格") + @PreAuthorize("@ss.hasPermission('tz:prod-weight-range-prices:create')") + public CommonResult createProdWeightRangePrices(@Valid @RequestBody ProdWeightRangePricesSaveReqVO createReqVO) { + return success(prodWeightRangePricesService.createProdWeightRangePrices(createReqVO)); + } + + @PutMapping("/update") + @Operation(summary = "更新体重区间价格") + @PreAuthorize("@ss.hasPermission('tz:prod-weight-range-prices:update')") + public CommonResult updateProdWeightRangePrices(@Valid @RequestBody ProdWeightRangePricesSaveReqVO updateReqVO) { + prodWeightRangePricesService.updateProdWeightRangePrices(updateReqVO); + return success(true); + } + + @DeleteMapping("/delete") + @Operation(summary = "删除体重区间价格") + @Parameter(name = "id", description = "编号", required = true) + @PreAuthorize("@ss.hasPermission('tz:prod-weight-range-prices:delete')") + public CommonResult deleteProdWeightRangePrices(@RequestParam("id") Long id) { + prodWeightRangePricesService.deleteProdWeightRangePrices(id); + return success(true); + } + + @GetMapping("/get") + @Operation(summary = "获得体重区间价格") + @Parameter(name = "id", description = "编号", required = true, example = "1024") + @PreAuthorize("@ss.hasPermission('tz:prod-weight-range-prices:query')") + public CommonResult getProdWeightRangePrices(@RequestParam("id") Long id) { + ProdWeightRangePricesDO prodWeightRangePrices = prodWeightRangePricesService.getProdWeightRangePrices(id); + return success(BeanUtils.toBean(prodWeightRangePrices, ProdWeightRangePricesRespVO.class)); + } + + @GetMapping("/page") + @Operation(summary = "获得体重区间价格分页") + @PreAuthorize("@ss.hasPermission('tz:prod-weight-range-prices:query')") + public CommonResult> getProdWeightRangePricesPage(@Valid ProdWeightRangePricesPageReqVO pageReqVO) { + PageResult pageResult = prodWeightRangePricesService.getProdWeightRangePricesPage(pageReqVO); + return success(BeanUtils.toBean(pageResult, ProdWeightRangePricesRespVO.class)); + } + + @GetMapping("/export-excel") + @Operation(summary = "导出体重区间价格 Excel") + @PreAuthorize("@ss.hasPermission('tz:prod-weight-range-prices:export')") + @ApiAccessLog(operateType = EXPORT) + public void exportProdWeightRangePricesExcel(@Valid ProdWeightRangePricesPageReqVO pageReqVO, + HttpServletResponse response) throws IOException { + pageReqVO.setPageSize(PageParam.PAGE_SIZE_NONE); + List list = prodWeightRangePricesService.getProdWeightRangePricesPage(pageReqVO).getList(); + // 导出 Excel + ExcelUtils.write(response, "体重区间价格.xls", "数据", ProdWeightRangePricesRespVO.class, + BeanUtils.toBean(list, ProdWeightRangePricesRespVO.class)); + } + +} \ No newline at end of file diff --git a/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/controller/admin/ProductOrderLimitController.java b/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/controller/admin/ProductOrderLimitController.java new file mode 100644 index 0000000..00d8b9e --- /dev/null +++ b/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/controller/admin/ProductOrderLimitController.java @@ -0,0 +1,94 @@ +package com.tashow.cloud.product.controller.admin; + +import com.tashow.cloud.common.pojo.CommonResult; +import com.tashow.cloud.common.pojo.PageParam; +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.common.util.object.BeanUtils; +import com.tashow.cloud.excel.excel.core.util.ExcelUtils; +import com.tashow.cloud.productapi.api.product.dto.ProductOrderLimitDO; +import com.tashow.cloud.product.service.ProductOrderLimitService; +import com.tashow.cloud.productapi.api.product.vo.productorderlimit.ProductOrderLimitPageReqVO; +import com.tashow.cloud.productapi.api.product.vo.productorderlimit.ProductOrderLimitRespVO; +import com.tashow.cloud.productapi.api.product.vo.productorderlimit.ProductOrderLimitSaveReqVO; +import com.tashow.cloud.web.apilog.core.annotation.ApiAccessLog; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.annotation.Resource; +import jakarta.servlet.http.HttpServletResponse; +import jakarta.validation.Valid; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import java.io.IOException; +import java.util.List; + +import static com.tashow.cloud.common.pojo.CommonResult.success; +import static com.tashow.cloud.web.apilog.core.enums.OperateTypeEnum.EXPORT; + + +@Tag(name = "管理后台 - 商品接单上限设置") +@RestController +@RequestMapping("/tz/product-order-limit") +@Validated +public class ProductOrderLimitController { + + @Resource + private ProductOrderLimitService productOrderLimitService; + + @PostMapping("/create") + @Operation(summary = "创建商品接单上限设置") + @PreAuthorize("@ss.hasPermission('tz:product-order-limit:create')") + public CommonResult createProductOrderLimit(@Valid @RequestBody ProductOrderLimitSaveReqVO createReqVO) { + return success(productOrderLimitService.createProductOrderLimit(createReqVO)); + } + + @PutMapping("/update") + @Operation(summary = "更新商品接单上限设置") + @PreAuthorize("@ss.hasPermission('tz:product-order-limit:update')") + public CommonResult updateProductOrderLimit(@Valid @RequestBody ProductOrderLimitSaveReqVO updateReqVO) { + productOrderLimitService.updateProductOrderLimit(updateReqVO); + return success(true); + } + + @DeleteMapping("/delete") + @Operation(summary = "删除商品接单上限设置") + @Parameter(name = "id", description = "编号", required = true) + @PreAuthorize("@ss.hasPermission('tz:product-order-limit:delete')") + public CommonResult deleteProductOrderLimit(@RequestParam("id") Long id) { + productOrderLimitService.deleteProductOrderLimit(id); + return success(true); + } + + @GetMapping("/get") + @Operation(summary = "获得商品接单上限设置") + @Parameter(name = "id", description = "编号", required = true, example = "1024") + @PreAuthorize("@ss.hasPermission('tz:product-order-limit:query')") + public CommonResult getProductOrderLimit(@RequestParam("id") Long id) { + ProductOrderLimitDO productOrderLimit = productOrderLimitService.getProductOrderLimit(id); + return success(BeanUtils.toBean(productOrderLimit, ProductOrderLimitRespVO.class)); + } + + @GetMapping("/page") + @Operation(summary = "获得商品接单上限设置分页") + @PreAuthorize("@ss.hasPermission('tz:product-order-limit:query')") + public CommonResult> getProductOrderLimitPage(@Valid ProductOrderLimitPageReqVO pageReqVO) { + PageResult pageResult = productOrderLimitService.getProductOrderLimitPage(pageReqVO); + return success(BeanUtils.toBean(pageResult, ProductOrderLimitRespVO.class)); + } + + @GetMapping("/export-excel") + @Operation(summary = "导出商品接单上限设置 Excel") + @PreAuthorize("@ss.hasPermission('tz:product-order-limit:export')") + @ApiAccessLog(operateType = EXPORT) + public void exportProductOrderLimitExcel(@Valid ProductOrderLimitPageReqVO pageReqVO, + HttpServletResponse response) throws IOException { + pageReqVO.setPageSize(PageParam.PAGE_SIZE_NONE); + List list = productOrderLimitService.getProductOrderLimitPage(pageReqVO).getList(); + // 导出 Excel + ExcelUtils.write(response, "商品接单上限设置.xls", "数据", ProductOrderLimitRespVO.class, + BeanUtils.toBean(list, ProductOrderLimitRespVO.class)); + } + +} \ No newline at end of file diff --git a/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/controller/admin/ShopDetailController.java b/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/controller/admin/ShopDetailController.java new file mode 100644 index 0000000..4f9c6ad --- /dev/null +++ b/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/controller/admin/ShopDetailController.java @@ -0,0 +1,96 @@ +package com.tashow.cloud.product.controller.admin; + +import com.tashow.cloud.common.pojo.CommonResult; +import com.tashow.cloud.common.pojo.PageParam; +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.common.util.object.BeanUtils; +import com.tashow.cloud.excel.excel.core.util.ExcelUtils; +import com.tashow.cloud.productapi.api.product.dto.ShopDetailDO; +import com.tashow.cloud.product.service.ShopDetailService; +import com.tashow.cloud.productapi.api.product.vo.shopdetail +.ShopDetailPageReqVO; +import com.tashow.cloud.productapi.api.product.vo.shopdetail +.ShopDetailRespVO; +import com.tashow.cloud.productapi.api.product.vo.shopdetail +.ShopDetailSaveReqVO; +import com.tashow.cloud.web.apilog.core.annotation.ApiAccessLog; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.annotation.Resource; +import jakarta.servlet.http.HttpServletResponse; +import jakarta.validation.Valid; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import java.io.IOException; +import java.util.List; + +import static com.tashow.cloud.common.pojo.CommonResult.success; +import static com.tashow.cloud.web.apilog.core.enums.OperateTypeEnum.EXPORT; + +@Tag(name = "管理后台 - 店铺信息") +@RestController +@RequestMapping("/tz/shop-detail") +@Validated +public class ShopDetailController { + + @Resource + private ShopDetailService shopDetailService; + + @PostMapping("/create") + @Operation(summary = "创建店铺信息") + @PreAuthorize("@ss.hasPermission('tz:shop-detail:create')") + public CommonResult createShopDetail(@Valid @RequestBody ShopDetailSaveReqVO createReqVO) { + return success(shopDetailService.createShopDetail(createReqVO)); + } + + @PutMapping("/update") + @Operation(summary = "更新店铺信息") + @PreAuthorize("@ss.hasPermission('tz:shop-detail:update')") + public CommonResult updateShopDetail(@Valid @RequestBody ShopDetailSaveReqVO updateReqVO) { + shopDetailService.updateShopDetail(updateReqVO); + return success(true); + } + + @DeleteMapping("/delete") + @Operation(summary = "删除店铺信息") + @Parameter(name = "id", description = "编号", required = true) + @PreAuthorize("@ss.hasPermission('tz:shop-detail:delete')") + public CommonResult deleteShopDetail(@RequestParam("id") Long id) { + shopDetailService.deleteShopDetail(id); + return success(true); + } + + @GetMapping("/get") + @Operation(summary = "获得店铺信息") + @Parameter(name = "id", description = "编号", required = true, example = "1024") + @PreAuthorize("@ss.hasPermission('tz:shop-detail:query')") + public CommonResult getShopDetail(@RequestParam("id") Long id) { + ShopDetailDO shopDetail = shopDetailService.getShopDetail(id); + return success(BeanUtils.toBean(shopDetail, ShopDetailRespVO.class)); + } + + @GetMapping("/page") + @Operation(summary = "获得店铺信息分页") + @PreAuthorize("@ss.hasPermission('tz:shop-detail:query')") + public CommonResult> getShopDetailPage(@Valid ShopDetailPageReqVO pageReqVO) { + PageResult pageResult = shopDetailService.getShopDetailPage(pageReqVO); + return success(BeanUtils.toBean(pageResult, ShopDetailRespVO.class)); + } + + @GetMapping("/export-excel") + @Operation(summary = "导出店铺信息 Excel") + @PreAuthorize("@ss.hasPermission('tz:shop-detail:export')") + @ApiAccessLog(operateType = EXPORT) + public void exportShopDetailExcel(@Valid ShopDetailPageReqVO pageReqVO, + HttpServletResponse response) throws IOException { + pageReqVO.setPageSize(PageParam.PAGE_SIZE_NONE); + List list = shopDetailService.getShopDetailPage(pageReqVO).getList(); + // 导出 Excel + ExcelUtils.write(response, "店铺信息.xls", "数据", ShopDetailRespVO.class, + BeanUtils.toBean(list, ShopDetailRespVO.class)); + } + +} \ No newline at end of file diff --git a/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/controller/admin/SkuController.java b/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/controller/admin/SkuController.java new file mode 100644 index 0000000..9f8194f --- /dev/null +++ b/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/controller/admin/SkuController.java @@ -0,0 +1,314 @@ +package com.tashow.cloud.product.controller.admin; + +import com.tashow.cloud.common.pojo.CommonResult; +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.common.util.object.BeanUtils; +import com.tashow.cloud.product.mapper.ProdPropMapper; +import com.tashow.cloud.product.mapper.ProdPropValueMapper; +import com.tashow.cloud.productapi.api.product.dto.*; +import com.tashow.cloud.product.mapper.SkuMapper; +import com.tashow.cloud.product.service.ProdExtendService; +import com.tashow.cloud.product.service.ProdPropService; +import com.tashow.cloud.product.service.ProdPropValueService; +import com.tashow.cloud.product.service.SkuService; +import com.tashow.cloud.productapi.api.product.vo.prodpropvalue.ProPageReqVO; +import com.tashow.cloud.productapi.api.product.vo.prodpropvalue.ProPropRecycleBinVO; +import com.tashow.cloud.productapi.api.product.vo.sku.*; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.annotation.Resource; +import jakarta.annotation.security.PermitAll; +import jakarta.validation.Valid; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import java.util.Date; +import java.util.List; + +import static com.tashow.cloud.common.pojo.CommonResult.success; + +@Tag(name = "管理后台 - 单品SKU") +@RestController +@RequestMapping("/product/sku") +@Validated +public class SkuController { + + @Resource + private SkuService skuService; + + @Resource + private ProdPropService prodPropService; + @Resource + private ProdPropValueService prodPropValueService; + + @Resource + private ProdExtendService prodExtendService; + + + @Resource + private ProdPropValueMapper prodPropValueMapper; + @Resource + private ProdPropMapper prodPropMapper; + + @Resource + private SkuMapper skuMapper; + +/* @PostMapping("/create") + @Operation(summary = "创建单品SKU") + @PreAuthorize("@ss.hasPermission('tz:sku:create')") + public CommonResult createSku(@Valid @RequestBody SkuSaveReqVO createReqVO) { + return success(skuService.createSku(createReqVO)); + }*/ + + @PutMapping("/update") + @Operation(summary = "更新单品SKU") + @PermitAll + public CommonResult updateSku(@RequestBody SkuSaveReqVO updateReqVO) { + skuService.updateSku(updateReqVO); + return success(true); + } + + + @PutMapping("/updateProp") + @Operation(summary = "新增统一保存sku规格") + @PermitAll + public CommonResult updateProp(@Valid @RequestBody SkuPropVO skuPropVO) { + skuService.updateProp(skuPropVO); + return success(true); + } + + @PutMapping("/updatePropValue") + @Operation(summary = "修改属性下面规格值") + @PermitAll + public CommonResult updatePropValue(@RequestParam("id") Long id,@RequestParam("propValue") String propValue) { + skuService.updatePropVal(id,propValue); + return success(true); + } + + @PutMapping("/updateProdProp") + @Operation(summary = "修改属性规格值") + @PermitAll + public CommonResult updateProdProp(@RequestParam("id") Long id,@RequestParam("propName") String propName) { + ProdPropDO prodPropDO = new ProdPropDO(); + prodPropDO.setId( id); + prodPropDO.setPropName(propName); + prodPropMapper.updateById(prodPropDO); + return success(true); + } + + + @GetMapping("/getSKuPropList") + @Operation(summary = "获取sku规格") + @PermitAll + @Parameter(name = "prodId", description = "商品id", required = true) + public CommonResult getSKuPropList(@RequestParam("prodId") Long prodId) { + return success(skuService.getSKuPropList(prodId)); + } + + + @PermitAll + @GetMapping("/getPropRecycleBinList") + @Operation(summary = "获取规格回收站") + public CommonResult> getSKuPropRecycleBinList(ProPageReqVO proPageReqVO) { + PageResult pageResult = skuService.getSKuPropRecycleBinList(proPageReqVO); + return success(pageResult); + } + + @PostMapping("/restorePropList") + @Operation(summary = "恢复规格") + @Parameter(name = "ids", description = "规格id集合", required = true) + @PermitAll + public CommonResult restorePropList(@RequestParam("ids") List ids) { + skuService.restorePropList(ids); + return success(true); + } + + + @PutMapping("/deleteProp") + @Operation(summary = "删除规格值") + @PermitAll + public CommonResult deleteProp(@RequestParam("id") Long id) { + skuService.deleteProp(id); + return success(true); + } + + + @PutMapping("/disableProp") + @Operation(summary = "禁用或者启用规格值") + @Parameter(name = "id", description = "规格id") + @Parameter(name = "state", description = "状态0禁用1启用") + @PermitAll + public CommonResult disableProp(@RequestParam("id") Long id,@RequestParam("state") Integer state) { + skuService.disableProp(id,state); + return success(true); + } + + + @DeleteMapping("/delete") + @Operation(summary = "删除单品SKU") + @Parameter(name = "id", description = "编号", required = true) + @PermitAll + public CommonResult deleteSku(@RequestParam("id") Long id) { + skuService.deleteSku(id); + return success(true); + } + + @DeleteMapping("/deleteSkuList") + @Operation(summary = "批量删除SKU") + @Parameter(name = "ids", description = "编号", required = true) + @PermitAll + public CommonResult deleteSkuList(@RequestParam("ids") List ids) { + skuService.deleteSkus(ids); + return success(true); + } + + + @PutMapping("/updateSkuShelf") + @Operation(summary = "修改单品上下架") + @Parameter(name = "id", description = "编号", required = true) + @Parameter(name = "isShelf", description = "是否上下架(0下架 1上架)", required = true) + @PermitAll + public CommonResult updateSkuShelf(@RequestParam("id") Long id,@RequestParam("isShelf") Integer isShelf) { + skuService.updatSkuIsShelf(id,isShelf); + return success(true); + } + + @PutMapping("/updateSkuShelfList") + @Operation(summary = "批量上下架") + @Parameter(name = "ids", description = "编号", required = true) + @Parameter(name = "isShelf", description = "是否上下架(0下架 1上架)", required = true) + @PermitAll + public CommonResult updateSkuShelfList(@RequestParam("ids") List ids,@RequestParam("isShelf") Integer isShelf) { + for(Long id:ids){ + SkuDO sku = new SkuDO(); + sku.setSkuId(id); + sku.setIsShelf(isShelf); + skuMapper.updateById(sku); + } + skuService.updatSkuIsShelfs(ids,isShelf); + return success(true); + } + + + @GetMapping("/get") + @Operation(summary = "获得单品SKU") + @Parameter(name = "id", description = "编号", required = true, example = "1024") + @PermitAll + public CommonResult getSku(@RequestParam("id") Long id) { + SkuDO sku = skuService.getSku(id); + return success(BeanUtils.toBean(sku, SkuRespVO.class)); + } + + @PermitAll + @GetMapping("/getSkuRecycleBinPageList") + @Operation(summary = "获得SKU回收站分页列表") + public CommonResult> getSkuRecycleBinPageList(@Valid SkuPageReqVO pageReqVO) { + PageResult pageResult = skuService.getSkuRecycleBinPageList(pageReqVO); + return success(pageResult); + } + + + @PostMapping("/restoreSkuList") + @Operation(summary = "恢复SKU") + @Parameter(name = "ids", description = "skuids", required = true) + @PermitAll + public CommonResult restoreSkuList(@RequestParam("ids") List ids) { + skuService.restoreSkuList(ids); + return success(true); + } + + + + @PermitAll + @GetMapping("/getSkuPageList") + @Operation(summary = "获得SKU分页列表") + public CommonResult> getSkuPageList(@Valid SkuPageReqVO pageReqVO) { + PageResult pageResult = skuService.getSkuPageList(pageReqVO); + return success(pageResult); + } + + + +/* @PermitAll + @GetMapping("/getSkuRecycleBinPageList") + @Operation(summary = "获得SKU回收站分页列表") + public CommonResult> getSkuRecycleBinPageList(@Valid SkuPageReqVO pageReqVO) { + PageResult pageResult = skuService.getSkuRecycleBinPageList(pageReqVO); + return success(pageResult); + }*/ + + + @PostMapping("/createSkuExtend") + @Operation(summary = "创建sku扩展服务配置") + @PermitAll + public CommonResult createSkuExtend(@Valid @RequestBody SkuExtendVO skuExtendVO) { + skuService.createSkuExtend(skuExtendVO); + return success(true); + } + + + @PostMapping("/getSkuExtend") + @Operation(summary = "获取sku扩展服务配置信息") + @Parameter(name = "formId", description = "表单id", required = true, example = "1") + @PermitAll + public CommonResult getSkuExtend(@RequestParam("formId") Integer formId) { + SkuExtendVO skuExtendVO =skuService.getSkuExtend(formId); + return success(skuExtendVO); + } + + + @PostMapping("/updateServiceDetails") + @Operation(summary = "修改扩展服务信息配置(遗体接运扩展服务,遗体清洁配置,追思告别配置,骨灰处理配置......)") + @PermitAll + public CommonResult updateServiceDetails(@RequestBody List skuServiceDetailsList) { + skuService.updateServiceDetails(skuServiceDetailsList); + return success(true); + } + + @PostMapping("/updateMaterial") + @Operation(summary = "修物料配置") + @PermitAll + public CommonResult updateMaterial(@RequestBody List skuServiceMaterialList) { + skuService.updateMaterial(skuServiceMaterialList); + return success(true); + } + + @PostMapping("/updateTransportAdress") + @Operation(summary = "修改接运地址配置") + @PermitAll + public CommonResult updateTransportAdress(@RequestBody List skuServiceTransportDOList) { + skuService.updateTransportAdress(skuServiceTransportDOList); + return success(true); + } + + @PostMapping("/updateDeliver") + @Operation(summary = "修改配送方式") + @PermitAll + public CommonResult updateDeliver(@RequestBody List skuServiceDeliverList) { + skuService.updateDeliver(skuServiceDeliverList); + return success(true); + } + + + /* @GetMapping("/page") + @Operation(summary = "获得单品SKU分页") + public CommonResult> getSkuPage(@Valid SkuPageReqVO pageReqVO) { + PageResult pageResult = skuService.getSkuPage(pageReqVO); + return success(BeanUtils.toBean(pageResult, SkuRespVO.class)); + }*/ +/* + @GetMapping("/export-excel") + @Operation(summary = "导出单品SKU Excel") + @PreAuthorize("@ss.hasPermission('tz:sku:export')") + @ApiAccessLog(operateType = EXPORT) + public void exportSkuExcel(@Valid SkuPageReqVO pageReqVO, + HttpServletResponse response) throws IOException { + pageReqVO.setPageSize(PageParam.PAGE_SIZE_NONE); + List list = skuService.getSkuPage(pageReqVO).getList(); + // 导出 Excel + ExcelUtils.write(response, "单品SKU.xls", "数据", SkuRespVO.class, + BeanUtils.toBean(list, SkuRespVO.class)); + }*/ + +} \ No newline at end of file diff --git a/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/controller/admin/SkuServiceDeliverController.java b/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/controller/admin/SkuServiceDeliverController.java new file mode 100644 index 0000000..43480a7 --- /dev/null +++ b/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/controller/admin/SkuServiceDeliverController.java @@ -0,0 +1,93 @@ +package com.tashow.cloud.product.controller.admin; + +import com.tashow.cloud.common.pojo.CommonResult; +import com.tashow.cloud.common.pojo.PageParam; +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.common.util.object.BeanUtils; +import com.tashow.cloud.excel.excel.core.util.ExcelUtils; +import com.tashow.cloud.productapi.api.product.dto.SkuServiceDeliverDO; +import com.tashow.cloud.product.service.SkuServiceDeliverService; +import com.tashow.cloud.productapi.api.product.vo.skuservicedeliver.SkuServiceDeliverPageReqVO; +import com.tashow.cloud.productapi.api.product.vo.skuservicedeliver.SkuServiceDeliverRespVO; +import com.tashow.cloud.productapi.api.product.vo.skuservicedeliver.SkuServiceDeliverSaveReqVO; +import com.tashow.cloud.web.apilog.core.annotation.ApiAccessLog; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.annotation.Resource; +import jakarta.servlet.http.HttpServletResponse; +import jakarta.validation.Valid; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import java.io.IOException; +import java.util.List; + +import static com.tashow.cloud.common.pojo.CommonResult.success; +import static com.tashow.cloud.web.apilog.core.enums.OperateTypeEnum.EXPORT; + +@Tag(name = "管理后台 - 服务交付方式") +@RestController +@RequestMapping("/tz/sku-service-deliver") +@Validated +public class SkuServiceDeliverController { + + @Resource + private SkuServiceDeliverService skuServiceDeliverService; + + @PostMapping("/create") + @Operation(summary = "创建服务交付方式") + @PreAuthorize("@ss.hasPermission('tz:sku-service-deliver:create')") + public CommonResult createSkuServiceDeliver(@Valid @RequestBody SkuServiceDeliverSaveReqVO createReqVO) { + return success(skuServiceDeliverService.createSkuServiceDeliver(createReqVO)); + } + + @PutMapping("/update") + @Operation(summary = "更新服务交付方式") + @PreAuthorize("@ss.hasPermission('tz:sku-service-deliver:update')") + public CommonResult updateSkuServiceDeliver(@Valid @RequestBody SkuServiceDeliverSaveReqVO updateReqVO) { + skuServiceDeliverService.updateSkuServiceDeliver(updateReqVO); + return success(true); + } + + @DeleteMapping("/delete") + @Operation(summary = "删除服务交付方式") + @Parameter(name = "id", description = "编号", required = true) + @PreAuthorize("@ss.hasPermission('tz:sku-service-deliver:delete')") + public CommonResult deleteSkuServiceDeliver(@RequestParam("id") Long id) { + skuServiceDeliverService.deleteSkuServiceDeliver(id); + return success(true); + } + + @GetMapping("/get") + @Operation(summary = "获得服务交付方式") + @Parameter(name = "id", description = "编号", required = true, example = "1024") + @PreAuthorize("@ss.hasPermission('tz:sku-service-deliver:query')") + public CommonResult getSkuServiceDeliver(@RequestParam("id") Long id) { + SkuServiceDeliverDO skuServiceDeliver = skuServiceDeliverService.getSkuServiceDeliver(id); + return success(BeanUtils.toBean(skuServiceDeliver, SkuServiceDeliverRespVO.class)); + } + + @GetMapping("/page") + @Operation(summary = "获得服务交付方式分页") + @PreAuthorize("@ss.hasPermission('tz:sku-service-deliver:query')") + public CommonResult> getSkuServiceDeliverPage(@Valid SkuServiceDeliverPageReqVO pageReqVO) { + PageResult pageResult = skuServiceDeliverService.getSkuServiceDeliverPage(pageReqVO); + return success(BeanUtils.toBean(pageResult, SkuServiceDeliverRespVO.class)); + } + + @GetMapping("/export-excel") + @Operation(summary = "导出服务交付方式 Excel") + @PreAuthorize("@ss.hasPermission('tz:sku-service-deliver:export')") + @ApiAccessLog(operateType = EXPORT) + public void exportSkuServiceDeliverExcel(@Valid SkuServiceDeliverPageReqVO pageReqVO, + HttpServletResponse response) throws IOException { + pageReqVO.setPageSize(PageParam.PAGE_SIZE_NONE); + List list = skuServiceDeliverService.getSkuServiceDeliverPage(pageReqVO).getList(); + // 导出 Excel + ExcelUtils.write(response, "服务交付方式.xls", "数据", SkuServiceDeliverRespVO.class, + BeanUtils.toBean(list, SkuServiceDeliverRespVO.class)); + } + +} \ No newline at end of file diff --git a/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/controller/admin/SkuServiceDetailsController.java b/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/controller/admin/SkuServiceDetailsController.java new file mode 100644 index 0000000..9de48e6 --- /dev/null +++ b/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/controller/admin/SkuServiceDetailsController.java @@ -0,0 +1,93 @@ +package com.tashow.cloud.product.controller.admin; + +import com.tashow.cloud.common.pojo.CommonResult; +import com.tashow.cloud.common.pojo.PageParam; +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.common.util.object.BeanUtils; +import com.tashow.cloud.excel.excel.core.util.ExcelUtils; +import com.tashow.cloud.productapi.api.product.dto.SkuServiceDetailsDO; +import com.tashow.cloud.product.service.SkuServiceDetailsService; +import com.tashow.cloud.productapi.api.product.vo.skuservicedetails.SkuServiceDetailsPageReqVO; +import com.tashow.cloud.productapi.api.product.vo.skuservicedetails.SkuServiceDetailsRespVO; +import com.tashow.cloud.productapi.api.product.vo.skuservicedetails.SkuServiceDetailsSaveReqVO; +import com.tashow.cloud.web.apilog.core.annotation.ApiAccessLog; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.annotation.Resource; +import jakarta.servlet.http.HttpServletResponse; +import jakarta.validation.Valid; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import java.io.IOException; +import java.util.List; + +import static com.tashow.cloud.common.pojo.CommonResult.success; +import static com.tashow.cloud.web.apilog.core.enums.OperateTypeEnum.EXPORT; + +@Tag(name = "管理后台 - 服务详情") +@RestController +@RequestMapping("/tz/sku-service-details") +@Validated +public class SkuServiceDetailsController { + + @Resource + private SkuServiceDetailsService skuServiceDetailsService; + + @PostMapping("/create") + @Operation(summary = "创建服务详情") + @PreAuthorize("@ss.hasPermission('tz:sku-service-details:create')") + public CommonResult createSkuServiceDetails(@Valid @RequestBody SkuServiceDetailsSaveReqVO createReqVO) { + return success(skuServiceDetailsService.createSkuServiceDetails(createReqVO)); + } + + @PutMapping("/update") + @Operation(summary = "更新服务详情") + @PreAuthorize("@ss.hasPermission('tz:sku-service-details:update')") + public CommonResult updateSkuServiceDetails(@Valid @RequestBody SkuServiceDetailsSaveReqVO updateReqVO) { + skuServiceDetailsService.updateSkuServiceDetails(updateReqVO); + return success(true); + } + + @DeleteMapping("/delete") + @Operation(summary = "删除服务详情") + @Parameter(name = "id", description = "编号", required = true) + @PreAuthorize("@ss.hasPermission('tz:sku-service-details:delete')") + public CommonResult deleteSkuServiceDetails(@RequestParam("id") Long id) { + skuServiceDetailsService.deleteSkuServiceDetails(id); + return success(true); + } + + @GetMapping("/get") + @Operation(summary = "获得服务详情") + @Parameter(name = "id", description = "编号", required = true, example = "1024") + @PreAuthorize("@ss.hasPermission('tz:sku-service-details:query')") + public CommonResult getSkuServiceDetails(@RequestParam("id") Long id) { + SkuServiceDetailsDO skuServiceDetails = skuServiceDetailsService.getSkuServiceDetails(id); + return success(BeanUtils.toBean(skuServiceDetails, SkuServiceDetailsRespVO.class)); + } + + @GetMapping("/page") + @Operation(summary = "获得服务详情分页") + @PreAuthorize("@ss.hasPermission('tz:sku-service-details:query')") + public CommonResult> getSkuServiceDetailsPage(@Valid SkuServiceDetailsPageReqVO pageReqVO) { + PageResult pageResult = skuServiceDetailsService.getSkuServiceDetailsPage(pageReqVO); + return success(BeanUtils.toBean(pageResult, SkuServiceDetailsRespVO.class)); + } + + @GetMapping("/export-excel") + @Operation(summary = "导出服务详情 Excel") + @PreAuthorize("@ss.hasPermission('tz:sku-service-details:export')") + @ApiAccessLog(operateType = EXPORT) + public void exportSkuServiceDetailsExcel(@Valid SkuServiceDetailsPageReqVO pageReqVO, + HttpServletResponse response) throws IOException { + pageReqVO.setPageSize(PageParam.PAGE_SIZE_NONE); + List list = skuServiceDetailsService.getSkuServiceDetailsPage(pageReqVO).getList(); + // 导出 Excel + ExcelUtils.write(response, "服务详情.xls", "数据", SkuServiceDetailsRespVO.class, + BeanUtils.toBean(list, SkuServiceDetailsRespVO.class)); + } + +} \ No newline at end of file diff --git a/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/controller/admin/SkuServiceMaterialController.java b/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/controller/admin/SkuServiceMaterialController.java new file mode 100644 index 0000000..055e2c8 --- /dev/null +++ b/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/controller/admin/SkuServiceMaterialController.java @@ -0,0 +1,94 @@ +package com.tashow.cloud.product.controller.admin; + +import com.tashow.cloud.common.pojo.CommonResult; +import com.tashow.cloud.common.pojo.PageParam; +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.common.util.object.BeanUtils; +import com.tashow.cloud.excel.excel.core.util.ExcelUtils; +import com.tashow.cloud.productapi.api.product.dto.SkuServiceMaterialDO; +import com.tashow.cloud.product.service.SkuServiceMaterialService; +import com.tashow.cloud.productapi.api.product.vo.skuservicematerial.SkuServiceMaterialPageReqVO; +import com.tashow.cloud.productapi.api.product.vo.skuservicematerial.SkuServiceMaterialRespVO; +import com.tashow.cloud.productapi.api.product.vo.skuservicematerial.SkuServiceMaterialSaveReqVO; +import com.tashow.cloud.web.apilog.core.annotation.ApiAccessLog; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.annotation.Resource; +import jakarta.servlet.http.HttpServletResponse; +import jakarta.validation.Valid; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import java.io.IOException; +import java.util.List; + +import static com.tashow.cloud.common.pojo.CommonResult.success; +import static com.tashow.cloud.web.apilog.core.enums.OperateTypeEnum.EXPORT; + + +@Tag(name = "管理后台 - 服务物料详情") +@RestController +@RequestMapping("/tz/sku-service-material") +@Validated +public class SkuServiceMaterialController { + + @Resource + private SkuServiceMaterialService skuServiceMaterialService; + + @PostMapping("/create") + @Operation(summary = "创建服务物料详情") + @PreAuthorize("@ss.hasPermission('tz:sku-service-material:create')") + public CommonResult createSkuServiceMaterial(@Valid @RequestBody SkuServiceMaterialSaveReqVO createReqVO) { + return success(skuServiceMaterialService.createSkuServiceMaterial(createReqVO)); + } + + @PutMapping("/update") + @Operation(summary = "更新服务物料详情") + @PreAuthorize("@ss.hasPermission('tz:sku-service-material:update')") + public CommonResult updateSkuServiceMaterial(@Valid @RequestBody SkuServiceMaterialSaveReqVO updateReqVO) { + skuServiceMaterialService.updateSkuServiceMaterial(updateReqVO); + return success(true); + } + + @DeleteMapping("/delete") + @Operation(summary = "删除服务物料详情") + @Parameter(name = "id", description = "编号", required = true) + @PreAuthorize("@ss.hasPermission('tz:sku-service-material:delete')") + public CommonResult deleteSkuServiceMaterial(@RequestParam("id") Long id) { + skuServiceMaterialService.deleteSkuServiceMaterial(id); + return success(true); + } + + @GetMapping("/get") + @Operation(summary = "获得服务物料详情") + @Parameter(name = "id", description = "编号", required = true, example = "1024") + @PreAuthorize("@ss.hasPermission('tz:sku-service-material:query')") + public CommonResult getSkuServiceMaterial(@RequestParam("id") Long id) { + SkuServiceMaterialDO skuServiceMaterial = skuServiceMaterialService.getSkuServiceMaterial(id); + return success(BeanUtils.toBean(skuServiceMaterial, SkuServiceMaterialRespVO.class)); + } + + @GetMapping("/page") + @Operation(summary = "获得服务物料详情分页") + @PreAuthorize("@ss.hasPermission('tz:sku-service-material:query')") + public CommonResult> getSkuServiceMaterialPage(@Valid SkuServiceMaterialPageReqVO pageReqVO) { + PageResult pageResult = skuServiceMaterialService.getSkuServiceMaterialPage(pageReqVO); + return success(BeanUtils.toBean(pageResult, SkuServiceMaterialRespVO.class)); + } + + @GetMapping("/export-excel") + @Operation(summary = "导出服务物料详情 Excel") + @PreAuthorize("@ss.hasPermission('tz:sku-service-material:export')") + @ApiAccessLog(operateType = EXPORT) + public void exportSkuServiceMaterialExcel(@Valid SkuServiceMaterialPageReqVO pageReqVO, + HttpServletResponse response) throws IOException { + pageReqVO.setPageSize(PageParam.PAGE_SIZE_NONE); + List list = skuServiceMaterialService.getSkuServiceMaterialPage(pageReqVO).getList(); + // 导出 Excel + ExcelUtils.write(response, "服务物料详情.xls", "数据", SkuServiceMaterialRespVO.class, + BeanUtils.toBean(list, SkuServiceMaterialRespVO.class)); + } + +} \ No newline at end of file diff --git a/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/controller/admin/SkuServiceTransportController.java b/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/controller/admin/SkuServiceTransportController.java new file mode 100644 index 0000000..5c818b5 --- /dev/null +++ b/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/controller/admin/SkuServiceTransportController.java @@ -0,0 +1,93 @@ +package com.tashow.cloud.product.controller.admin; + +import com.tashow.cloud.common.pojo.CommonResult; +import com.tashow.cloud.common.pojo.PageParam; +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.common.util.object.BeanUtils; +import com.tashow.cloud.excel.excel.core.util.ExcelUtils; +import com.tashow.cloud.productapi.api.product.dto.SkuServiceTransportDO; +import com.tashow.cloud.product.service.SkuServiceTransportService; +import com.tashow.cloud.productapi.api.product.vo.skuservicetransport.SkuServiceTransportPageReqVO; +import com.tashow.cloud.productapi.api.product.vo.skuservicetransport.SkuServiceTransportRespVO; +import com.tashow.cloud.productapi.api.product.vo.skuservicetransport.SkuServiceTransportSaveReqVO; +import com.tashow.cloud.web.apilog.core.annotation.ApiAccessLog; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.annotation.Resource; +import jakarta.servlet.http.HttpServletResponse; +import jakarta.validation.Valid; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import java.io.IOException; +import java.util.List; + +import static com.tashow.cloud.common.pojo.CommonResult.success; +import static com.tashow.cloud.web.apilog.core.enums.OperateTypeEnum.EXPORT; + +@Tag(name = "管理后台 - 服务遗体运输") +@RestController +@RequestMapping("/tz/sku-service-transport") +@Validated +public class SkuServiceTransportController { + + @Resource + private SkuServiceTransportService skuServiceTransportService; + + @PostMapping("/create") + @Operation(summary = "创建服务遗体运输") + @PreAuthorize("@ss.hasPermission('tz:sku-service-transport:create')") + public CommonResult createSkuServiceTransport(@Valid @RequestBody SkuServiceTransportSaveReqVO createReqVO) { + return success(skuServiceTransportService.createSkuServiceTransport(createReqVO)); + } + + @PutMapping("/update") + @Operation(summary = "更新服务遗体运输") + @PreAuthorize("@ss.hasPermission('tz:sku-service-transport:update')") + public CommonResult updateSkuServiceTransport(@Valid @RequestBody SkuServiceTransportSaveReqVO updateReqVO) { + skuServiceTransportService.updateSkuServiceTransport(updateReqVO); + return success(true); + } + + @DeleteMapping("/delete") + @Operation(summary = "删除服务遗体运输") + @Parameter(name = "id", description = "编号", required = true) + @PreAuthorize("@ss.hasPermission('tz:sku-service-transport:delete')") + public CommonResult deleteSkuServiceTransport(@RequestParam("id") Long id) { + skuServiceTransportService.deleteSkuServiceTransport(id); + return success(true); + } + + @GetMapping("/get") + @Operation(summary = "获得服务遗体运输") + @Parameter(name = "id", description = "编号", required = true, example = "1024") + @PreAuthorize("@ss.hasPermission('tz:sku-service-transport:query')") + public CommonResult getSkuServiceTransport(@RequestParam("id") Long id) { + SkuServiceTransportDO skuServiceTransport = skuServiceTransportService.getSkuServiceTransport(id); + return success(BeanUtils.toBean(skuServiceTransport, SkuServiceTransportRespVO.class)); + } + + @GetMapping("/page") + @Operation(summary = "获得服务遗体运输分页") + @PreAuthorize("@ss.hasPermission('tz:sku-service-transport:query')") + public CommonResult> getSkuServiceTransportPage(@Valid SkuServiceTransportPageReqVO pageReqVO) { + PageResult pageResult = skuServiceTransportService.getSkuServiceTransportPage(pageReqVO); + return success(BeanUtils.toBean(pageResult, SkuServiceTransportRespVO.class)); + } + + @GetMapping("/export-excel") + @Operation(summary = "导出服务遗体运输 Excel") + @PreAuthorize("@ss.hasPermission('tz:sku-service-transport:export')") + @ApiAccessLog(operateType = EXPORT) + public void exportSkuServiceTransportExcel(@Valid SkuServiceTransportPageReqVO pageReqVO, + HttpServletResponse response) throws IOException { + pageReqVO.setPageSize(PageParam.PAGE_SIZE_NONE); + List list = skuServiceTransportService.getSkuServiceTransportPage(pageReqVO).getList(); + // 导出 Excel + ExcelUtils.write(response, "服务遗体运输.xls", "数据", SkuServiceTransportRespVO.class, + BeanUtils.toBean(list, SkuServiceTransportRespVO.class)); + } + +} \ No newline at end of file diff --git a/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/controller/admin/SkuServicesFormController.java b/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/controller/admin/SkuServicesFormController.java new file mode 100644 index 0000000..0459b16 --- /dev/null +++ b/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/controller/admin/SkuServicesFormController.java @@ -0,0 +1,93 @@ +package com.tashow.cloud.product.controller.admin; + +import com.tashow.cloud.common.pojo.CommonResult; +import com.tashow.cloud.common.pojo.PageParam; +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.common.util.object.BeanUtils; +import com.tashow.cloud.excel.excel.core.util.ExcelUtils; +import com.tashow.cloud.productapi.api.product.dto.SkuServicesFormDO; +import com.tashow.cloud.product.service.SkuServicesFormService; +import com.tashow.cloud.productapi.api.product.vo.skuservicesform.SkuServicesFormPageReqVO; +import com.tashow.cloud.productapi.api.product.vo.skuservicesform.SkuServicesFormRespVO; +import com.tashow.cloud.productapi.api.product.vo.skuservicesform.SkuServicesFormSaveReqVO; +import com.tashow.cloud.web.apilog.core.annotation.ApiAccessLog; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.annotation.Resource; +import jakarta.servlet.http.HttpServletResponse; +import jakarta.validation.Valid; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import java.io.IOException; +import java.util.List; + +import static com.tashow.cloud.common.pojo.CommonResult.success; +import static com.tashow.cloud.web.apilog.core.enums.OperateTypeEnum.EXPORT; + +@Tag(name = "管理后台 - 商品SKU扩展服务表单") +@RestController +@RequestMapping("/tz/sku-services-form") +@Validated +public class SkuServicesFormController { + + @Resource + private SkuServicesFormService skuServicesFormService; + + @PostMapping("/create") + @Operation(summary = "创建商品SKU扩展服务表单") + @PreAuthorize("@ss.hasPermission('tz:sku-services-form:create')") + public CommonResult createSkuServicesForm(@Valid @RequestBody SkuServicesFormSaveReqVO createReqVO) { + return success(skuServicesFormService.createSkuServicesForm(createReqVO)); + } + + @PutMapping("/update") + @Operation(summary = "更新商品SKU扩展服务表单") + @PreAuthorize("@ss.hasPermission('tz:sku-services-form:update')") + public CommonResult updateSkuServicesForm(@Valid @RequestBody SkuServicesFormSaveReqVO updateReqVO) { + skuServicesFormService.updateSkuServicesForm(updateReqVO); + return success(true); + } + + @DeleteMapping("/delete") + @Operation(summary = "删除商品SKU扩展服务表单") + @Parameter(name = "id", description = "编号", required = true) + @PreAuthorize("@ss.hasPermission('tz:sku-services-form:delete')") + public CommonResult deleteSkuServicesForm(@RequestParam("id") Long id) { + skuServicesFormService.deleteSkuServicesForm(id); + return success(true); + } + + @GetMapping("/get") + @Operation(summary = "获得商品SKU扩展服务表单") + @Parameter(name = "id", description = "编号", required = true, example = "1024") + @PreAuthorize("@ss.hasPermission('tz:sku-services-form:query')") + public CommonResult getSkuServicesForm(@RequestParam("id") Long id) { + SkuServicesFormDO skuServicesForm = skuServicesFormService.getSkuServicesForm(id); + return success(BeanUtils.toBean(skuServicesForm, SkuServicesFormRespVO.class)); + } + + @GetMapping("/page") + @Operation(summary = "获得商品SKU扩展服务表单分页") + @PreAuthorize("@ss.hasPermission('tz:sku-services-form:query')") + public CommonResult> getSkuServicesFormPage(@Valid SkuServicesFormPageReqVO pageReqVO) { + PageResult pageResult = skuServicesFormService.getSkuServicesFormPage(pageReqVO); + return success(BeanUtils.toBean(pageResult, SkuServicesFormRespVO.class)); + } + + @GetMapping("/export-excel") + @Operation(summary = "导出商品SKU扩展服务表单 Excel") + @PreAuthorize("@ss.hasPermission('tz:sku-services-form:export')") + @ApiAccessLog(operateType = EXPORT) + public void exportSkuServicesFormExcel(@Valid SkuServicesFormPageReqVO pageReqVO, + HttpServletResponse response) throws IOException { + pageReqVO.setPageSize(PageParam.PAGE_SIZE_NONE); + List list = skuServicesFormService.getSkuServicesFormPage(pageReqVO).getList(); + // 导出 Excel + ExcelUtils.write(response, "商品SKU扩展服务表单.xls", "数据", SkuServicesFormRespVO.class, + BeanUtils.toBean(list, SkuServicesFormRespVO.class)); + } + +} \ No newline at end of file diff --git a/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/mapper/CategoryMapper.java b/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/mapper/CategoryMapper.java new file mode 100644 index 0000000..ccaff7d --- /dev/null +++ b/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/mapper/CategoryMapper.java @@ -0,0 +1,29 @@ +package com.tashow.cloud.product.mapper; + +import java.util.*; + +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.mybatis.mybatis.core.query.LambdaQueryWrapperX; +import com.tashow.cloud.mybatis.mybatis.core.mapper.BaseMapperX; +import com.tashow.cloud.productapi.api.product.dto.CategoryDO; +import com.tashow.cloud.productapi.api.product.dto.CategoryDto; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; +/** + * 产品类目 Mapper + * + * @author 芋道源码 + */ +@Mapper +public interface CategoryMapper extends BaseMapperX { + + /** + * 根据条件查询类目列表 + */ + List selectCategoryList(@Param("grade") Integer grade, + @Param("categoryId") Long categoryId, + @Param("categoryName") String categoryName, + @Param("status") Integer status, + @Param("shopId") Long shopId); + +} \ No newline at end of file diff --git a/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/mapper/ProdAdditionalFeeDatesMapper.java b/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/mapper/ProdAdditionalFeeDatesMapper.java new file mode 100644 index 0000000..f039577 --- /dev/null +++ b/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/mapper/ProdAdditionalFeeDatesMapper.java @@ -0,0 +1,25 @@ +package com.tashow.cloud.product.mapper; + +import java.util.*; + +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.mybatis.mybatis.core.query.LambdaQueryWrapperX; +import com.tashow.cloud.mybatis.mybatis.core.mapper.BaseMapperX; +import com.tashow.cloud.productapi.api.product.dto.ProdAdditionalFeeDatesDO; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; + +/** + * 特殊日期附加费用规则 Mapper + * + * @author 芋道源码 + */ +@Mapper +public interface ProdAdditionalFeeDatesMapper extends BaseMapperX { + + /** + * 删除关联 + */ + public int deleteAdditionalFeeDates(@Param("prodId")Long prodId,@Param("type")Integer type); + +} \ No newline at end of file diff --git a/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/mapper/ProdAdditionalFeePeriodsMapper.java b/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/mapper/ProdAdditionalFeePeriodsMapper.java new file mode 100644 index 0000000..3a97ed5 --- /dev/null +++ b/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/mapper/ProdAdditionalFeePeriodsMapper.java @@ -0,0 +1,20 @@ +package com.tashow.cloud.product.mapper; + +import java.util.*; + +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.mybatis.mybatis.core.query.LambdaQueryWrapperX; +import com.tashow.cloud.mybatis.mybatis.core.mapper.BaseMapperX; +import com.tashow.cloud.productapi.api.product.dto.ProdAdditionalFeePeriodsDO; +import org.apache.ibatis.annotations.Mapper; + +/** + * 特殊时段附加费用规则 Mapper + * + * @author 芋道源码 + */ +@Mapper +public interface ProdAdditionalFeePeriodsMapper extends BaseMapperX { + + +} \ No newline at end of file diff --git a/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/mapper/ProdEmergencyResponseIntervalsMapper.java b/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/mapper/ProdEmergencyResponseIntervalsMapper.java new file mode 100644 index 0000000..a433d92 --- /dev/null +++ b/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/mapper/ProdEmergencyResponseIntervalsMapper.java @@ -0,0 +1,24 @@ +package com.tashow.cloud.product.mapper; + +import java.util.*; + +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.mybatis.mybatis.core.query.LambdaQueryWrapperX; +import com.tashow.cloud.mybatis.mybatis.core.mapper.BaseMapperX; +import com.tashow.cloud.productapi.api.product.dto.ProdEmergencyResponseIntervalsDO; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; + +/** + * 紧急响应时间区间设置 Mapper + * + * @author 芋道源码 + */ +@Mapper +public interface ProdEmergencyResponseIntervalsMapper extends BaseMapperX { + + /** + * 删除关联 + */ + public int deleteIntervals(@Param("configId")Long configId); +} \ No newline at end of file diff --git a/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/mapper/ProdEmergencyResponseMapper.java b/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/mapper/ProdEmergencyResponseMapper.java new file mode 100644 index 0000000..d4a1045 --- /dev/null +++ b/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/mapper/ProdEmergencyResponseMapper.java @@ -0,0 +1,20 @@ +package com.tashow.cloud.product.mapper; + +import java.util.*; + +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.mybatis.mybatis.core.query.LambdaQueryWrapperX; +import com.tashow.cloud.mybatis.mybatis.core.mapper.BaseMapperX; +import com.tashow.cloud.productapi.api.product.dto.ProdEmergencyResponseDO; +import org.apache.ibatis.annotations.Mapper; + +/** + * 商品紧急响应服务设置 Mapper + * + * @author 芋道源码 + */ +@Mapper +public interface ProdEmergencyResponseMapper extends BaseMapperX { + + +} \ No newline at end of file diff --git a/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/mapper/ProdExtendMapper.java b/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/mapper/ProdExtendMapper.java new file mode 100644 index 0000000..6c8d0df --- /dev/null +++ b/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/mapper/ProdExtendMapper.java @@ -0,0 +1,22 @@ +package com.tashow.cloud.product.mapper; + +import com.tashow.cloud.mybatis.mybatis.core.mapper.BaseMapperX; +import com.tashow.cloud.productapi.api.product.dto.ProdExtendDO; +import com.tashow.cloud.productapi.api.product.dto.ProdPropValueDO; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * 属性规则 Mapper + * + * @author 芋道源码 + */ +@Mapper +public interface ProdExtendMapper extends BaseMapperX { + // 自定义更新方法(可选) + int updateByProdId(@Param("prodId") Long prodId, + @Param("isDisable") Integer isDisable, + @Param("isExpire") Integer isExpire); +} \ No newline at end of file diff --git a/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/mapper/ProdMapper.java b/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/mapper/ProdMapper.java new file mode 100644 index 0000000..1c68e82 --- /dev/null +++ b/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/mapper/ProdMapper.java @@ -0,0 +1,42 @@ +package com.tashow.cloud.product.mapper; + +import java.util.*; + +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.mybatis.mybatis.core.query.LambdaQueryWrapperX; +import com.tashow.cloud.mybatis.mybatis.core.mapper.BaseMapperX; +import com.tashow.cloud.productapi.api.product.dto.*; +import com.tashow.cloud.productapi.api.product.vo.prod.ProdListVO; +import com.tashow.cloud.productapi.api.product.vo.prod.ProdPageReqVO; +import com.tashow.cloud.productapi.api.product.vo.prod.ProdRestoreListVO; +import com.tashow.cloud.productapi.api.product.vo.prod.ProdServiceVO; +import com.tashow.cloud.productapi.api.product.vo.sku.SkuRecycleBinVO; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; + +/** + * 商品 Mapper + * + * @author 芋道源码 + */ +@Mapper +public interface ProdMapper extends BaseMapperX { + + IPage getProdPageList(Page page,@Param("createTime") String[] createTime + , @Param("prodName") String prodName + , @Param("shopId") Long shopId + , @Param("status") Integer status + , @Param("categoryId") Long categoryId); + + ProdServiceVO selectProdService(@Param("prodId") Long prodId); + + ProdServiceVO selectProdServiceInfo(@Param("prodId") Long prodId); + + IPage getProdRecycleBinPageList(Page page, @Param("deleteTime") String[] deleteTime + , @Param("prodName") String prodName); + + + void batchRestoreProd(@Param("ids") List ids); +} \ No newline at end of file diff --git a/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/mapper/ProdPropMapper.java b/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/mapper/ProdPropMapper.java new file mode 100644 index 0000000..262a721 --- /dev/null +++ b/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/mapper/ProdPropMapper.java @@ -0,0 +1,28 @@ +package com.tashow.cloud.product.mapper; + +import java.util.*; + +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.mybatis.mybatis.core.query.LambdaQueryWrapperX; +import com.tashow.cloud.mybatis.mybatis.core.mapper.BaseMapperX; +import com.tashow.cloud.productapi.api.product.dto.ProdPropDO; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; + +/** + * 商品属性 Mapper + * + * @author 芋道源码 + */ +@Mapper +public interface ProdPropMapper extends BaseMapperX { + + /** + * 根据店铺id和属性名称获取商品属性 + * @param propName + * @param prodId + * @param rule + * @return + */ + ProdPropDO getProdPropByPropNameAndShopId(@Param("propName") String propName, @Param("prodId") Integer prodId, @Param("rule") Integer rule); +} \ No newline at end of file diff --git a/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/mapper/ProdPropValueMapper.java b/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/mapper/ProdPropValueMapper.java new file mode 100644 index 0000000..1e22404 --- /dev/null +++ b/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/mapper/ProdPropValueMapper.java @@ -0,0 +1,52 @@ +package com.tashow.cloud.product.mapper; + +import java.util.*; + +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.mybatis.mybatis.core.query.LambdaQueryWrapperX; +import com.tashow.cloud.mybatis.mybatis.core.mapper.BaseMapperX; +import com.tashow.cloud.productapi.api.product.dto.ProdPropValueDO; +import com.tashow.cloud.productapi.api.product.dto.SkuDO; +import com.tashow.cloud.productapi.api.product.vo.prodpropvalue.ProPropRecycleBinVO; +import com.tashow.cloud.productapi.api.product.vo.sku.SkuRecycleBinVO; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; + +/** + * 属性规则 Mapper + * + * @author 芋道源码 + */ +@Mapper +public interface ProdPropValueMapper extends BaseMapperX { + + /** + * 插入商品属性数值 + * @param propId + * @param prodPropValues + */ + void insertPropValues(@Param("propId") Long propId, @Param("prodPropValues") List prodPropValues); + + List selectSalesValuesByProdId(@Param("prodId") Long prodId); + + List selectSalesValuesByState(@Param("prodId") Long prodId); + + void batchMarkDeleted(@Param("ids") List ids); + + int deleteProdPropValueById( @Param("id")Long id); + + List selectRestoreProp(@Param("prodId") Long prodId); + + IPage getSKuPropRecycleBinList(Page page, @Param("prodId") Long prodId,@Param("propValue")String propValue); + + List getskuListByPropValueIds(@Param("ids")List ids); + + void restorePropValue(@Param("ids") List ids); + + int getMaxPropValue(@Param("propId")Long propId); + + // 根据商品 ID 更新销售属性值的 state(仅 rule=1 的) + void updateStateByProdId(@Param("prodId") Long prodId, @Param("state") Integer state); +} \ No newline at end of file diff --git a/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/mapper/ProdReservationConfigMapper.java b/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/mapper/ProdReservationConfigMapper.java new file mode 100644 index 0000000..a5647e1 --- /dev/null +++ b/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/mapper/ProdReservationConfigMapper.java @@ -0,0 +1,25 @@ +package com.tashow.cloud.product.mapper; + +import java.util.*; + +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.mybatis.mybatis.core.query.LambdaQueryWrapperX; +import com.tashow.cloud.mybatis.mybatis.core.mapper.BaseMapperX; +import com.tashow.cloud.productapi.api.product.dto.ProdReservationConfigDO; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; + +/** + * 商品预约配置 Mapper + * + * @author 芋道源码 + */ +@Mapper +public interface ProdReservationConfigMapper extends BaseMapperX { + + + /** + * 删除关联 + */ + public int deleteReservationConfig(@Param("prodId")Long prodId); +} \ No newline at end of file diff --git a/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/mapper/ProdServiceAreaRelevanceMapper.java b/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/mapper/ProdServiceAreaRelevanceMapper.java new file mode 100644 index 0000000..c69fe33 --- /dev/null +++ b/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/mapper/ProdServiceAreaRelevanceMapper.java @@ -0,0 +1,24 @@ +package com.tashow.cloud.product.mapper; + +import java.util.*; + +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.mybatis.mybatis.core.query.LambdaQueryWrapperX; +import com.tashow.cloud.mybatis.mybatis.core.mapper.BaseMapperX; +import com.tashow.cloud.productapi.api.product.dto.ProdServiceAreaRelevanceDO; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; + +/** + * 商品与服务区域关联 Mapper + * + * @author 芋道源码 + */ +@Mapper +public interface ProdServiceAreaRelevanceMapper extends BaseMapperX { + + /** + * 删除关联 + */ + public int deleteRelevance(@Param("prodId")Long prodId); +} \ No newline at end of file diff --git a/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/mapper/ProdServiceAreasMapper.java b/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/mapper/ProdServiceAreasMapper.java new file mode 100644 index 0000000..d94d885 --- /dev/null +++ b/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/mapper/ProdServiceAreasMapper.java @@ -0,0 +1,20 @@ +package com.tashow.cloud.product.mapper; + +import java.util.*; + +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.mybatis.mybatis.core.query.LambdaQueryWrapperX; +import com.tashow.cloud.mybatis.mybatis.core.mapper.BaseMapperX; +import com.tashow.cloud.productapi.api.product.dto.ProdServiceAreasDO; +import org.apache.ibatis.annotations.Mapper; + +/** + * 服务区域 Mapper + * + * @author 芋道源码 + */ +@Mapper +public interface ProdServiceAreasMapper extends BaseMapperX { + + +} \ No newline at end of file diff --git a/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/mapper/ProdServiceOverAreaRulesMapper.java b/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/mapper/ProdServiceOverAreaRulesMapper.java new file mode 100644 index 0000000..a930140 --- /dev/null +++ b/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/mapper/ProdServiceOverAreaRulesMapper.java @@ -0,0 +1,20 @@ +package com.tashow.cloud.product.mapper; + +import java.util.*; + +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.mybatis.mybatis.core.query.LambdaQueryWrapperX; +import com.tashow.cloud.mybatis.mybatis.core.mapper.BaseMapperX; +import com.tashow.cloud.productapi.api.product.dto.ProdServiceOverAreaRulesDO; +import org.apache.ibatis.annotations.Mapper; + +/** + * 超区规则 Mapper + * + * @author 芋道源码 + */ +@Mapper +public interface ProdServiceOverAreaRulesMapper extends BaseMapperX { + + +} \ No newline at end of file diff --git a/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/mapper/ProdTagsMapper.java b/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/mapper/ProdTagsMapper.java new file mode 100644 index 0000000..2368366 --- /dev/null +++ b/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/mapper/ProdTagsMapper.java @@ -0,0 +1,19 @@ +package com.tashow.cloud.product.mapper; + +import java.util.*; + +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.mybatis.mybatis.core.query.LambdaQueryWrapperX; +import com.tashow.cloud.mybatis.mybatis.core.mapper.BaseMapperX; +import com.tashow.cloud.productapi.api.product.dto.ProdTagsDO; +import org.apache.ibatis.annotations.Mapper; + +/** + * 商品和标签管理 Mapper + * + * @author 芋道源码 + */ +@Mapper +public interface ProdTagsMapper extends BaseMapperX { + +} \ No newline at end of file diff --git a/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/mapper/ProdWeightRangePricesMapper.java b/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/mapper/ProdWeightRangePricesMapper.java new file mode 100644 index 0000000..efec471 --- /dev/null +++ b/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/mapper/ProdWeightRangePricesMapper.java @@ -0,0 +1,25 @@ +package com.tashow.cloud.product.mapper; + +import java.util.*; + +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.mybatis.mybatis.core.query.LambdaQueryWrapperX; +import com.tashow.cloud.mybatis.mybatis.core.mapper.BaseMapperX; +import com.tashow.cloud.productapi.api.product.dto.ProdWeightRangePricesDO; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; + +/** + * 体重区间价格 Mapper + * + * @author 芋道源码 + */ +@Mapper +public interface ProdWeightRangePricesMapper extends BaseMapperX { + + + /** + * 删除关联 + */ + public int deleteWeightRangePrices(@Param("prodId")Long prodId); +} \ No newline at end of file diff --git a/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/mapper/ProductOrderLimitMapper.java b/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/mapper/ProductOrderLimitMapper.java new file mode 100644 index 0000000..766d8e4 --- /dev/null +++ b/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/mapper/ProductOrderLimitMapper.java @@ -0,0 +1,20 @@ +package com.tashow.cloud.product.mapper; + +import java.util.*; + +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.mybatis.mybatis.core.query.LambdaQueryWrapperX; +import com.tashow.cloud.mybatis.mybatis.core.mapper.BaseMapperX; +import com.tashow.cloud.productapi.api.product.dto.ProductOrderLimitDO; +import org.apache.ibatis.annotations.Mapper; + +/** + * 商品接单上限设置 Mapper + * + * @author 芋道源码 + */ +@Mapper +public interface ProductOrderLimitMapper extends BaseMapperX { + + +} \ No newline at end of file diff --git a/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/mapper/ShopDetailMapper.java b/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/mapper/ShopDetailMapper.java new file mode 100644 index 0000000..498f24b --- /dev/null +++ b/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/mapper/ShopDetailMapper.java @@ -0,0 +1,20 @@ +package com.tashow.cloud.product.mapper; + +import java.util.*; + +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.mybatis.mybatis.core.query.LambdaQueryWrapperX; +import com.tashow.cloud.mybatis.mybatis.core.mapper.BaseMapperX; +import com.tashow.cloud.productapi.api.product.dto.ShopDetailDO; +import org.apache.ibatis.annotations.Mapper; + +/** + * 店铺信息 Mapper + * + * @author 芋道源码 + */ +@Mapper +public interface ShopDetailMapper extends BaseMapperX { + + +} \ No newline at end of file diff --git a/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/mapper/SkuMapper.java b/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/mapper/SkuMapper.java new file mode 100644 index 0000000..c5f8c97 --- /dev/null +++ b/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/mapper/SkuMapper.java @@ -0,0 +1,47 @@ +package com.tashow.cloud.product.mapper; + +import java.util.*; + +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.tashow.cloud.mybatis.mybatis.core.mapper.BaseMapperX; +import com.tashow.cloud.productapi.api.product.dto.SkuDO; +import com.tashow.cloud.productapi.api.product.vo.sku.SkuRecycleBinVO; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; + +/** + * 单品SKU Mapper + * + * @author 芋道源码 + */ +@Mapper +public interface SkuMapper extends BaseMapperX { + + IPage getSkuRecycleBinPageList(Page page, @Param("prodId") Long prodId, @Param("properties")String properties); + + IPage getSkuPageList(Page page, @Param("prodId") Long prodId,@Param("skuId") Long skuId, @Param("properties")String properties); + + + List getSkuListByName( @Param("prodId")Long prodId , @Param("propertiesName")String propertiesName); + + List selectPropertiesByProdIdAndNotDeleted( @Param("prodId")Long prodId); + + List selectPropertiesByProdIdShelf( @Param("prodId")Long prodId); + + int deleteBySkuId( @Param("skuId")Long skuId); + + void batchSkuDeleted(@Param("ids") List ids); + + void updateSkuDeleted(@Param("skuId") Long skuId); + + + List getskuListBySkuIds( @Param("ids")List ids); + + List getskuListByDeleted( @Param("prodId")Long prodId,@Param("ids")List ids); + + void batchSkuRecover(@Param("ids") List ids); + + // 查询商品下所有 SKU 的 is_shelf 状态(未删除) + List selectShelfStatusByProdId(@Param("prodId") Long prodId); +} \ No newline at end of file diff --git a/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/mapper/SkuServiceDeliverMapper.java b/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/mapper/SkuServiceDeliverMapper.java new file mode 100644 index 0000000..95a23b6 --- /dev/null +++ b/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/mapper/SkuServiceDeliverMapper.java @@ -0,0 +1,20 @@ +package com.tashow.cloud.product.mapper; + +import java.util.*; + +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.mybatis.mybatis.core.query.LambdaQueryWrapperX; +import com.tashow.cloud.mybatis.mybatis.core.mapper.BaseMapperX; +import com.tashow.cloud.productapi.api.product.dto.SkuServiceDeliverDO; +import org.apache.ibatis.annotations.Mapper; + +/** + * 服务交付方式 Mapper + * + * @author 芋道源码 + */ +@Mapper +public interface SkuServiceDeliverMapper extends BaseMapperX { + + void deleteDelivers(Long serviceId); +} \ No newline at end of file diff --git a/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/mapper/SkuServiceDetailsMapper.java b/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/mapper/SkuServiceDetailsMapper.java new file mode 100644 index 0000000..34ca4e3 --- /dev/null +++ b/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/mapper/SkuServiceDetailsMapper.java @@ -0,0 +1,21 @@ +package com.tashow.cloud.product.mapper; + +import java.util.*; + +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.mybatis.mybatis.core.query.LambdaQueryWrapperX; +import com.tashow.cloud.mybatis.mybatis.core.mapper.BaseMapperX; +import com.tashow.cloud.productapi.api.product.dto.SkuServiceDetailsDO; +import org.apache.ibatis.annotations.Mapper; + +/** + * 服务详情 Mapper + * + * @author 芋道源码 + */ +@Mapper +public interface SkuServiceDetailsMapper extends BaseMapperX { + + void deleteDetails(Long serviceId); + +} \ No newline at end of file diff --git a/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/mapper/SkuServiceMaterialMapper.java b/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/mapper/SkuServiceMaterialMapper.java new file mode 100644 index 0000000..9d4052b --- /dev/null +++ b/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/mapper/SkuServiceMaterialMapper.java @@ -0,0 +1,21 @@ +package com.tashow.cloud.product.mapper; + +import java.util.*; + +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.mybatis.mybatis.core.query.LambdaQueryWrapperX; +import com.tashow.cloud.mybatis.mybatis.core.mapper.BaseMapperX; +import com.tashow.cloud.productapi.api.product.dto.SkuServiceMaterialDO; +import org.apache.ibatis.annotations.Mapper; + +/** + * 服务物料详情 Mapper + * + * @author 芋道源码 + */ +@Mapper +public interface SkuServiceMaterialMapper extends BaseMapperX { + + + void deleteMaterials(Long serviceId); +} \ No newline at end of file diff --git a/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/mapper/SkuServiceTransportMapper.java b/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/mapper/SkuServiceTransportMapper.java new file mode 100644 index 0000000..2c55645 --- /dev/null +++ b/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/mapper/SkuServiceTransportMapper.java @@ -0,0 +1,21 @@ +package com.tashow.cloud.product.mapper; + +import java.util.*; + +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.mybatis.mybatis.core.query.LambdaQueryWrapperX; +import com.tashow.cloud.mybatis.mybatis.core.mapper.BaseMapperX; +import com.tashow.cloud.productapi.api.product.dto.SkuServiceTransportDO; +import org.apache.ibatis.annotations.Mapper; + +/** + * 服务遗体运输 Mapper + * + * @author 芋道源码 + */ +@Mapper +public interface SkuServiceTransportMapper extends BaseMapperX { + + + void deleteTransports(Long serviceId); +} \ No newline at end of file diff --git a/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/mapper/SkuServicesFormMapper.java b/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/mapper/SkuServicesFormMapper.java new file mode 100644 index 0000000..cf5e6a6 --- /dev/null +++ b/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/mapper/SkuServicesFormMapper.java @@ -0,0 +1,23 @@ +package com.tashow.cloud.product.mapper; + +import java.util.*; + +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.mybatis.mybatis.core.query.LambdaQueryWrapperX; +import com.tashow.cloud.mybatis.mybatis.core.mapper.BaseMapperX; +import com.tashow.cloud.productapi.api.product.dto.SkuDO; +import com.tashow.cloud.productapi.api.product.dto.SkuServicesFormDO; +import com.tashow.cloud.productapi.api.product.vo.sku.SkuServiceExtendVO; +import org.apache.ibatis.annotations.Mapper; + +/** + * 商品SKU扩展服务表单 Mapper + * + * @author 芋道源码 + */ +@Mapper +public interface SkuServicesFormMapper extends BaseMapperX { + + List selectSkuServiceExtendWithDetails(Integer formId); + +} \ No newline at end of file diff --git a/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/service/CategoryService.java b/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/service/CategoryService.java new file mode 100644 index 0000000..b9092f2 --- /dev/null +++ b/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/service/CategoryService.java @@ -0,0 +1,65 @@ +package com.tashow.cloud.product.service; + +import java.util.*; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.tashow.cloud.productapi.api.product.dto.CategoryDO; +import com.tashow.cloud.productapi.api.product.dto.ProdPropValueDO; +import com.tashow.cloud.productapi.api.product.dto.CategoryDto; +import com.tashow.cloud.productapi.api.product.vo.CategoryPageReqVO; +import com.tashow.cloud.productapi.api.product.vo.CategorySaveReqVO; +import jakarta.validation.*; +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.common.pojo.PageParam; +import org.springframework.web.bind.annotation.RequestParam; + +/** + * 产品类目 Service 接口 + * + * @author 芋道源码 + */ +public interface CategoryService extends IService { + + + List categoryList(Integer grade, Long categoryId,String categoryName, Integer status); + + + /** + * 创建产品类目 + * + * @param createReqVO 创建信息 + * @return 编号 + */ + Long createCategory(@Valid CategorySaveReqVO createReqVO); + + /** + * 更新产品类目 + * + * @param updateReqVO 更新信息 + */ + void updateCategory(@Valid CategorySaveReqVO updateReqVO); + + /** + * 删除产品类目 + * + * @param id 编号 + */ + void deleteCategory(Long id); + + /** + * 获得产品类目 + * + * @param id 编号 + * @return 产品类目 + */ + CategoryDO getCategory(Long id); + + /** + * 获得产品类目分页 + * + * @param pageReqVO 分页查询 + * @return 产品类目分页 + */ + PageResult getCategoryPage(CategoryPageReqVO pageReqVO); + +} \ No newline at end of file diff --git a/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/service/ProdAdditionalFeeDatesService.java b/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/service/ProdAdditionalFeeDatesService.java new file mode 100644 index 0000000..0c7749b --- /dev/null +++ b/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/service/ProdAdditionalFeeDatesService.java @@ -0,0 +1,57 @@ +package com.tashow.cloud.product.service; + +import java.util.*; + +import com.tashow.cloud.productapi.api.product.dto.ProdAdditionalFeeDatesDO; +import com.tashow.cloud.productapi.api.product.vo.prodadditionalfeedates.ProdAdditionalFeeDatesPageReqVO; +import com.tashow.cloud.productapi.api.product.vo.prodadditionalfeedates.ProdAdditionalFeeDatesSaveReqVO; +import jakarta.validation.*; +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.common.pojo.PageParam; + +/** + * 特殊日期附加费用规则 Service 接口 + * + * @author 芋道源码 + */ +public interface ProdAdditionalFeeDatesService { + + /** + * 创建特殊日期附加费用规则 + * + * @param createReqVO 创建信息 + * @return 编号 + */ + Long createProdAdditionalFeeDates(@Valid ProdAdditionalFeeDatesSaveReqVO createReqVO); + + /** + * 更新特殊日期附加费用规则 + * + * @param updateReqVO 更新信息 + */ + void updateProdAdditionalFeeDates(@Valid ProdAdditionalFeeDatesSaveReqVO updateReqVO); + + /** + * 删除特殊日期附加费用规则 + * + * @param id 编号 + */ + void deleteProdAdditionalFeeDates(Long id); + + /** + * 获得特殊日期附加费用规则 + * + * @param id 编号 + * @return 特殊日期附加费用规则 + */ + ProdAdditionalFeeDatesDO getProdAdditionalFeeDates(Long id); + + /** + * 获得特殊日期附加费用规则分页 + * + * @param pageReqVO 分页查询 + * @return 特殊日期附加费用规则分页 + */ + PageResult getProdAdditionalFeeDatesPage(ProdAdditionalFeeDatesPageReqVO pageReqVO); + +} \ No newline at end of file diff --git a/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/service/ProdAdditionalFeePeriodsService.java b/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/service/ProdAdditionalFeePeriodsService.java new file mode 100644 index 0000000..9ee9afe --- /dev/null +++ b/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/service/ProdAdditionalFeePeriodsService.java @@ -0,0 +1,57 @@ +package com.tashow.cloud.product.service; + +import java.util.*; + +import com.tashow.cloud.productapi.api.product.dto.ProdAdditionalFeePeriodsDO; +import com.tashow.cloud.productapi.api.product.vo.prodadditionalfeeperiods.ProdAdditionalFeePeriodsPageReqVO; +import com.tashow.cloud.productapi.api.product.vo.prodadditionalfeeperiods.ProdAdditionalFeePeriodsSaveReqVO; +import jakarta.validation.*; +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.common.pojo.PageParam; + +/** + * 特殊时段附加费用规则 Service 接口 + * + * @author 芋道源码 + */ +public interface ProdAdditionalFeePeriodsService { + + /** + * 创建特殊时段附加费用规则 + * + * @param createReqVO 创建信息 + * @return 编号 + */ + Long createProdAdditionalFeePeriods(@Valid ProdAdditionalFeePeriodsSaveReqVO createReqVO); + + /** + * 更新特殊时段附加费用规则 + * + * @param updateReqVO 更新信息 + */ + void updateProdAdditionalFeePeriods(@Valid ProdAdditionalFeePeriodsSaveReqVO updateReqVO); + + /** + * 删除特殊时段附加费用规则 + * + * @param id 编号 + */ + void deleteProdAdditionalFeePeriods(Long id); + + /** + * 获得特殊时段附加费用规则 + * + * @param id 编号 + * @return 特殊时段附加费用规则 + */ + ProdAdditionalFeePeriodsDO getProdAdditionalFeePeriods(Long id); + + /** + * 获得特殊时段附加费用规则分页 + * + * @param pageReqVO 分页查询 + * @return 特殊时段附加费用规则分页 + */ + PageResult getProdAdditionalFeePeriodsPage(ProdAdditionalFeePeriodsPageReqVO pageReqVO); + +} \ No newline at end of file diff --git a/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/service/ProdEmergencyResponseIntervalsService.java b/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/service/ProdEmergencyResponseIntervalsService.java new file mode 100644 index 0000000..840182c --- /dev/null +++ b/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/service/ProdEmergencyResponseIntervalsService.java @@ -0,0 +1,57 @@ +package com.tashow.cloud.product.service; + +import java.util.*; + +import com.tashow.cloud.productapi.api.product.dto.ProdEmergencyResponseIntervalsDO; +import com.tashow.cloud.productapi.api.product.vo.prodemergencyresponseintervals.ProdEmergencyResponseIntervalsPageReqVO; +import com.tashow.cloud.productapi.api.product.vo.prodemergencyresponseintervals.ProdEmergencyResponseIntervalsSaveReqVO; +import jakarta.validation.*; +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.common.pojo.PageParam; + +/** + * 紧急响应时间区间设置 Service 接口 + * + * @author 芋道源码 + */ +public interface ProdEmergencyResponseIntervalsService { + + /** + * 创建紧急响应时间区间设置 + * + * @param createReqVO 创建信息 + * @return 编号 + */ + Long createProdEmergencyResponseIntervals(@Valid ProdEmergencyResponseIntervalsSaveReqVO createReqVO); + + /** + * 更新紧急响应时间区间设置 + * + * @param updateReqVO 更新信息 + */ + void updateProdEmergencyResponseIntervals(@Valid ProdEmergencyResponseIntervalsSaveReqVO updateReqVO); + + /** + * 删除紧急响应时间区间设置 + * + * @param id 编号 + */ + void deleteProdEmergencyResponseIntervals(Long id); + + /** + * 获得紧急响应时间区间设置 + * + * @param id 编号 + * @return 紧急响应时间区间设置 + */ + ProdEmergencyResponseIntervalsDO getProdEmergencyResponseIntervals(Long id); + + /** + * 获得紧急响应时间区间设置分页 + * + * @param pageReqVO 分页查询 + * @return 紧急响应时间区间设置分页 + */ + PageResult getProdEmergencyResponseIntervalsPage(ProdEmergencyResponseIntervalsPageReqVO pageReqVO); + +} \ No newline at end of file diff --git a/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/service/ProdEmergencyResponseService.java b/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/service/ProdEmergencyResponseService.java new file mode 100644 index 0000000..3da93b2 --- /dev/null +++ b/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/service/ProdEmergencyResponseService.java @@ -0,0 +1,57 @@ +package com.tashow.cloud.product.service; + +import java.util.*; + +import com.tashow.cloud.productapi.api.product.dto.ProdEmergencyResponseDO; +import com.tashow.cloud.productapi.api.product.vo.prodemergencyresponse.ProdEmergencyResponsePageReqVO; +import com.tashow.cloud.productapi.api.product.vo.prodemergencyresponse.ProdEmergencyResponseSaveReqVO; +import jakarta.validation.*; +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.common.pojo.PageParam; + +/** + * 商品紧急响应服务设置 Service 接口 + * + * @author 芋道源码 + */ +public interface ProdEmergencyResponseService { + + /** + * 创建商品紧急响应服务设置 + * + * @param createReqVO 创建信息 + * @return 编号 + */ + Long createProdEmergencyResponse(@Valid ProdEmergencyResponseSaveReqVO createReqVO); + + /** + * 更新商品紧急响应服务设置 + * + * @param updateReqVO 更新信息 + */ + void updateProdEmergencyResponse(@Valid ProdEmergencyResponseSaveReqVO updateReqVO); + + /** + * 删除商品紧急响应服务设置 + * + * @param id 编号 + */ + void deleteProdEmergencyResponse(Long id); + + /** + * 获得商品紧急响应服务设置 + * + * @param id 编号 + * @return 商品紧急响应服务设置 + */ + ProdEmergencyResponseDO getProdEmergencyResponse(Long id); + + /** + * 获得商品紧急响应服务设置分页 + * + * @param pageReqVO 分页查询 + * @return 商品紧急响应服务设置分页 + */ + PageResult getProdEmergencyResponsePage(ProdEmergencyResponsePageReqVO pageReqVO); + +} \ No newline at end of file diff --git a/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/service/ProdExtendService.java b/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/service/ProdExtendService.java new file mode 100644 index 0000000..08e856a --- /dev/null +++ b/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/service/ProdExtendService.java @@ -0,0 +1,15 @@ +package com.tashow.cloud.product.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.tashow.cloud.productapi.api.product.dto.ProdExtendDO; + + +/** + * 商品属性 Service 接口 + * + * @author 芋道源码 + */ +public interface ProdExtendService extends IService { + + +} \ No newline at end of file diff --git a/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/service/ProdPropService.java b/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/service/ProdPropService.java new file mode 100644 index 0000000..513a4a6 --- /dev/null +++ b/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/service/ProdPropService.java @@ -0,0 +1,65 @@ +package com.tashow.cloud.product.service; + +import java.util.*; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.tashow.cloud.productapi.api.product.dto.ProdPropDO; +import com.tashow.cloud.productapi.api.product.vo.prod.ProdSaveReqVO; +import com.tashow.cloud.productapi.api.product.vo.prodprop.ProdPropPageReqVO; +import com.tashow.cloud.productapi.api.product.vo.prodprop.ProdPropSaveReqVO; +import com.tashow.cloud.productapi.api.product.vo.sku.SkuPropVO; +import jakarta.validation.*; +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.common.pojo.PageParam; + +/** + * 商品属性 Service 接口 + * + * @author 芋道源码 + */ +public interface ProdPropService extends IService { + + /** + * 创建商品属性 + * + * @param createReqVO 创建信息 + * @return 编号 + */ + Long createProdProp(@Valid ProdPropSaveReqVO createReqVO); + + void saveProdPropAndValues(ProdSaveReqVO createReqVO); + + + + void updateProdPropAndValues(SkuPropVO skuPropVO); + /** + * 更新商品属性 + * + * @param updateReqVO 更新信息 + */ + void updateProdProp(@Valid ProdPropSaveReqVO updateReqVO); + + /** + * 删除商品属性 + * + * @param id 编号 + */ + void deleteProdProp(Long id); + + /** + * 获得商品属性 + * + * @param id 编号 + * @return 商品属性 + */ + ProdPropDO getProdProp(Long id); + + /** + * 获得商品属性分页 + * + * @param pageReqVO 分页查询 + * @return 商品属性分页 + */ + PageResult getProdPropPage(ProdPropPageReqVO pageReqVO); + +} \ No newline at end of file diff --git a/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/service/ProdPropValueService.java b/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/service/ProdPropValueService.java new file mode 100644 index 0000000..ba25b6b --- /dev/null +++ b/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/service/ProdPropValueService.java @@ -0,0 +1,59 @@ +package com.tashow.cloud.product.service; + +import java.util.*; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.tashow.cloud.productapi.api.product.dto.ProdPropDO; +import com.tashow.cloud.productapi.api.product.dto.ProdPropValueDO; +import com.tashow.cloud.productapi.api.product.vo.prodpropvalue.ProdPropValuePageReqVO; +import com.tashow.cloud.productapi.api.product.vo.prodpropvalue.ProdPropValueSaveReqVO; +import jakarta.validation.*; +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.common.pojo.PageParam; + +/** + * 属性规则 Service 接口 + * + * @author 芋道源码 + */ +public interface ProdPropValueService extends IService { + + /** + * 创建属性规则 + * + * @param createReqVO 创建信息 + * @return 编号 + */ + Long createProdPropValue(@Valid ProdPropValueSaveReqVO createReqVO); + + /** + * 更新属性规则 + * + * @param updateReqVO 更新信息 + */ + void updateProdPropValue(@Valid ProdPropValueSaveReqVO updateReqVO); + + /** + * 删除属性规则 + * + * @param id 编号 + */ + void deleteProdPropValue(Long id); + + /** + * 获得属性规则 + * + * @param id 编号 + * @return 属性规则 + */ + ProdPropValueDO getProdPropValue(Long id); + + /** + * 获得属性规则分页 + * + * @param pageReqVO 分页查询 + * @return 属性规则分页 + */ + PageResult getProdPropValuePage(ProdPropValuePageReqVO pageReqVO); + +} \ No newline at end of file diff --git a/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/service/ProdReservationConfigService.java b/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/service/ProdReservationConfigService.java new file mode 100644 index 0000000..a9cc2ab --- /dev/null +++ b/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/service/ProdReservationConfigService.java @@ -0,0 +1,57 @@ +package com.tashow.cloud.product.service; + +import java.util.*; + +import com.tashow.cloud.productapi.api.product.dto.ProdReservationConfigDO; +import com.tashow.cloud.productapi.api.product.vo.prodreservationconfig.ProdReservationConfigPageReqVO; +import com.tashow.cloud.productapi.api.product.vo.prodreservationconfig.ProdReservationConfigSaveReqVO; +import jakarta.validation.*; +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.common.pojo.PageParam; + +/** + * 商品预约配置 Service 接口 + * + * @author 芋道源码 + */ +public interface ProdReservationConfigService { + + /** + * 创建商品预约配置 + * + * @param createReqVO 创建信息 + * @return 编号 + */ + Long createProdReservationConfig(@Valid ProdReservationConfigSaveReqVO createReqVO); + + /** + * 更新商品预约配置 + * + * @param updateReqVO 更新信息 + */ + void updateProdReservationConfig(@Valid ProdReservationConfigSaveReqVO updateReqVO); + + /** + * 删除商品预约配置 + * + * @param id 编号 + */ + void deleteProdReservationConfig(Long id); + + /** + * 获得商品预约配置 + * + * @param id 编号 + * @return 商品预约配置 + */ + ProdReservationConfigDO getProdReservationConfig(Long id); + + /** + * 获得商品预约配置分页 + * + * @param pageReqVO 分页查询 + * @return 商品预约配置分页 + */ + PageResult getProdReservationConfigPage(ProdReservationConfigPageReqVO pageReqVO); + +} \ No newline at end of file diff --git a/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/service/ProdService.java b/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/service/ProdService.java new file mode 100644 index 0000000..c543068 --- /dev/null +++ b/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/service/ProdService.java @@ -0,0 +1,92 @@ +package com.tashow.cloud.product.service; + +import java.util.*; + +import com.tashow.cloud.productapi.api.product.dto.ProdDO; +import com.tashow.cloud.productapi.api.product.vo.prod.*; +import com.tashow.cloud.productapi.api.product.vo.sku.SkuRecycleBinVO; +import jakarta.validation.*; +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.common.pojo.PageParam; + +/** + * 商品 Service 接口 + * + * @author 芋道源码 + */ +public interface ProdService { + + /** + * 创建商品 + * + * @param createReqVO 创建信息 + * @return 编号 + */ + Long createProd(@Valid ProdSaveReqVO createReqVO); + + /** + * 创建商品服务配置 + * + * @param prodServiceVO 创建信息 + * @return 编号 + */ + void createProdService(@Valid ProdServiceVO prodServiceVO); + + + /** + * 修改商品服务配置 + * + * @param prodServiceInfoVO 创建信息 + * @return 编号 + */ + void uptateProdService(@Valid ProdServiceInfoVO prodServiceInfoVO); + + + + /** + * 获取商品服务配置 + * + * @param prodId 商品id + * @return 编号 + */ + ProdServiceVO getProdService(@Valid Long prodId); + + /** + * 更新商品 + * + * @param updateReqVO 更新信息 + */ + void updateProd(@Valid ProdSaveReqVO updateReqVO); + + /** + * 删除商品 + * + * @param id 编号 + */ + void deleteProd(Long id); + + /** + * 获得商品 + * + * @param id 编号 + * @return 商品 + */ + ProdDO getProd(Long id); + + /** + * 获得商品分页 + * + * @param pageReqVO 分页查询 + * @return 商品分页 + */ + PageResult getProdPage(ProdPageReqVO pageReqVO); + + PageResult getProdRecycleBinPageList(ProdRecycleBinVO prodRecycleBinVO); + + /** + * 恢复商品 + * + * @param ids + */ + void restoreProdList(List ids); +} \ No newline at end of file diff --git a/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/service/ProdServiceAreaRelevanceService.java b/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/service/ProdServiceAreaRelevanceService.java new file mode 100644 index 0000000..bb5412f --- /dev/null +++ b/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/service/ProdServiceAreaRelevanceService.java @@ -0,0 +1,57 @@ +package com.tashow.cloud.product.service; + +import java.util.*; + +import com.tashow.cloud.productapi.api.product.dto.ProdServiceAreaRelevanceDO; +import com.tashow.cloud.productapi.api.product.vo.prodservicearearelevance.ProdServiceAreaRelevancePageReqVO; +import com.tashow.cloud.productapi.api.product.vo.prodservicearearelevance.ProdServiceAreaRelevanceSaveReqVO; +import jakarta.validation.*; +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.common.pojo.PageParam; + +/** + * 商品与服务区域关联 Service 接口 + * + * @author 芋道源码 + */ +public interface ProdServiceAreaRelevanceService { + + /** + * 创建商品与服务区域关联 + * + * @param createReqVO 创建信息 + * @return 编号 + */ + Long createProdServiceAreaRelevance(@Valid ProdServiceAreaRelevanceSaveReqVO createReqVO); + + /** + * 更新商品与服务区域关联 + * + * @param updateReqVO 更新信息 + */ + void updateProdServiceAreaRelevance(@Valid ProdServiceAreaRelevanceSaveReqVO updateReqVO); + + /** + * 删除商品与服务区域关联 + * + * @param id 编号 + */ + void deleteProdServiceAreaRelevance(Long id); + + /** + * 获得商品与服务区域关联 + * + * @param id 编号 + * @return 商品与服务区域关联 + */ + ProdServiceAreaRelevanceDO getProdServiceAreaRelevance(Long id); + + /** + * 获得商品与服务区域关联分页 + * + * @param pageReqVO 分页查询 + * @return 商品与服务区域关联分页 + */ + PageResult getProdServiceAreaRelevancePage(ProdServiceAreaRelevancePageReqVO pageReqVO); + +} \ No newline at end of file diff --git a/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/service/ProdServiceAreasService.java b/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/service/ProdServiceAreasService.java new file mode 100644 index 0000000..d43455d --- /dev/null +++ b/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/service/ProdServiceAreasService.java @@ -0,0 +1,59 @@ +package com.tashow.cloud.product.service; + +import java.util.*; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.tashow.cloud.productapi.api.product.dto.ProdPropDO; +import com.tashow.cloud.productapi.api.product.dto.ProdServiceAreasDO; +import com.tashow.cloud.productapi.api.product.vo.prodserviceareas.ProdServiceAreasPageReqVO; +import com.tashow.cloud.productapi.api.product.vo.prodserviceareas.ProdServiceAreasSaveReqVO; +import jakarta.validation.*; +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.common.pojo.PageParam; + +/** + * 服务区域 Service 接口 + * + * @author 芋道源码 + */ +public interface ProdServiceAreasService extends IService { + + /** + * 创建服务区域 + * + * @param createReqVO 创建信息 + * @return 编号 + */ + Long createProdServiceAreas(@Valid ProdServiceAreasSaveReqVO createReqVO); + + /** + * 更新服务区域 + * + * @param updateReqVO 更新信息 + */ + void updateProdServiceAreas(@Valid ProdServiceAreasSaveReqVO updateReqVO); + + /** + * 删除服务区域 + * + * @param id 编号 + */ + void deleteProdServiceAreas(Long id); + + /** + * 获得服务区域 + * + * @param id 编号 + * @return 服务区域 + */ + ProdServiceAreasDO getProdServiceAreas(Long id); + + /** + * 获得服务区域分页 + * + * @param pageReqVO 分页查询 + * @return 服务区域分页 + */ + PageResult getProdServiceAreasPage(ProdServiceAreasPageReqVO pageReqVO); + +} \ No newline at end of file diff --git a/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/service/ProdServiceOverAreaRulesService.java b/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/service/ProdServiceOverAreaRulesService.java new file mode 100644 index 0000000..1f8f2b1 --- /dev/null +++ b/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/service/ProdServiceOverAreaRulesService.java @@ -0,0 +1,57 @@ +package com.tashow.cloud.product.service; + +import java.util.*; + +import com.tashow.cloud.productapi.api.product.dto.ProdServiceOverAreaRulesDO; +import com.tashow.cloud.productapi.api.product.vo.prodserviceoverarearules.ProdServiceOverAreaRulesPageReqVO; +import com.tashow.cloud.productapi.api.product.vo.prodserviceoverarearules.ProdServiceOverAreaRulesSaveReqVO; +import jakarta.validation.*; +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.common.pojo.PageParam; + +/** + * 超区规则 Service 接口 + * + * @author 芋道源码 + */ +public interface ProdServiceOverAreaRulesService { + + /** + * 创建超区规则 + * + * @param createReqVO 创建信息 + * @return 编号 + */ + Long createProdServiceOverAreaRules(@Valid ProdServiceOverAreaRulesSaveReqVO createReqVO); + + /** + * 更新超区规则 + * + * @param updateReqVO 更新信息 + */ + void updateProdServiceOverAreaRules(@Valid ProdServiceOverAreaRulesSaveReqVO updateReqVO); + + /** + * 删除超区规则 + * + * @param id 编号 + */ + void deleteProdServiceOverAreaRules(Long id); + + /** + * 获得超区规则 + * + * @param id 编号 + * @return 超区规则 + */ + ProdServiceOverAreaRulesDO getProdServiceOverAreaRules(Long id); + + /** + * 获得超区规则分页 + * + * @param pageReqVO 分页查询 + * @return 超区规则分页 + */ + PageResult getProdServiceOverAreaRulesPage(ProdServiceOverAreaRulesPageReqVO pageReqVO); + +} \ No newline at end of file diff --git a/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/service/ProdTagsService.java b/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/service/ProdTagsService.java new file mode 100644 index 0000000..ade48cb --- /dev/null +++ b/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/service/ProdTagsService.java @@ -0,0 +1,57 @@ +package com.tashow.cloud.product.service; + +import java.util.*; + +import com.tashow.cloud.productapi.api.product.dto.ProdTagsDO; +import com.tashow.cloud.productapi.api.product.vo.prodtags.ProdTagsPageReqVO; +import com.tashow.cloud.productapi.api.product.vo.prodtags.ProdTagsSaveReqVO; +import jakarta.validation.*; +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.common.pojo.PageParam; + +/** + * 商品和标签管理 Service 接口 + * + * @author 芋道源码 + */ +public interface ProdTagsService { + + /** + * 创建商品和标签管理 + * + * @param createReqVO 创建信息 + * @return 编号 + */ + Long createProdTags(@Valid ProdTagsSaveReqVO createReqVO); + + /** + * 更新商品和标签管理 + * + * @param updateReqVO 更新信息 + */ + void updateProdTags(@Valid ProdTagsSaveReqVO updateReqVO); + + /** + * 删除商品和标签管理 + * + * @param id 编号 + */ + void deleteProdTags(Long id); + + /** + * 获得商品和标签管理 + * + * @param id 编号 + * @return 商品和标签管理 + */ + ProdTagsDO getProdTags(Long id); + + /** + * 获得商品和标签管理分页 + * + * @param pageReqVO 分页查询 + * @return 商品和标签管理分页 + */ + PageResult getProdTagsPage(ProdTagsPageReqVO pageReqVO); + +} \ No newline at end of file diff --git a/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/service/ProdWeightRangePricesService.java b/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/service/ProdWeightRangePricesService.java new file mode 100644 index 0000000..8188680 --- /dev/null +++ b/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/service/ProdWeightRangePricesService.java @@ -0,0 +1,57 @@ +package com.tashow.cloud.product.service; + +import java.util.*; + +import com.tashow.cloud.productapi.api.product.dto.ProdWeightRangePricesDO; +import com.tashow.cloud.productapi.api.product.vo.prodweightrangeprices.ProdWeightRangePricesPageReqVO; +import com.tashow.cloud.productapi.api.product.vo.prodweightrangeprices.ProdWeightRangePricesSaveReqVO; +import jakarta.validation.*; +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.common.pojo.PageParam; + +/** + * 体重区间价格 Service 接口 + * + * @author 芋道源码 + */ +public interface ProdWeightRangePricesService { + + /** + * 创建体重区间价格 + * + * @param createReqVO 创建信息 + * @return 编号 + */ + Long createProdWeightRangePrices(@Valid ProdWeightRangePricesSaveReqVO createReqVO); + + /** + * 更新体重区间价格 + * + * @param updateReqVO 更新信息 + */ + void updateProdWeightRangePrices(@Valid ProdWeightRangePricesSaveReqVO updateReqVO); + + /** + * 删除体重区间价格 + * + * @param id 编号 + */ + void deleteProdWeightRangePrices(Long id); + + /** + * 获得体重区间价格 + * + * @param id 编号 + * @return 体重区间价格 + */ + ProdWeightRangePricesDO getProdWeightRangePrices(Long id); + + /** + * 获得体重区间价格分页 + * + * @param pageReqVO 分页查询 + * @return 体重区间价格分页 + */ + PageResult getProdWeightRangePricesPage(ProdWeightRangePricesPageReqVO pageReqVO); + +} \ No newline at end of file diff --git a/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/service/ProductOrderLimitService.java b/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/service/ProductOrderLimitService.java new file mode 100644 index 0000000..719042b --- /dev/null +++ b/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/service/ProductOrderLimitService.java @@ -0,0 +1,57 @@ +package com.tashow.cloud.product.service; + +import java.util.*; + +import com.tashow.cloud.productapi.api.product.dto.ProductOrderLimitDO; +import com.tashow.cloud.productapi.api.product.vo.productorderlimit.ProductOrderLimitPageReqVO; +import com.tashow.cloud.productapi.api.product.vo.productorderlimit.ProductOrderLimitSaveReqVO; +import jakarta.validation.*; +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.common.pojo.PageParam; + +/** + * 商品接单上限设置 Service 接口 + * + * @author 芋道源码 + */ +public interface ProductOrderLimitService { + + /** + * 创建商品接单上限设置 + * + * @param createReqVO 创建信息 + * @return 编号 + */ + Long createProductOrderLimit(@Valid ProductOrderLimitSaveReqVO createReqVO); + + /** + * 更新商品接单上限设置 + * + * @param updateReqVO 更新信息 + */ + void updateProductOrderLimit(@Valid ProductOrderLimitSaveReqVO updateReqVO); + + /** + * 删除商品接单上限设置 + * + * @param id 编号 + */ + void deleteProductOrderLimit(Long id); + + /** + * 获得商品接单上限设置 + * + * @param id 编号 + * @return 商品接单上限设置 + */ + ProductOrderLimitDO getProductOrderLimit(Long id); + + /** + * 获得商品接单上限设置分页 + * + * @param pageReqVO 分页查询 + * @return 商品接单上限设置分页 + */ + PageResult getProductOrderLimitPage(ProductOrderLimitPageReqVO pageReqVO); + +} \ No newline at end of file diff --git a/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/service/ShopDetailService.java b/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/service/ShopDetailService.java new file mode 100644 index 0000000..4c358aa --- /dev/null +++ b/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/service/ShopDetailService.java @@ -0,0 +1,59 @@ +package com.tashow.cloud.product.service; + +import java.util.*; + +import com.tashow.cloud.productapi.api.product.dto.ShopDetailDO; +import com.tashow.cloud.productapi.api.product.vo.shopdetail +.ShopDetailPageReqVO; +import com.tashow.cloud.productapi.api.product.vo.shopdetail +.ShopDetailSaveReqVO; +import jakarta.validation.*; +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.common.pojo.PageParam; + +/** + * 店铺信息 Service 接口 + * + * @author 芋道源码 + */ +public interface ShopDetailService { + + /** + * 创建店铺信息 + * + * @param createReqVO 创建信息 + * @return 编号 + */ + Long createShopDetail(@Valid ShopDetailSaveReqVO createReqVO); + + /** + * 更新店铺信息 + * + * @param updateReqVO 更新信息 + */ + void updateShopDetail(@Valid ShopDetailSaveReqVO updateReqVO); + + /** + * 删除店铺信息 + * + * @param id 编号 + */ + void deleteShopDetail(Long id); + + /** + * 获得店铺信息 + * + * @param id 编号 + * @return 店铺信息 + */ + ShopDetailDO getShopDetail(Long id); + + /** + * 获得店铺信息分页 + * + * @param pageReqVO 分页查询 + * @return 店铺信息分页 + */ + PageResult getShopDetailPage(ShopDetailPageReqVO pageReqVO); + +} \ No newline at end of file diff --git a/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/service/SkuService.java b/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/service/SkuService.java new file mode 100644 index 0000000..ce7375f --- /dev/null +++ b/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/service/SkuService.java @@ -0,0 +1,179 @@ +package com.tashow.cloud.product.service; + +import java.util.*; + +import com.tashow.cloud.productapi.api.product.dto.*; +import com.tashow.cloud.productapi.api.product.vo.prodpropvalue.ProPageReqVO; +import com.tashow.cloud.productapi.api.product.vo.prodpropvalue.ProPropRecycleBinVO; +import com.tashow.cloud.productapi.api.product.vo.sku.*; +import jakarta.validation.*; +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.common.pojo.PageParam; +import org.springframework.web.bind.annotation.RequestParam; + +/** + * 单品SKU Service 接口 + * + * @author 芋道源码 + */ +public interface SkuService { + + /** + * 创建单品SKU + * + * @param createReqVO 创建信息 + * @return 编号 + */ + Long createSku(@Valid SkuSaveReqVO createReqVO); + + /** + * 创建sku扩展服务配置 + * + * @param skuExtendVO 更新信息 + */ + void createSkuExtend(@Valid SkuExtendVO skuExtendVO); + + + + + /** + * 创建sku扩展服务配置 + * + * @param formId 更新信息 + */ + SkuExtendVO getSkuExtend(Integer formId); + + + /** + * 更新单品SKU + * + * @param updateReqVO 更新信息 + */ + void updateSku(@Valid SkuSaveReqVO updateReqVO); + + /** + * 更新sku规格 + * + * @param skuPropVO 更新信息 + */ + void updateProp(SkuPropVO skuPropVO); + + + void updatePropVal(Long id, String propValue); + + /** + * 删除规格值 + * + * @param id 删除规格值 + */ + void deleteProp(Long id); + + /** + * 禁用规格值 + * + * @param id 删除规格值 + */ + void disableProp(Long id,Integer state); + + + + SkuPropInfoVO getSKuPropList(Long prodId); + + PageResult getSKuPropRecycleBinList(ProPageReqVO proPageReqVO); + /** + * 删除单品SKU + * + * @param id 编号 + */ + void deleteSku(Long id); + + /** + * 批量删除删SKU + * + * @param ids + */ + void deleteSkus(List ids); + + /** + * 恢复SKU + * + * @param ids + */ + void restoreSkuList(List ids); + /** + * 恢复规格 + * + * @param ids + */ + void restorePropList(List ids); + + + /** + * 删除单品SKU + * + * @param id 编号 + */ + void updatSkuIsShelf(Long id,Integer isShelf); + + /** + * 批量删除删SKU + * + * @param ids + */ + void updatSkuIsShelfs(List ids,Integer isShelf); + + + /** + * 获得单品SKU + * + * @param id 编号 + * @return 单品SKU + */ + SkuDO getSku(Long id); + + + + PageResult getSkuRecycleBinPageList(SkuPageReqVO pageReqVO); + + + PageResult getSkuPageList(SkuPageReqVO pageReqVO); + /** + * 获得单品SKU分页 + * + * @param pageReqVO 分页查询 + * @return 单品SKU分页 + */ + PageResult getSkuPage(SkuPageReqVO pageReqVO); + + + /** + * 创建sku扩展服务配置 + * + * @param skuServiceDetailsList 更新信息 + */ + void updateServiceDetails(List skuServiceDetailsList); + + + /** + * 修物料配置 + * + * @param skuServiceMaterialList 更新信息 + */ + void updateMaterial(List skuServiceMaterialList); + + + /** + * 修改接运地址配置 + * + * @param skuServiceTransportDOList 更新信息 + */ + void updateTransportAdress( List skuServiceTransportDOList); + + /** + * 修改配送方式 + * + * @param skuServiceDeliverList 更新信息 + */ + void updateDeliver(List skuServiceDeliverList); + +} \ No newline at end of file diff --git a/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/service/SkuServiceDeliverService.java b/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/service/SkuServiceDeliverService.java new file mode 100644 index 0000000..eda1e66 --- /dev/null +++ b/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/service/SkuServiceDeliverService.java @@ -0,0 +1,57 @@ +package com.tashow.cloud.product.service; + +import java.util.*; + +import com.tashow.cloud.productapi.api.product.dto.SkuServiceDeliverDO; +import com.tashow.cloud.productapi.api.product.vo.skuservicedeliver.SkuServiceDeliverPageReqVO; +import com.tashow.cloud.productapi.api.product.vo.skuservicedeliver.SkuServiceDeliverSaveReqVO; +import jakarta.validation.*; +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.common.pojo.PageParam; + +/** + * 服务交付方式 Service 接口 + * + * @author 芋道源码 + */ +public interface SkuServiceDeliverService { + + /** + * 创建服务交付方式 + * + * @param createReqVO 创建信息 + * @return 编号 + */ + Long createSkuServiceDeliver(@Valid SkuServiceDeliverSaveReqVO createReqVO); + + /** + * 更新服务交付方式 + * + * @param updateReqVO 更新信息 + */ + void updateSkuServiceDeliver(@Valid SkuServiceDeliverSaveReqVO updateReqVO); + + /** + * 删除服务交付方式 + * + * @param id 编号 + */ + void deleteSkuServiceDeliver(Long id); + + /** + * 获得服务交付方式 + * + * @param id 编号 + * @return 服务交付方式 + */ + SkuServiceDeliverDO getSkuServiceDeliver(Long id); + + /** + * 获得服务交付方式分页 + * + * @param pageReqVO 分页查询 + * @return 服务交付方式分页 + */ + PageResult getSkuServiceDeliverPage(SkuServiceDeliverPageReqVO pageReqVO); + +} \ No newline at end of file diff --git a/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/service/SkuServiceDetailsService.java b/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/service/SkuServiceDetailsService.java new file mode 100644 index 0000000..c6024ee --- /dev/null +++ b/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/service/SkuServiceDetailsService.java @@ -0,0 +1,57 @@ +package com.tashow.cloud.product.service; + +import java.util.*; + +import com.tashow.cloud.productapi.api.product.dto.SkuServiceDetailsDO; +import com.tashow.cloud.productapi.api.product.vo.skuservicedetails.SkuServiceDetailsPageReqVO; +import com.tashow.cloud.productapi.api.product.vo.skuservicedetails.SkuServiceDetailsSaveReqVO; +import jakarta.validation.*; +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.common.pojo.PageParam; + +/** + * 服务详情 Service 接口 + * + * @author 芋道源码 + */ +public interface SkuServiceDetailsService { + + /** + * 创建服务详情 + * + * @param createReqVO 创建信息 + * @return 编号 + */ + Long createSkuServiceDetails(@Valid SkuServiceDetailsSaveReqVO createReqVO); + + /** + * 更新服务详情 + * + * @param updateReqVO 更新信息 + */ + void updateSkuServiceDetails(@Valid SkuServiceDetailsSaveReqVO updateReqVO); + + /** + * 删除服务详情 + * + * @param id 编号 + */ + void deleteSkuServiceDetails(Long id); + + /** + * 获得服务详情 + * + * @param id 编号 + * @return 服务详情 + */ + SkuServiceDetailsDO getSkuServiceDetails(Long id); + + /** + * 获得服务详情分页 + * + * @param pageReqVO 分页查询 + * @return 服务详情分页 + */ + PageResult getSkuServiceDetailsPage(SkuServiceDetailsPageReqVO pageReqVO); + +} \ No newline at end of file diff --git a/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/service/SkuServiceMaterialService.java b/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/service/SkuServiceMaterialService.java new file mode 100644 index 0000000..2fa6d13 --- /dev/null +++ b/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/service/SkuServiceMaterialService.java @@ -0,0 +1,57 @@ +package com.tashow.cloud.product.service; + +import java.util.*; + +import com.tashow.cloud.productapi.api.product.dto.SkuServiceMaterialDO; +import com.tashow.cloud.productapi.api.product.vo.skuservicematerial.SkuServiceMaterialPageReqVO; +import com.tashow.cloud.productapi.api.product.vo.skuservicematerial.SkuServiceMaterialSaveReqVO; +import jakarta.validation.*; +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.common.pojo.PageParam; + +/** + * 服务物料详情 Service 接口 + * + * @author 芋道源码 + */ +public interface SkuServiceMaterialService { + + /** + * 创建服务物料详情 + * + * @param createReqVO 创建信息 + * @return 编号 + */ + Long createSkuServiceMaterial(@Valid SkuServiceMaterialSaveReqVO createReqVO); + + /** + * 更新服务物料详情 + * + * @param updateReqVO 更新信息 + */ + void updateSkuServiceMaterial(@Valid SkuServiceMaterialSaveReqVO updateReqVO); + + /** + * 删除服务物料详情 + * + * @param id 编号 + */ + void deleteSkuServiceMaterial(Long id); + + /** + * 获得服务物料详情 + * + * @param id 编号 + * @return 服务物料详情 + */ + SkuServiceMaterialDO getSkuServiceMaterial(Long id); + + /** + * 获得服务物料详情分页 + * + * @param pageReqVO 分页查询 + * @return 服务物料详情分页 + */ + PageResult getSkuServiceMaterialPage(SkuServiceMaterialPageReqVO pageReqVO); + +} \ No newline at end of file diff --git a/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/service/SkuServiceTransportService.java b/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/service/SkuServiceTransportService.java new file mode 100644 index 0000000..1469304 --- /dev/null +++ b/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/service/SkuServiceTransportService.java @@ -0,0 +1,57 @@ +package com.tashow.cloud.product.service; + +import java.util.*; + +import com.tashow.cloud.productapi.api.product.dto.SkuServiceTransportDO; +import com.tashow.cloud.productapi.api.product.vo.skuservicetransport.SkuServiceTransportPageReqVO; +import com.tashow.cloud.productapi.api.product.vo.skuservicetransport.SkuServiceTransportSaveReqVO; +import jakarta.validation.*; +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.common.pojo.PageParam; + +/** + * 服务遗体运输 Service 接口 + * + * @author 芋道源码 + */ +public interface SkuServiceTransportService { + + /** + * 创建服务遗体运输 + * + * @param createReqVO 创建信息 + * @return 编号 + */ + Long createSkuServiceTransport(@Valid SkuServiceTransportSaveReqVO createReqVO); + + /** + * 更新服务遗体运输 + * + * @param updateReqVO 更新信息 + */ + void updateSkuServiceTransport(@Valid SkuServiceTransportSaveReqVO updateReqVO); + + /** + * 删除服务遗体运输 + * + * @param id 编号 + */ + void deleteSkuServiceTransport(Long id); + + /** + * 获得服务遗体运输 + * + * @param id 编号 + * @return 服务遗体运输 + */ + SkuServiceTransportDO getSkuServiceTransport(Long id); + + /** + * 获得服务遗体运输分页 + * + * @param pageReqVO 分页查询 + * @return 服务遗体运输分页 + */ + PageResult getSkuServiceTransportPage(SkuServiceTransportPageReqVO pageReqVO); + +} \ No newline at end of file diff --git a/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/service/SkuServicesFormService.java b/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/service/SkuServicesFormService.java new file mode 100644 index 0000000..598a094 --- /dev/null +++ b/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/service/SkuServicesFormService.java @@ -0,0 +1,57 @@ +package com.tashow.cloud.product.service; + +import java.util.*; + +import com.tashow.cloud.productapi.api.product.dto.SkuServicesFormDO; +import com.tashow.cloud.productapi.api.product.vo.skuservicesform.SkuServicesFormPageReqVO; +import com.tashow.cloud.productapi.api.product.vo.skuservicesform.SkuServicesFormSaveReqVO; +import jakarta.validation.*; +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.common.pojo.PageParam; + +/** + * 商品SKU扩展服务表单 Service 接口 + * + * @author 芋道源码 + */ +public interface SkuServicesFormService { + + /** + * 创建商品SKU扩展服务表单 + * + * @param createReqVO 创建信息 + * @return 编号 + */ + Long createSkuServicesForm(@Valid SkuServicesFormSaveReqVO createReqVO); + + /** + * 更新商品SKU扩展服务表单 + * + * @param updateReqVO 更新信息 + */ + void updateSkuServicesForm(@Valid SkuServicesFormSaveReqVO updateReqVO); + + /** + * 删除商品SKU扩展服务表单 + * + * @param id 编号 + */ + void deleteSkuServicesForm(Long id); + + /** + * 获得商品SKU扩展服务表单 + * + * @param id 编号 + * @return 商品SKU扩展服务表单 + */ + SkuServicesFormDO getSkuServicesForm(Long id); + + /** + * 获得商品SKU扩展服务表单分页 + * + * @param pageReqVO 分页查询 + * @return 商品SKU扩展服务表单分页 + */ + PageResult getSkuServicesFormPage(SkuServicesFormPageReqVO pageReqVO); + +} \ No newline at end of file diff --git a/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/service/impl/CategoryServiceImpl.java b/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/service/impl/CategoryServiceImpl.java new file mode 100644 index 0000000..beb94c8 --- /dev/null +++ b/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/service/impl/CategoryServiceImpl.java @@ -0,0 +1,91 @@ +package com.tashow.cloud.product.service.impl; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.tashow.cloud.common.exception.ErrorCode; +import com.tashow.cloud.productapi.api.product.dto.CategoryDO; +import com.tashow.cloud.productapi.api.product.dto.ProdPropValueDO; +import com.tashow.cloud.product.mapper.CategoryMapper; +import com.tashow.cloud.product.mapper.ProdPropValueMapper; +import com.tashow.cloud.product.service.CategoryService; +import com.tashow.cloud.productapi.api.product.dto.CategoryDto; +import com.tashow.cloud.productapi.api.product.vo.CategoryPageReqVO; +import com.tashow.cloud.productapi.api.product.vo.CategorySaveReqVO; +import com.tashow.cloud.productapi.enums.ErrorCodeConstants; +import org.springframework.stereotype.Service; +import jakarta.annotation.Resource; +import org.springframework.validation.annotation.Validated; +import org.springframework.transaction.annotation.Transactional; + +import java.util.*; + +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.common.pojo.PageParam; +import com.tashow.cloud.common.util.object.BeanUtils; + + +import static com.tashow.cloud.common.exception.util.ServiceExceptionUtil.exception; + +/** + * 产品类目 Service 实现类 + * + * @author 芋道源码 + */ +@Service +@Validated +public class CategoryServiceImpl extends ServiceImpl implements CategoryService { + + @Resource + private CategoryMapper categoryMapper; + + @Override + public List categoryList(Integer grade, Long categoryId,String categoryName, Integer status) { + List categoryMenuList =categoryMapper.selectCategoryList(grade, categoryId,categoryName, status,1L); + return categoryMenuList; + } + + @Override + public Long createCategory(CategorySaveReqVO createReqVO) { + // 插入 + CategoryDO category = BeanUtils.toBean(createReqVO, CategoryDO.class); + category.setShopId(1L); + categoryMapper.insert(category); + // 返回 + return category.getCategoryId(); + } + + @Override + public void updateCategory(CategorySaveReqVO updateReqVO) { + // 校验存在 + validateCategoryExists(updateReqVO.getCategoryId()); + // 更新 + CategoryDO updateObj = BeanUtils.toBean(updateReqVO, CategoryDO.class); + categoryMapper.updateById(updateObj); + } + + @Override + public void deleteCategory(Long id) { + // 校验存在 + validateCategoryExists(id); + // 删除 + categoryMapper.deleteById(id); + } + + private void validateCategoryExists(Long id) { + if (categoryMapper.selectById(id) == null) { + throw exception(ErrorCodeConstants.CATEGORY_NOT_EXISTS); + } + } + + @Override + public CategoryDO getCategory(Long id) { + return categoryMapper.selectById(id); + } + + @Override + public PageResult getCategoryPage(CategoryPageReqVO pageReqVO) { + return null; + } + + +} \ No newline at end of file diff --git a/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/service/impl/ProdAdditionalFeeDatesServiceImpl.java b/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/service/impl/ProdAdditionalFeeDatesServiceImpl.java new file mode 100644 index 0000000..15856ed --- /dev/null +++ b/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/service/impl/ProdAdditionalFeeDatesServiceImpl.java @@ -0,0 +1,76 @@ +package com.tashow.cloud.product.service.impl; + +import com.tashow.cloud.productapi.api.product.dto.ProdAdditionalFeeDatesDO; +import com.tashow.cloud.product.mapper.ProdAdditionalFeeDatesMapper; +import com.tashow.cloud.product.service.ProdAdditionalFeeDatesService; +import com.tashow.cloud.productapi.api.product.vo.prodadditionalfeedates.ProdAdditionalFeeDatesPageReqVO; +import com.tashow.cloud.productapi.api.product.vo.prodadditionalfeedates.ProdAdditionalFeeDatesSaveReqVO; +import com.tashow.cloud.productapi.enums.ErrorCodeConstants; +import org.springframework.stereotype.Service; +import jakarta.annotation.Resource; +import org.springframework.validation.annotation.Validated; +import org.springframework.transaction.annotation.Transactional; + +import java.util.*; +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.common.pojo.PageParam; +import com.tashow.cloud.common.util.object.BeanUtils; + + +import static com.tashow.cloud.common.exception.util.ServiceExceptionUtil.exception; + +/** + * 特殊日期附加费用规则 Service 实现类 + * + * @author 芋道源码 + */ +@Service +@Validated +public class ProdAdditionalFeeDatesServiceImpl implements ProdAdditionalFeeDatesService { + + @Resource + private ProdAdditionalFeeDatesMapper prodAdditionalFeeDatesMapper; + + @Override + public Long createProdAdditionalFeeDates(ProdAdditionalFeeDatesSaveReqVO createReqVO) { + // 插入 + ProdAdditionalFeeDatesDO prodAdditionalFeeDates = BeanUtils.toBean(createReqVO, ProdAdditionalFeeDatesDO.class); + prodAdditionalFeeDatesMapper.insert(prodAdditionalFeeDates); + // 返回 + return prodAdditionalFeeDates.getId(); + } + + @Override + public void updateProdAdditionalFeeDates(ProdAdditionalFeeDatesSaveReqVO updateReqVO) { + // 校验存在 + validateProdAdditionalFeeDatesExists(updateReqVO.getId()); + // 更新 + ProdAdditionalFeeDatesDO updateObj = BeanUtils.toBean(updateReqVO, ProdAdditionalFeeDatesDO.class); + prodAdditionalFeeDatesMapper.updateById(updateObj); + } + + @Override + public void deleteProdAdditionalFeeDates(Long id) { + // 校验存在 + validateProdAdditionalFeeDatesExists(id); + // 删除 + prodAdditionalFeeDatesMapper.deleteById(id); + } + + private void validateProdAdditionalFeeDatesExists(Long id) { + if (prodAdditionalFeeDatesMapper.selectById(id) == null) { + throw exception(ErrorCodeConstants.PROD_ADDITIONAL_FEE_DATES_NOT_EXISTS); + } + } + + @Override + public ProdAdditionalFeeDatesDO getProdAdditionalFeeDates(Long id) { + return prodAdditionalFeeDatesMapper.selectById(id); + } + + @Override + public PageResult getProdAdditionalFeeDatesPage(ProdAdditionalFeeDatesPageReqVO pageReqVO) { + return null; + } + +} \ No newline at end of file diff --git a/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/service/impl/ProdAdditionalFeePeriodsServiceImpl.java b/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/service/impl/ProdAdditionalFeePeriodsServiceImpl.java new file mode 100644 index 0000000..8bae84c --- /dev/null +++ b/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/service/impl/ProdAdditionalFeePeriodsServiceImpl.java @@ -0,0 +1,76 @@ +package com.tashow.cloud.product.service.impl; + +import com.tashow.cloud.productapi.api.product.dto.ProdAdditionalFeePeriodsDO; +import com.tashow.cloud.product.mapper.ProdAdditionalFeePeriodsMapper; +import com.tashow.cloud.product.service.ProdAdditionalFeePeriodsService; +import com.tashow.cloud.productapi.api.product.vo.prodadditionalfeeperiods.ProdAdditionalFeePeriodsPageReqVO; +import com.tashow.cloud.productapi.api.product.vo.prodadditionalfeeperiods.ProdAdditionalFeePeriodsSaveReqVO; +import com.tashow.cloud.productapi.enums.ErrorCodeConstants; +import org.springframework.stereotype.Service; +import jakarta.annotation.Resource; +import org.springframework.validation.annotation.Validated; +import org.springframework.transaction.annotation.Transactional; + +import java.util.*; +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.common.pojo.PageParam; +import com.tashow.cloud.common.util.object.BeanUtils; + + +import static com.tashow.cloud.common.exception.util.ServiceExceptionUtil.exception; + +/** + * 特殊时段附加费用规则 Service 实现类 + * + * @author 芋道源码 + */ +@Service +@Validated +public class ProdAdditionalFeePeriodsServiceImpl implements ProdAdditionalFeePeriodsService { + + @Resource + private ProdAdditionalFeePeriodsMapper prodAdditionalFeePeriodsMapper; + + @Override + public Long createProdAdditionalFeePeriods(ProdAdditionalFeePeriodsSaveReqVO createReqVO) { + // 插入 + ProdAdditionalFeePeriodsDO prodAdditionalFeePeriods = BeanUtils.toBean(createReqVO, ProdAdditionalFeePeriodsDO.class); + prodAdditionalFeePeriodsMapper.insert(prodAdditionalFeePeriods); + // 返回 + return prodAdditionalFeePeriods.getId(); + } + + @Override + public void updateProdAdditionalFeePeriods(ProdAdditionalFeePeriodsSaveReqVO updateReqVO) { + // 校验存在 + validateProdAdditionalFeePeriodsExists(updateReqVO.getId()); + // 更新 + ProdAdditionalFeePeriodsDO updateObj = BeanUtils.toBean(updateReqVO, ProdAdditionalFeePeriodsDO.class); + prodAdditionalFeePeriodsMapper.updateById(updateObj); + } + + @Override + public void deleteProdAdditionalFeePeriods(Long id) { + // 校验存在 + validateProdAdditionalFeePeriodsExists(id); + // 删除 + prodAdditionalFeePeriodsMapper.deleteById(id); + } + + private void validateProdAdditionalFeePeriodsExists(Long id) { + if (prodAdditionalFeePeriodsMapper.selectById(id) == null) { + throw exception(ErrorCodeConstants.PROD_ADDITIONAL_FEE_PERIODS_NOT_EXISTS); + } + } + + @Override + public ProdAdditionalFeePeriodsDO getProdAdditionalFeePeriods(Long id) { + return prodAdditionalFeePeriodsMapper.selectById(id); + } + + @Override + public PageResult getProdAdditionalFeePeriodsPage(ProdAdditionalFeePeriodsPageReqVO pageReqVO) { + return null; + } + +} \ No newline at end of file diff --git a/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/service/impl/ProdEmergencyResponseIntervalsServiceImpl.java b/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/service/impl/ProdEmergencyResponseIntervalsServiceImpl.java new file mode 100644 index 0000000..00d580a --- /dev/null +++ b/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/service/impl/ProdEmergencyResponseIntervalsServiceImpl.java @@ -0,0 +1,76 @@ +package com.tashow.cloud.product.service.impl; + +import com.tashow.cloud.productapi.api.product.dto.ProdEmergencyResponseIntervalsDO; +import com.tashow.cloud.product.mapper.ProdEmergencyResponseIntervalsMapper; +import com.tashow.cloud.product.service.ProdEmergencyResponseIntervalsService; +import com.tashow.cloud.productapi.api.product.vo.prodemergencyresponseintervals.ProdEmergencyResponseIntervalsPageReqVO; +import com.tashow.cloud.productapi.api.product.vo.prodemergencyresponseintervals.ProdEmergencyResponseIntervalsSaveReqVO; +import com.tashow.cloud.productapi.enums.ErrorCodeConstants; +import org.springframework.stereotype.Service; +import jakarta.annotation.Resource; +import org.springframework.validation.annotation.Validated; +import org.springframework.transaction.annotation.Transactional; + +import java.util.*; +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.common.pojo.PageParam; +import com.tashow.cloud.common.util.object.BeanUtils; + + +import static com.tashow.cloud.common.exception.util.ServiceExceptionUtil.exception; + +/** + * 紧急响应时间区间设置 Service 实现类 + * + * @author 芋道源码 + */ +@Service +@Validated +public class ProdEmergencyResponseIntervalsServiceImpl implements ProdEmergencyResponseIntervalsService { + + @Resource + private ProdEmergencyResponseIntervalsMapper prodEmergencyResponseIntervalsMapper; + + @Override + public Long createProdEmergencyResponseIntervals(ProdEmergencyResponseIntervalsSaveReqVO createReqVO) { + // 插入 + ProdEmergencyResponseIntervalsDO prodEmergencyResponseIntervals = BeanUtils.toBean(createReqVO, ProdEmergencyResponseIntervalsDO.class); + prodEmergencyResponseIntervalsMapper.insert(prodEmergencyResponseIntervals); + // 返回 + return prodEmergencyResponseIntervals.getId(); + } + + @Override + public void updateProdEmergencyResponseIntervals(ProdEmergencyResponseIntervalsSaveReqVO updateReqVO) { + // 校验存在 + validateProdEmergencyResponseIntervalsExists(updateReqVO.getId()); + // 更新 + ProdEmergencyResponseIntervalsDO updateObj = BeanUtils.toBean(updateReqVO, ProdEmergencyResponseIntervalsDO.class); + prodEmergencyResponseIntervalsMapper.updateById(updateObj); + } + + @Override + public void deleteProdEmergencyResponseIntervals(Long id) { + // 校验存在 + validateProdEmergencyResponseIntervalsExists(id); + // 删除 + prodEmergencyResponseIntervalsMapper.deleteById(id); + } + + private void validateProdEmergencyResponseIntervalsExists(Long id) { + if (prodEmergencyResponseIntervalsMapper.selectById(id) == null) { + throw exception(ErrorCodeConstants.PROD_EMERGENCY_RESPONSE_INTERVALS_NOT_EXISTS); + } + } + + @Override + public ProdEmergencyResponseIntervalsDO getProdEmergencyResponseIntervals(Long id) { + return prodEmergencyResponseIntervalsMapper.selectById(id); + } + + @Override + public PageResult getProdEmergencyResponseIntervalsPage(ProdEmergencyResponseIntervalsPageReqVO pageReqVO) { + return null; + } + +} \ No newline at end of file diff --git a/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/service/impl/ProdEmergencyResponseServiceImpl.java b/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/service/impl/ProdEmergencyResponseServiceImpl.java new file mode 100644 index 0000000..c00baf5 --- /dev/null +++ b/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/service/impl/ProdEmergencyResponseServiceImpl.java @@ -0,0 +1,76 @@ +package com.tashow.cloud.product.service.impl; + +import com.tashow.cloud.productapi.api.product.dto.ProdEmergencyResponseDO; +import com.tashow.cloud.product.mapper.ProdEmergencyResponseMapper; +import com.tashow.cloud.product.service.ProdEmergencyResponseService; +import com.tashow.cloud.productapi.api.product.vo.prodemergencyresponse.ProdEmergencyResponsePageReqVO; +import com.tashow.cloud.productapi.api.product.vo.prodemergencyresponse.ProdEmergencyResponseSaveReqVO; +import com.tashow.cloud.productapi.enums.ErrorCodeConstants; +import org.springframework.stereotype.Service; +import jakarta.annotation.Resource; +import org.springframework.validation.annotation.Validated; +import org.springframework.transaction.annotation.Transactional; + +import java.util.*; +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.common.pojo.PageParam; +import com.tashow.cloud.common.util.object.BeanUtils; + + +import static com.tashow.cloud.common.exception.util.ServiceExceptionUtil.exception; + +/** + * 商品紧急响应服务设置 Service 实现类 + * + * @author 芋道源码 + */ +@Service +@Validated +public class ProdEmergencyResponseServiceImpl implements ProdEmergencyResponseService { + + @Resource + private ProdEmergencyResponseMapper prodEmergencyResponseMapper; + + @Override + public Long createProdEmergencyResponse(ProdEmergencyResponseSaveReqVO createReqVO) { + // 插入 + ProdEmergencyResponseDO prodEmergencyResponse = BeanUtils.toBean(createReqVO, ProdEmergencyResponseDO.class); + prodEmergencyResponseMapper.insert(prodEmergencyResponse); + // 返回 + return prodEmergencyResponse.getId(); + } + + @Override + public void updateProdEmergencyResponse(ProdEmergencyResponseSaveReqVO updateReqVO) { + // 校验存在 + validateProdEmergencyResponseExists(updateReqVO.getId()); + // 更新 + ProdEmergencyResponseDO updateObj = BeanUtils.toBean(updateReqVO, ProdEmergencyResponseDO.class); + prodEmergencyResponseMapper.updateById(updateObj); + } + + @Override + public void deleteProdEmergencyResponse(Long id) { + // 校验存在 + validateProdEmergencyResponseExists(id); + // 删除 + prodEmergencyResponseMapper.deleteById(id); + } + + private void validateProdEmergencyResponseExists(Long id) { + if (prodEmergencyResponseMapper.selectById(id) == null) { + throw exception(ErrorCodeConstants.PROD_EMERGENCY_RESPONSE_NOT_EXISTS); + } + } + + @Override + public ProdEmergencyResponseDO getProdEmergencyResponse(Long id) { + return prodEmergencyResponseMapper.selectById(id); + } + + @Override + public PageResult getProdEmergencyResponsePage(ProdEmergencyResponsePageReqVO pageReqVO) { + return null; + } + +} \ No newline at end of file diff --git a/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/service/impl/ProdExtendServiceImpl.java b/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/service/impl/ProdExtendServiceImpl.java new file mode 100644 index 0000000..171d92f --- /dev/null +++ b/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/service/impl/ProdExtendServiceImpl.java @@ -0,0 +1,23 @@ +package com.tashow.cloud.product.service.impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.tashow.cloud.productapi.api.product.dto.ProdExtendDO; +import com.tashow.cloud.productapi.api.product.dto.ProdPropValueDO; +import com.tashow.cloud.product.mapper.ProdExtendMapper; +import com.tashow.cloud.product.mapper.ProdPropValueMapper; +import com.tashow.cloud.product.service.ProdExtendService; +import com.tashow.cloud.product.service.ProdPropValueService; +import org.springframework.stereotype.Service; +import org.springframework.validation.annotation.Validated; + + +/** + * 超区规则 Service 实现类 + * + * @author 芋道源码 + */ +@Service +@Validated +public class ProdExtendServiceImpl extends ServiceImpl implements ProdExtendService { + +} \ No newline at end of file diff --git a/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/service/impl/ProdPropServiceImpl.java b/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/service/impl/ProdPropServiceImpl.java new file mode 100644 index 0000000..0b5a7c2 --- /dev/null +++ b/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/service/impl/ProdPropServiceImpl.java @@ -0,0 +1,136 @@ +package com.tashow.cloud.product.service.impl; + +import cn.hutool.core.collection.CollUtil; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.tashow.cloud.productapi.api.product.dto.ProdPropDO; +import com.tashow.cloud.productapi.api.product.dto.ProdPropValueDO; +import com.tashow.cloud.product.mapper.ProdPropMapper; +import com.tashow.cloud.product.mapper.ProdPropValueMapper; +import com.tashow.cloud.product.service.ProdPropService; +import com.tashow.cloud.productapi.api.product.vo.prod.ProdSaveReqVO; +import com.tashow.cloud.productapi.api.product.vo.prodprop.ProdPropPageReqVO; +import com.tashow.cloud.productapi.api.product.vo.prodprop.ProdPropSaveReqVO; +import com.tashow.cloud.productapi.api.product.vo.sku.SkuPropVO; +import com.tashow.cloud.productapi.enums.BaseEnum; +import com.tashow.cloud.productapi.enums.ErrorCodeConstants; +import org.springframework.stereotype.Service; +import jakarta.annotation.Resource; +import org.springframework.validation.annotation.Validated; +import org.springframework.transaction.annotation.Transactional; + +import java.util.*; +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.common.pojo.PageParam; +import com.tashow.cloud.common.util.object.BeanUtils; + + +import static com.tashow.cloud.common.exception.util.ServiceExceptionUtil.exception; + +/** + * 商品属性 Service 实现类 + * + * @author 芋道源码 + */ +@Service +@Validated +public class ProdPropServiceImpl extends ServiceImpl implements ProdPropService { + + @Resource + private ProdPropMapper prodPropMapper; + @Resource + private ProdPropValueMapper prodPropValueMapper; + + @Override + public Long createProdProp(ProdPropSaveReqVO createReqVO) { + // 插入 + ProdPropDO prodProp = BeanUtils.toBean(createReqVO, ProdPropDO.class); + prodPropMapper.insert(prodProp); + // 返回 + return prodProp.getPropId(); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void saveProdPropAndValues(ProdSaveReqVO createReqVOInfo) { + + for (ProdPropSaveReqVO createReqVO : createReqVOInfo.getProdPropSaveReqVO()){ + ProdPropDO dbProdProp = prodPropMapper.getProdPropByPropNameAndShopId(createReqVO.getPropName(), createReqVO.getProdId(), createReqVO.getRule()); + if (dbProdProp != null) { + throw new RuntimeException("已有相同名称规格"); + } + ProdPropDO prodProp = BeanUtils.toBean(createReqVO, ProdPropDO.class); + prodProp.setProdId(createReqVOInfo.getProdId()); + prodPropMapper.insert(prodProp); + if (CollUtil.isEmpty(createReqVO.getProdPropValues())) { + return; + } + for (ProdPropValueDO prodPropValueDO : createReqVO.getProdPropValues()){ + prodPropValueDO.setPropId(prodProp.getId()); + prodPropValueMapper.insert(prodPropValueDO); + } + } + } + + @Override + @Transactional + public void updateProdPropAndValues(SkuPropVO skuPropVO) { + for (ProdPropSaveReqVO createReqVO : skuPropVO.getProdPropSaveReqVO()){ + ProdPropDO dbProdProp = prodPropMapper.getProdPropByPropNameAndShopId(createReqVO.getPropName(), createReqVO.getProdId(), createReqVO.getRule()); + if (dbProdProp != null) { + throw new RuntimeException("已有相同名称规格"); + } + ProdPropDO prodProp = BeanUtils.toBean(createReqVO, ProdPropDO.class); + if(Objects.equals(BaseEnum.YES_ONE.getKey(),createReqVO.getIsExist())){ + prodPropMapper.insert(prodProp); + }else { + prodPropMapper.updateById(prodProp); + } + if (CollUtil.isEmpty(createReqVO.getProdPropValues())) { + return; + } + for (ProdPropValueDO prodPropValueDO : createReqVO.getProdPropValues()){ + if(Objects.equals(BaseEnum.YES_ONE.getKey(),prodPropValueDO.getIsExist())){ + prodPropValueDO.setPropId(prodProp.getId()); + prodPropValueMapper.insert(prodPropValueDO); + }else { + prodPropValueMapper.updateById(prodPropValueDO); + } + } + } + } + + + @Override + public void updateProdProp(ProdPropSaveReqVO updateReqVO) { + // 校验存在 + validateProdPropExists(updateReqVO.getPropId()); + // 更新 + ProdPropDO updateObj = BeanUtils.toBean(updateReqVO, ProdPropDO.class); + prodPropMapper.updateById(updateObj); + } + + @Override + public void deleteProdProp(Long id) { + // 校验存在 + validateProdPropExists(id); + // 删除 + prodPropMapper.deleteById(id); + } + + private void validateProdPropExists(Long id) { + if (prodPropMapper.selectById(id) == null) { + throw exception(ErrorCodeConstants.PROD_PROP_NOT_EXISTS); + } + } + + @Override + public ProdPropDO getProdProp(Long id) { + return prodPropMapper.selectById(id); + } + + @Override + public PageResult getProdPropPage(ProdPropPageReqVO pageReqVO) { + return null; + } + +} \ No newline at end of file diff --git a/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/service/impl/ProdPropValueServiceImpl.java b/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/service/impl/ProdPropValueServiceImpl.java new file mode 100644 index 0000000..a724c9f --- /dev/null +++ b/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/service/impl/ProdPropValueServiceImpl.java @@ -0,0 +1,76 @@ +package com.tashow.cloud.product.service.impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.tashow.cloud.productapi.api.product.dto.ProdPropDO; +import com.tashow.cloud.productapi.api.product.dto.ProdPropValueDO; +import com.tashow.cloud.product.mapper.ProdPropMapper; +import com.tashow.cloud.product.mapper.ProdPropValueMapper; +import com.tashow.cloud.product.service.ProdPropValueService; +import com.tashow.cloud.productapi.api.product.vo.prodpropvalue.ProdPropValuePageReqVO; +import com.tashow.cloud.productapi.api.product.vo.prodpropvalue.ProdPropValueSaveReqVO; +import com.tashow.cloud.productapi.enums.ErrorCodeConstants; +import org.springframework.stereotype.Service; +import jakarta.annotation.Resource; +import org.springframework.validation.annotation.Validated; +import org.springframework.transaction.annotation.Transactional; +import java.util.*; +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.common.pojo.PageParam; +import com.tashow.cloud.common.util.object.BeanUtils; +import static com.tashow.cloud.common.exception.util.ServiceExceptionUtil.exception; + +/** + * 属性规则 Service 实现类 + * + * @author 芋道源码 + */ +@Service +@Validated +public class ProdPropValueServiceImpl extends ServiceImpl implements ProdPropValueService { + + @Resource + private ProdPropValueMapper prodPropValueMapper; + + @Override + public Long createProdPropValue(ProdPropValueSaveReqVO createReqVO) { + // 插入 + ProdPropValueDO prodPropValue = BeanUtils.toBean(createReqVO, ProdPropValueDO.class); + prodPropValueMapper.insert(prodPropValue); + // 返回 + return prodPropValue.getPropId(); + } + + @Override + public void updateProdPropValue(ProdPropValueSaveReqVO updateReqVO) { + // 校验存在 + validateProdPropValueExists(updateReqVO.getPropId()); + // 更新 + ProdPropValueDO updateObj = BeanUtils.toBean(updateReqVO, ProdPropValueDO.class); + prodPropValueMapper.updateById(updateObj); + } + + @Override + public void deleteProdPropValue(Long id) { + // 校验存在 + validateProdPropValueExists(id); + // 删除 + prodPropValueMapper.deleteProdPropValueById(id); + } + + private void validateProdPropValueExists(Long id) { + if (prodPropValueMapper.selectById(id) == null) { + throw exception(ErrorCodeConstants.PROD_PROP_VALUE_NOT_EXISTS); + } + } + + @Override + public ProdPropValueDO getProdPropValue(Long id) { + return prodPropValueMapper.selectById(id); + } + + @Override + public PageResult getProdPropValuePage(ProdPropValuePageReqVO pageReqVO) { + return null; + } + +} \ No newline at end of file diff --git a/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/service/impl/ProdReservationConfigServiceImpl.java b/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/service/impl/ProdReservationConfigServiceImpl.java new file mode 100644 index 0000000..dc977b3 --- /dev/null +++ b/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/service/impl/ProdReservationConfigServiceImpl.java @@ -0,0 +1,76 @@ +package com.tashow.cloud.product.service.impl; + +import com.tashow.cloud.productapi.api.product.dto.ProdReservationConfigDO; +import com.tashow.cloud.product.mapper.ProdReservationConfigMapper; +import com.tashow.cloud.product.service.ProdReservationConfigService; +import com.tashow.cloud.productapi.api.product.vo.prodreservationconfig.ProdReservationConfigPageReqVO; +import com.tashow.cloud.productapi.api.product.vo.prodreservationconfig.ProdReservationConfigSaveReqVO; +import com.tashow.cloud.productapi.enums.ErrorCodeConstants; +import org.springframework.stereotype.Service; +import jakarta.annotation.Resource; +import org.springframework.validation.annotation.Validated; +import org.springframework.transaction.annotation.Transactional; + +import java.util.*; +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.common.pojo.PageParam; +import com.tashow.cloud.common.util.object.BeanUtils; + + +import static com.tashow.cloud.common.exception.util.ServiceExceptionUtil.exception; + +/** + * 商品预约配置 Service 实现类 + * + * @author 芋道源码 + */ +@Service +@Validated +public class ProdReservationConfigServiceImpl implements ProdReservationConfigService { + + @Resource + private ProdReservationConfigMapper prodReservationConfigMapper; + + @Override + public Long createProdReservationConfig(ProdReservationConfigSaveReqVO createReqVO) { + // 插入 + ProdReservationConfigDO prodReservationConfig = BeanUtils.toBean(createReqVO, ProdReservationConfigDO.class); + prodReservationConfigMapper.insert(prodReservationConfig); + // 返回 + return prodReservationConfig.getId(); + } + + @Override + public void updateProdReservationConfig(ProdReservationConfigSaveReqVO updateReqVO) { + // 校验存在 + validateProdReservationConfigExists(updateReqVO.getId()); + // 更新 + ProdReservationConfigDO updateObj = BeanUtils.toBean(updateReqVO, ProdReservationConfigDO.class); + prodReservationConfigMapper.updateById(updateObj); + } + + @Override + public void deleteProdReservationConfig(Long id) { + // 校验存在 + validateProdReservationConfigExists(id); + // 删除 + prodReservationConfigMapper.deleteById(id); + } + + private void validateProdReservationConfigExists(Long id) { + if (prodReservationConfigMapper.selectById(id) == null) { + throw exception(ErrorCodeConstants.PROD_RESERVATION_CONFIG_NOT_EXISTS); + } + } + + @Override + public ProdReservationConfigDO getProdReservationConfig(Long id) { + return prodReservationConfigMapper.selectById(id); + } + + @Override + public PageResult getProdReservationConfigPage(ProdReservationConfigPageReqVO pageReqVO) { + return null; + } + +} \ No newline at end of file diff --git a/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/service/impl/ProdServiceAreaRelevanceServiceImpl.java b/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/service/impl/ProdServiceAreaRelevanceServiceImpl.java new file mode 100644 index 0000000..516cf31 --- /dev/null +++ b/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/service/impl/ProdServiceAreaRelevanceServiceImpl.java @@ -0,0 +1,76 @@ +package com.tashow.cloud.product.service.impl; + +import com.tashow.cloud.productapi.api.product.dto.ProdServiceAreaRelevanceDO; +import com.tashow.cloud.product.mapper.ProdServiceAreaRelevanceMapper; +import com.tashow.cloud.product.service.ProdServiceAreaRelevanceService; +import com.tashow.cloud.productapi.api.product.vo.prodservicearearelevance.ProdServiceAreaRelevancePageReqVO; +import com.tashow.cloud.productapi.api.product.vo.prodservicearearelevance.ProdServiceAreaRelevanceSaveReqVO; +import com.tashow.cloud.productapi.enums.ErrorCodeConstants; +import org.springframework.stereotype.Service; +import jakarta.annotation.Resource; +import org.springframework.validation.annotation.Validated; +import org.springframework.transaction.annotation.Transactional; + +import java.util.*; +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.common.pojo.PageParam; +import com.tashow.cloud.common.util.object.BeanUtils; + + +import static com.tashow.cloud.common.exception.util.ServiceExceptionUtil.exception; + +/** + * 商品与服务区域关联 Service 实现类 + * + * @author 芋道源码 + */ +@Service +@Validated +public class ProdServiceAreaRelevanceServiceImpl implements ProdServiceAreaRelevanceService { + + @Resource + private ProdServiceAreaRelevanceMapper prodServiceAreaRelevanceMapper; + + @Override + public Long createProdServiceAreaRelevance(ProdServiceAreaRelevanceSaveReqVO createReqVO) { + // 插入 + ProdServiceAreaRelevanceDO prodServiceAreaRelevance = BeanUtils.toBean(createReqVO, ProdServiceAreaRelevanceDO.class); + prodServiceAreaRelevanceMapper.insert(prodServiceAreaRelevance); + // 返回 + return prodServiceAreaRelevance.getProdId(); + } + + @Override + public void updateProdServiceAreaRelevance(ProdServiceAreaRelevanceSaveReqVO updateReqVO) { + // 校验存在 + validateProdServiceAreaRelevanceExists(updateReqVO.getProdId()); + // 更新 + ProdServiceAreaRelevanceDO updateObj = BeanUtils.toBean(updateReqVO, ProdServiceAreaRelevanceDO.class); + prodServiceAreaRelevanceMapper.updateById(updateObj); + } + + @Override + public void deleteProdServiceAreaRelevance(Long id) { + // 校验存在 + validateProdServiceAreaRelevanceExists(id); + // 删除 + prodServiceAreaRelevanceMapper.deleteById(id); + } + + private void validateProdServiceAreaRelevanceExists(Long id) { + if (prodServiceAreaRelevanceMapper.selectById(id) == null) { + throw exception(ErrorCodeConstants.PROD_SERVICE_AREA_RELEVANCE_NOT_EXISTS); + } + } + + @Override + public ProdServiceAreaRelevanceDO getProdServiceAreaRelevance(Long id) { + return prodServiceAreaRelevanceMapper.selectById(id); + } + + @Override + public PageResult getProdServiceAreaRelevancePage(ProdServiceAreaRelevancePageReqVO pageReqVO) { + return null; + } + +} \ No newline at end of file diff --git a/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/service/impl/ProdServiceAreasServiceImpl.java b/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/service/impl/ProdServiceAreasServiceImpl.java new file mode 100644 index 0000000..fe7ccfb --- /dev/null +++ b/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/service/impl/ProdServiceAreasServiceImpl.java @@ -0,0 +1,79 @@ +package com.tashow.cloud.product.service.impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.tashow.cloud.productapi.api.product.dto.ProdPropDO; +import com.tashow.cloud.productapi.api.product.dto.ProdServiceAreasDO; +import com.tashow.cloud.product.mapper.ProdPropMapper; +import com.tashow.cloud.product.mapper.ProdServiceAreasMapper; +import com.tashow.cloud.product.service.ProdServiceAreasService; +import com.tashow.cloud.productapi.api.product.vo.prodserviceareas.ProdServiceAreasPageReqVO; +import com.tashow.cloud.productapi.api.product.vo.prodserviceareas.ProdServiceAreasSaveReqVO; +import com.tashow.cloud.productapi.enums.ErrorCodeConstants; +import org.springframework.stereotype.Service; +import jakarta.annotation.Resource; +import org.springframework.validation.annotation.Validated; +import org.springframework.transaction.annotation.Transactional; + +import java.util.*; +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.common.pojo.PageParam; +import com.tashow.cloud.common.util.object.BeanUtils; + + +import static com.tashow.cloud.common.exception.util.ServiceExceptionUtil.exception; + +/** + * 服务区域 Service 实现类 + * + * @author 芋道源码 + */ +@Service +@Validated +public class ProdServiceAreasServiceImpl extends ServiceImpl implements ProdServiceAreasService { + + @Resource + private ProdServiceAreasMapper prodServiceAreasMapper; + + @Override + public Long createProdServiceAreas(ProdServiceAreasSaveReqVO createReqVO) { + // 插入 + ProdServiceAreasDO prodServiceAreas = BeanUtils.toBean(createReqVO, ProdServiceAreasDO.class); + prodServiceAreasMapper.insert(prodServiceAreas); + // 返回 + return prodServiceAreas.getId(); + } + + @Override + public void updateProdServiceAreas(ProdServiceAreasSaveReqVO updateReqVO) { + // 校验存在 + validateProdServiceAreasExists(updateReqVO.getId()); + // 更新 + ProdServiceAreasDO updateObj = BeanUtils.toBean(updateReqVO, ProdServiceAreasDO.class); + prodServiceAreasMapper.updateById(updateObj); + } + + @Override + public void deleteProdServiceAreas(Long id) { + // 校验存在 + validateProdServiceAreasExists(id); + // 删除 + prodServiceAreasMapper.deleteById(id); + } + + private void validateProdServiceAreasExists(Long id) { + if (prodServiceAreasMapper.selectById(id) == null) { + throw exception(ErrorCodeConstants.PROD_SERVICE_AREAS_NOT_EXISTS); + } + } + + @Override + public ProdServiceAreasDO getProdServiceAreas(Long id) { + return prodServiceAreasMapper.selectById(id); + } + + @Override + public PageResult getProdServiceAreasPage(ProdServiceAreasPageReqVO pageReqVO) { + return null; + } + +} \ No newline at end of file diff --git a/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/service/impl/ProdServiceImpl.java b/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/service/impl/ProdServiceImpl.java new file mode 100644 index 0000000..2fa6345 --- /dev/null +++ b/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/service/impl/ProdServiceImpl.java @@ -0,0 +1,512 @@ +package com.tashow.cloud.product.service.impl; + +import cn.hutool.core.collection.CollectionUtil; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.tashow.cloud.common.util.date.DateUtils; +import com.tashow.cloud.mybatis.mybatis.core.util.MyBatisUtils; +import com.tashow.cloud.productapi.api.product.dto.*; +import com.tashow.cloud.product.mapper.*; +import com.tashow.cloud.product.service.ProdPropService; +import com.tashow.cloud.product.service.ProdService; + +import com.tashow.cloud.productapi.api.product.vo.prod.*; +import com.tashow.cloud.productapi.api.product.vo.prodadditionalfeedates.ProdAdditionalFeeBlackVO; +import com.tashow.cloud.productapi.api.product.vo.prodemergencyresponse.ProdEmergencyInfoVO; +import com.tashow.cloud.productapi.api.product.vo.prodreservationconfig.ProdReservationInfoReqVO; +import com.tashow.cloud.productapi.api.product.vo.prodreservationconfig.ProdReservationInfoVO; +import com.tashow.cloud.productapi.api.product.vo.sku.SkuPageReqVO; +import com.tashow.cloud.productapi.api.product.vo.sku.SkuRecycleBinVO; +import com.tashow.cloud.productapi.enums.BaseEnum; +import lombok.val; +import org.springframework.stereotype.Service; +import jakarta.annotation.Resource; +import org.springframework.validation.annotation.Validated; +import org.springframework.transaction.annotation.Transactional; + +import java.util.*; + +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.common.pojo.PageParam; +import com.tashow.cloud.common.util.object.BeanUtils; + + +import static com.tashow.cloud.common.exception.util.ServiceExceptionUtil.exception; + +/** + * 商品 Service 实现类 + * + * @author 芋道源码 + */ +@Service +@Validated +public class ProdServiceImpl implements ProdService { + + @Resource + private ProdMapper prodMapper; + @Resource + private SkuMapper skuMapper; + @Resource + private ProdPropService prodPropService; + @Resource + private ProdServiceAreasMapper prodServiceAreasMapper; + @Resource + private ProdServiceAreaRelevanceMapper prodServiceAreaRelevanceMapper; + @Resource + private ProdServiceOverAreaRulesMapper prodServiceOverAreaRulesMapper; + @Resource + private ProdReservationConfigMapper prodReservationConfigMapper; + @Resource + private ProdEmergencyResponseIntervalsMapper prodEmergencyResponseIntervalsMapper; + @Resource + private ProdEmergencyResponseMapper prodEmergencyResponseMapper; + @Resource + private ProductOrderLimitMapper productOrderLimitMapper; + @Resource + private ProdAdditionalFeeDatesMapper prodAdditionalFeeDatesMapper; + @Resource + private ProdAdditionalFeePeriodsMapper prodAdditionalFeePeriodsMapper; + @Resource + private ProdWeightRangePricesMapper prodWeightRangePricesMapper; + @Resource + private ProdExtendMapper prodExtendMapper; + + @Override + @Transactional + public Long createProd(ProdSaveReqVO createReqVO) { + // 插入 + ProdDO prod = BeanUtils.toBean(createReqVO, ProdDO.class); + prodMapper.insert(prod); + //保存sku + if (CollectionUtil.isNotEmpty(createReqVO.getSkuList())) { + List skuList = createReqVO.getSkuList(); + for (SkuDO sku : skuList) { + sku.setProdId(prod.getProdId()); + } + skuMapper.insertBatch(skuList); + } + createReqVO.setProdId(prod.getProdId()); + //保存规格 + prodPropService.saveProdPropAndValues(createReqVO); + ProdExtendDO prodExtendDO = new ProdExtendDO(); + prodExtendDO.setIsExpire(BaseEnum.YES_ONE.getKey()); + prodExtendDO.setIsExpire(BaseEnum.YES_ONE.getKey()); + prodExtendDO.setProdId(prod.getProdId()); + prodExtendMapper.insert(prodExtendDO); + // 返回 + return prod.getProdId(); + } + + @Override + @Transactional + public void createProdService(ProdServiceVO prodServiceVO) { + ProdDO prodDO = new ProdDO(); + prodDO.setProdId(prodServiceVO.getProdId()); + //服务区域设置 + if (Objects.equals(prodServiceVO.getRegionSwitch(), BaseEnum.YES_ONE.getKey())) { + prodDO.setRegionSwitch(BaseEnum.YES_ONE.getKey()); + //保存区域设置信息 + ProdServiceOverAreaRulesDO prodServiceOverAreaRules = BeanUtils.toBean(prodServiceVO.getProdServiceAreasInfo(), ProdServiceOverAreaRulesDO.class); + prodServiceOverAreaRules.setProdId(prodDO.getProdId()); + prodServiceOverAreaRulesMapper.insert(prodServiceOverAreaRules); + //保存区域 + List areaNameList = prodServiceVO.getProdServiceAreasInfo().getAreaNameList(); + for (String areaName : areaNameList) { + //查询区域信息 + ProdServiceAreasDO prodServiceAreas = prodServiceAreasMapper.selectOne(new LambdaQueryWrapper() + .eq(ProdServiceAreasDO::getAreaName, areaName) + ); + if (prodServiceAreas != null) { + //插入关联表 + prodServiceAreaRelevanceMapper.insert(new ProdServiceAreaRelevanceDO() + .setProdId(prodDO.getProdId()) + .setAreaId(prodServiceAreas.getId()) + ); + } + } + } + //预约设置设置 + /*if (Objects.equals(prodServiceVO.getReservationSwitch(), BaseEnum.YES_ONE.getKey())) { + prodDO.setReservationSwitch(BaseEnum.YES_ONE.getKey()); + ProdReservationInfoVO prodReservationConfigDO = prodServiceVO.getProdReservationConfig(); + prodReservationConfigDO.setProdId(prodDO.getProdId()); + prodReservationConfigMapper.insert(prodReservationConfigDO); + }*/ + //紧急响应设置 + if (Objects.equals(prodServiceVO.getEmergencySwitch(), BaseEnum.YES_ONE.getKey())) { + prodDO.setEmergencySwitch(BaseEnum.YES_ONE.getKey()); + // 插入 + ProdEmergencyResponseDO prodEmergencyResponse = BeanUtils.toBean(prodServiceVO.prodEmergencyInfoVO, ProdEmergencyResponseDO.class); + prodEmergencyResponse.setProdId(prodDO.getProdId()); + prodEmergencyResponseMapper.insert(prodEmergencyResponse); + for (ProdEmergencyResponseIntervalsDO prodEmergencyResponseIntervals : prodServiceVO.prodEmergencyInfoVO.getProdEmergencyResponseIntervalsList()) { + prodEmergencyResponseIntervals.setConfigId(prodEmergencyResponse.getId()); + prodEmergencyResponseIntervalsMapper.insert(prodEmergencyResponseIntervals); + } + } + //接单上线设置 + if (Objects.equals(prodServiceVO.getOrderLimitSwitch(), BaseEnum.YES_ONE.getKey())) { + prodDO.setOrderLimitSwitch(BaseEnum.YES_ONE.getKey()); + ProductOrderLimitDO productOrderLimit = prodServiceVO.productOrderLimitVO; + productOrderLimit.setProdId(prodDO.getProdId()); + productOrderLimitMapper.insert(productOrderLimit); + } + //特殊时段设置 + if (Objects.equals(prodServiceVO.getAdditionalSwitch(), BaseEnum.YES_ONE.getKey())) { + prodDO.setAdditionalSwitch(BaseEnum.YES_ONE.getKey()); + for (ProdAdditionalFeeDatesDO prodAdditionalFeeDates : prodServiceVO.getProdAdditionalFeeDatesList()) { + prodAdditionalFeeDates.setProdId(prodServiceVO.getProdId()); + prodAdditionalFeeDatesMapper.insert(prodAdditionalFeeDates); + } + } + //特殊日期设置 + if (Objects.equals(prodServiceVO.getAdditionalFeeSwitch(), BaseEnum.YES_ONE.getKey())) { + prodDO.setAdditionalFeeSwitch(BaseEnum.YES_ONE.getKey()); + for (ProdAdditionalFeePeriodsDO prodAdditionalFeePeriods : prodServiceVO.getProdAdditionalFeePeriodsList()) { + prodAdditionalFeePeriods.setProdId(prodServiceVO.getProdId()); + prodAdditionalFeePeriodsMapper.insert(prodAdditionalFeePeriods); + } + } + //体重设置 + /* if (Objects.equals(prodServiceVO.getWeightSwitch(), BaseEnum.YES_ONE.getKey())) { + prodDO.setWeightSwitch(BaseEnum.YES_ONE.getKey()); + ProdWeightRangePricesDO prodWeightRangePrices = prodServiceVO.prodWeightConfig; + prodWeightRangePrices.setProdId(prodDO.getProdId()); + prodWeightRangePricesMapper.insert(prodWeightRangePrices); + }*/ + prodMapper.updateById(prodDO); + } + + @Override + @Transactional + public void uptateProdService(ProdServiceInfoVO prodServiceVO) { + + ProdDO prod = BeanUtils.toBean(prodServiceVO, ProdDO.class); + //服务区域设置 + if (Objects.equals(prodServiceVO.getRegionSwitch(), BaseEnum.YES_ONE.getKey())) { + ProdServiceOverAreaRulesDO overAreaRules = prodServiceOverAreaRulesMapper.selectOne(new LambdaQueryWrapper() + .eq(ProdServiceOverAreaRulesDO::getProdId, prodServiceVO.getProdId())); + if (overAreaRules == null) { + prod.setRegionSwitch(BaseEnum.YES_ONE.getKey()); + //保存区域设置信息 + ProdServiceOverAreaRulesDO prodServiceOverAreaRules = BeanUtils.toBean(prodServiceVO.getProdServiceAreasInfo(), ProdServiceOverAreaRulesDO.class); + prodServiceOverAreaRules.setProdId(prod.getProdId()); + prodServiceOverAreaRulesMapper.insert(prodServiceOverAreaRules); + //保存区域 + List areaNameList = prodServiceVO.getProdServiceAreasInfo().getAreaNameList(); + for (String areaName : areaNameList) { + //查询区域信息 + ProdServiceAreasDO prodServiceAreas = prodServiceAreasMapper.selectOne(new LambdaQueryWrapper() + .eq(ProdServiceAreasDO::getAreaName, areaName) + ); + if (prodServiceAreas != null) { + //插入关联表 + prodServiceAreaRelevanceMapper.insert(new ProdServiceAreaRelevanceDO() + .setProdId(prod.getProdId()) + .setAreaId(prodServiceAreas.getId()) + ); + } + } + } else { + //保存区域设置信息 + ProdServiceOverAreaRulesDO prodServiceOverAreaRules = BeanUtils.toBean(prodServiceVO.getProdServiceAreasInfo(), ProdServiceOverAreaRulesDO.class); + prodServiceOverAreaRules.setProdId(prodServiceVO.getProdId()); + prodServiceOverAreaRulesMapper.updateById(prodServiceOverAreaRules); + //先批量删除区域关联表 + prodServiceAreaRelevanceMapper.deleteRelevance(prodServiceVO.getProdId()); + //保存区域 + List areaNameList = prodServiceVO.getProdServiceAreasInfo().getAreaNameList(); + for (String areaName : areaNameList) { + //查询区域信息 + ProdServiceAreasDO prodServiceAreas = prodServiceAreasMapper.selectOne(new LambdaQueryWrapper() + .eq(ProdServiceAreasDO::getAreaName, areaName) + ); + if (prodServiceAreas != null) { + //插入关联表 + prodServiceAreaRelevanceMapper.insert(new ProdServiceAreaRelevanceDO() + .setProdId(prodServiceVO.getProdId()) + .setAreaId(prodServiceAreas.getId()) + ); + } + } + } + + } + //预约设置设置 + if (Objects.equals(prodServiceVO.getReservationSwitch(), BaseEnum.YES_ONE.getKey())) { + ProdReservationConfigDO reservationConfig = prodReservationConfigMapper.selectOne(new LambdaQueryWrapper() + .eq(ProdReservationConfigDO::getProdId, prodServiceVO.getProdId())); + ProdReservationInfoReqVO prodReservationInfoVO = prodServiceVO.getProdReservationConfig(); + if (reservationConfig == null) { + prod.setReservationSwitch(BaseEnum.YES_ONE.getKey()); + ProdReservationConfigDO prodReservationConfigDO = BeanUtils.toBean(prodReservationInfoVO, ProdReservationConfigDO.class); + prodReservationConfigDO.setProdId(prod.getProdId()); + prodReservationConfigDO.setTimeSlot(prodReservationInfoVO.getTimeBook().getTimeSlot()); + prodReservationConfigDO.setReservationTimeSlots(prodReservationInfoVO.getTimeBook().getReservationTimeSlots()); + prodReservationConfigMapper.insert(prodReservationConfigDO); + //先删除在新增 + prodAdditionalFeeDatesMapper.deleteAdditionalFeeDates(prod.getProdId(), 2); + if (prodServiceVO.getProdReservationConfig().getProdReservationBlackList() != null) { + for (ProdAdditionalFeeBlackVO prodAdditionalFeeBlackVO : prodServiceVO.getProdReservationConfig().getProdReservationBlackList()) { + ProdAdditionalFeeDatesDO prodAdditionalFeeDatesDO = BeanUtils.toBean(prodAdditionalFeeBlackVO, ProdAdditionalFeeDatesDO.class); + prodAdditionalFeeDatesDO.setProdId(prod.getProdId()); + prodAdditionalFeeDatesDO.setType(2); + prodAdditionalFeeDatesMapper.insert(prodAdditionalFeeDatesDO); + } + } + } else { + ProdReservationConfigDO prodReservationConfigDO = BeanUtils.toBean(prodServiceVO.getProdReservationConfig(), ProdReservationConfigDO.class); + prodReservationConfigDO.setTimeSlot(prodReservationInfoVO.getTimeBook().getTimeSlot()); + prodReservationConfigDO.setReservationTimeSlots(prodReservationInfoVO.getTimeBook().getReservationTimeSlots()); + prodReservationConfigMapper.updateById(prodReservationConfigDO); + //先删除在新增 + prodAdditionalFeeDatesMapper.deleteAdditionalFeeDates(prod.getProdId(), 2); + if (prodServiceVO.getProdReservationConfig().getProdReservationBlackList() != null) { + for (ProdAdditionalFeeBlackVO prodAdditionalFeeBlackVO : prodServiceVO.getProdReservationConfig().getProdReservationBlackList()) { + ProdAdditionalFeeDatesDO prodAdditionalFeeDatesDO = BeanUtils.toBean(prodAdditionalFeeBlackVO, ProdAdditionalFeeDatesDO.class); + prodAdditionalFeeDatesDO.setProdId(prod.getProdId()); + prodAdditionalFeeDatesDO.setType(2); + prodAdditionalFeeDatesMapper.insert(prodAdditionalFeeDatesDO); + } + } + } + } + //紧急响应设置 + if (Objects.equals(prodServiceVO.getEmergencySwitch(), BaseEnum.YES_ONE.getKey())) { + ProdEmergencyResponseDO rmergencyResponse = prodEmergencyResponseMapper.selectOne(new LambdaQueryWrapper() + .eq(ProdEmergencyResponseDO::getProdId, prodServiceVO.getProdId())); + if (rmergencyResponse == null) { + prod.setEmergencySwitch(BaseEnum.YES_ONE.getKey()); + // 插入 + ProdEmergencyResponseDO prodEmergencyResponse = BeanUtils.toBean(prodServiceVO.getProdEmergencyInfoVO(), ProdEmergencyResponseDO.class); + prodEmergencyResponse.setProdId(prod.getProdId()); + prodEmergencyResponseMapper.insert(prodEmergencyResponse); + if (prodServiceVO.getProdEmergencyInfoVO().getProdEmergencyResponseIntervalsList() != null) { + for (ProdEmergencyResponseIntervalsDO prodEmergencyResponseIntervals : prodServiceVO.getProdEmergencyInfoVO().getProdEmergencyResponseIntervalsList()) { + prodEmergencyResponseIntervals.setId(null); + prodEmergencyResponseIntervals.setConfigId(prodEmergencyResponse.getId()); + prodEmergencyResponseIntervals.setProdId(prod.getProdId()); + prodEmergencyResponseIntervalsMapper.insert(prodEmergencyResponseIntervals); + } + } + if (prodServiceVO.getProdEmergencyInfoVO().getProdEmergencyResponseBlackList() != null) { + for (ProdAdditionalFeeBlackVO prodAdditionalFeeBlackVO : prodServiceVO.getProdEmergencyInfoVO().getProdEmergencyResponseBlackList()) { + ProdAdditionalFeeDatesDO prodAdditionalFeeDatesDO = BeanUtils.toBean(prodAdditionalFeeBlackVO, ProdAdditionalFeeDatesDO.class); + prodAdditionalFeeDatesDO.setId(null); + prodAdditionalFeeDatesDO.setProdId(prod.getProdId()); + prodAdditionalFeeDatesDO.setType(3); + prodAdditionalFeeDatesMapper.insert(prodAdditionalFeeDatesDO); + } + } + } else { + ProdEmergencyResponseDO prodEmergencyResponse = BeanUtils.toBean(prodServiceVO.getProdEmergencyInfoVO(), ProdEmergencyResponseDO.class); + if (prodEmergencyResponse != null) { + prodEmergencyResponseMapper.updateById(prodEmergencyResponse); + prodEmergencyResponseIntervalsMapper.deleteIntervals(prodEmergencyResponse.getId()); + if (prodServiceVO.getProdEmergencyInfoVO().getProdEmergencyResponseIntervalsList() != null) { + for (ProdEmergencyResponseIntervalsDO prodEmergencyResponseIntervals : prodServiceVO.getProdEmergencyInfoVO().getProdEmergencyResponseIntervalsList()) { + prodEmergencyResponseIntervals.setId(null); + prodEmergencyResponseIntervals.setConfigId(prodEmergencyResponse.getId()); + prodEmergencyResponseIntervals.setProdId(prod.getProdId()); + prodEmergencyResponseIntervalsMapper.insert(prodEmergencyResponseIntervals); + } + } + //先删除在新增 + prodAdditionalFeeDatesMapper.deleteAdditionalFeeDates(prod.getProdId(), 3); + if (prodServiceVO.getProdReservationConfig().getProdReservationBlackList() != null) { + for (ProdAdditionalFeeBlackVO prodAdditionalFeeBlackVO : prodServiceVO.getProdReservationConfig().getProdReservationBlackList()) { + ProdAdditionalFeeDatesDO prodAdditionalFeeDatesDO = BeanUtils.toBean(prodAdditionalFeeBlackVO, ProdAdditionalFeeDatesDO.class); + prodAdditionalFeeDatesDO.setId(null); + prodAdditionalFeeDatesDO.setProdId(prod.getProdId()); + prodAdditionalFeeDatesDO.setType(3); + prodAdditionalFeeDatesMapper.insert(prodAdditionalFeeDatesDO); + } + } + } + } + } + //接单上线设置 + if (Objects.equals(prodServiceVO.getOrderLimitSwitch(), BaseEnum.YES_ONE.getKey())) { + ProductOrderLimitDO orderLimit = productOrderLimitMapper.selectOne(new LambdaQueryWrapper() + .eq(ProductOrderLimitDO::getProdId, prodServiceVO.getProdId())); + if (orderLimit == null) { + prod.setOrderLimitSwitch(BaseEnum.YES_ONE.getKey()); + ProductOrderLimitDO productOrderLimit = prodServiceVO.getProductOrderLimitVO(); + productOrderLimit.setProdId(prod.getProdId()); + productOrderLimitMapper.insert(productOrderLimit); + } else { + productOrderLimitMapper.updateById(prodServiceVO.getProductOrderLimitVO()); + } + } + //特殊时段设置 + if (Objects.equals(prodServiceVO.getAdditionalSwitch(), BaseEnum.YES_ONE.getKey())) { + if (prodServiceVO.getProdAdditionalFeeDatesList() != null) + for (ProdAdditionalFeeDatesDO prodAdditionalFeeDates : prodServiceVO.getProdAdditionalFeeDatesList()) { + if (prodAdditionalFeeDates.getId() == null) { + prodAdditionalFeeDates.setProdId(prodServiceVO.getProdId()); + prodAdditionalFeeDates.setType(1); + prodAdditionalFeeDatesMapper.insert(prodAdditionalFeeDates); + } else { + prodAdditionalFeeDatesMapper.updateById(prodAdditionalFeeDates); + } + } + } + //特殊日期设置 + if (Objects.equals(prodServiceVO.getAdditionalFeeSwitch(), BaseEnum.YES_ONE.getKey())) { + for (ProdAdditionalFeePeriodsDO prodAdditionalFeePeriods : prodServiceVO.getProdAdditionalFeePeriodsList()) { + if (prodAdditionalFeePeriods.getId() == null) { + prodAdditionalFeePeriods.setProdId(prodServiceVO.getProdId()); + prodAdditionalFeePeriodsMapper.insert(prodAdditionalFeePeriods); + } else { + prodAdditionalFeePeriodsMapper.updateById(prodAdditionalFeePeriods); + } + } + } + //体重设置 + if (Objects.equals(prodServiceVO.getWeightSwitch(), BaseEnum.YES_ONE.getKey())) { + prod.setWeightSwitch(prodServiceVO.getWeightSwitch()); + ProdExtendDO prodExtendDO = prodExtendMapper.selectOne(ProdExtendDO::getProdId, prod.getProdId()); + if (prodExtendDO != null) { + prodExtendDO.setIsWeightCharge(prodServiceVO.getProdWeightConfig().getIsWeightCharge()); + prodExtendMapper.updateById(prodExtendDO); + } else { + prodExtendDO = new ProdExtendDO(); + prodExtendDO.setProdId(prod.getProdId()); + prodExtendDO.setIsWeightCharge(prodServiceVO.getProdWeightConfig().getIsWeightCharge()); + prodExtendMapper.insert(prodExtendDO); + } + prodWeightRangePricesMapper.deleteWeightRangePrices(prod.getProdId()); + if (prodServiceVO.getProdWeightConfig().getProdWeightConfigList() != null) { + for (ProdWeightRangePricesDO prodWeightRangePricesDO : prodServiceVO.getProdWeightConfig().getProdWeightConfigList()) { + prodWeightRangePricesDO.setProdId(prod.getProdId()); + prodWeightRangePricesMapper.insert(prodWeightRangePricesDO); + } + } + } + prodMapper.updateById(prod); + } + + @Override + public ProdServiceVO getProdService(Long prodId) { + ProdDO prodDO = prodMapper.selectById(prodId); + ProdServiceVO prodServiceVO = prodMapper.selectProdService(prodDO.getProdId()); + + if (prodServiceVO.getProdEmergencyInfoVO().getId()== null) { + prodServiceVO.setProdEmergencyInfoVO(null); + }else { + if (prodServiceVO.getProdEmergencyInfoVO().getId()!= null&&prodServiceVO.getProdEmergencyInfoVO().getProdEmergencyResponseIntervalsList().size()== 0) { + ProdEmergencyInfoVO prodEmergencyInfoVO = prodServiceVO.getProdEmergencyInfoVO(); + prodEmergencyInfoVO.setProdEmergencyResponseIntervalsList(new ArrayList<>()); + } + if (prodServiceVO.getProdEmergencyInfoVO().getId()!= null&&prodServiceVO.getProdEmergencyInfoVO().getProdEmergencyResponseBlackList().get(0).isEmpty()) { + ProdEmergencyInfoVO prodEmergencyInfoVO = prodServiceVO.getProdEmergencyInfoVO(); + prodEmergencyInfoVO.setProdEmergencyResponseBlackList(new ArrayList<>()); + } + } + if (prodServiceVO.getProdAdditionalFeeDatesList().get(0).getId()==null) { + prodServiceVO.setProdAdditionalFeeDatesList(new ArrayList<>()); + } + if (prodServiceVO.getProdAdditionalFeePeriodsList().get(0).getId()==null) { + prodServiceVO.setProdAdditionalFeePeriodsList(new ArrayList<>()); + } + return prodServiceVO; + /* if (prodDO != null && prodDO.getRegionSwitch() == BaseEnum.YES_ONE.getKey()) { + ProdServiceVO prodServiceVO = prodMapper.selectProdService(prodDO.getProdId()); + if (Objects.equals(prodDO.getReservationSwitch(), BaseEnum.YES_ONE.getKey())&&prodServiceVO.getProdReservationConfig().getProdReservationBlackList().get(0).isEmpty()) { + ProdReservationInfoVO prodReservationInfoVO = prodServiceVO.getProdReservationConfig(); + prodReservationInfoVO.setProdReservationBlackList(new ArrayList<>()); + } + if (Objects.equals(prodDO.getEmergencySwitch(), BaseEnum.YES_ONE.getKey())&&prodServiceVO.getProdEmergencyInfoVO().getProdEmergencyResponseIntervalsList().get(0).isEmpty()) { + ProdEmergencyInfoVO prodEmergencyInfoVO = prodServiceVO.getProdEmergencyInfoVO(); + prodEmergencyInfoVO.setProdEmergencyResponseIntervalsList(new ArrayList<>()); + } + if (Objects.equals(prodDO.getAdditionalSwitch(), BaseEnum.YES_ONE.getKey())&&prodServiceVO.getProdAdditionalFeeDatesList().get(0).isEmpty()) { + prodServiceVO.setProdAdditionalFeeDatesList(new ArrayList<>()); + } + if (Objects.equals(prodDO.getAdditionalFeeSwitch(), BaseEnum.YES_ONE.getKey())&&prodServiceVO.getProdAdditionalFeePeriodsList().get(0).isEmpty()) { + prodServiceVO.setProdAdditionalFeePeriodsList(new ArrayList<>()); + } + return prodServiceVO; + } else { + ProdServiceVO prodServiceVO = prodMapper.selectProdServiceInfo(prodDO.getProdId()); + if (Objects.equals(prodDO.getReservationSwitch(), BaseEnum.YES_ONE.getKey())&&prodServiceVO.getProdReservationConfig()!=null&&prodServiceVO.getProdReservationConfig().getProdReservationBlackList().get(0).isEmpty()) { + ProdReservationInfoVO prodReservationInfoVO = prodServiceVO.getProdReservationConfig(); + prodReservationInfoVO.setProdReservationBlackList(new ArrayList<>()); + } + if (Objects.equals(prodDO.getEmergencySwitch(), BaseEnum.YES_ONE.getKey())&&prodServiceVO.getProdEmergencyInfoVO()!=null&&prodServiceVO.getProdEmergencyInfoVO().getProdEmergencyResponseIntervalsList().get(0).isEmpty()) { + ProdEmergencyInfoVO prodEmergencyInfoVO = prodServiceVO.getProdEmergencyInfoVO(); + prodEmergencyInfoVO.setProdEmergencyResponseIntervalsList(new ArrayList<>()); + } + if (Objects.equals(prodDO.getEmergencySwitch(), BaseEnum.YES_ONE.getKey())&&prodServiceVO.getProdEmergencyInfoVO()!=null&&prodServiceVO.getProdEmergencyInfoVO().getProdEmergencyResponseBlackList().get(0).isEmpty()) { + ProdEmergencyInfoVO prodEmergencyInfoVO = prodServiceVO.getProdEmergencyInfoVO(); + prodEmergencyInfoVO.setProdEmergencyResponseBlackList(new ArrayList<>()); + } + if (Objects.equals(prodDO.getAdditionalSwitch(), BaseEnum.YES_ONE.getKey())&&prodServiceVO.getProdAdditionalFeeDatesList()!=null&&prodServiceVO.getProdAdditionalFeeDatesList().get(0).isEmpty()) { + prodServiceVO.setProdAdditionalFeeDatesList(new ArrayList<>()); + } + if (Objects.equals(prodDO.getAdditionalFeeSwitch(), BaseEnum.YES_ONE.getKey())&&prodServiceVO.getProdAdditionalFeePeriodsList()!=null&&prodServiceVO.getProdAdditionalFeePeriodsList().get(0).isEmpty()) { + prodServiceVO.setProdAdditionalFeePeriodsList(new ArrayList<>()); + } + return prodServiceVO; + }*/ + + + } + + @Override + public void updateProd(ProdSaveReqVO updateReqVO) { + // 校验存在 + validateProdExists(updateReqVO.getProdId()); + // 更新 + ProdDO updateObj = BeanUtils.toBean(updateReqVO, ProdDO.class); + prodMapper.updateById(updateObj); + } + + @Override + public void deleteProd(Long id) { + // 校验存在 + validateProdExists(id); + ProdDO prod = new ProdDO(); + prod.setProdId(id); + prod.setDeleted(BaseEnum.YES_ONE.getKey()); + prod.setDeleteTime(new Date()); + // 删除 + prodMapper.deleteById(prod); + } + + private void validateProdExists(Long id) { + if (prodMapper.selectById(id) == null) { + //throw exception(100, "商品不存在"); + } + } + + @Override + public ProdDO getProd(Long id) { + return prodMapper.selectById(id); + } + + @Override + public PageResult getProdPage(ProdPageReqVO pageReqVO) { + IPage prodPageList = prodMapper.getProdPageList(MyBatisUtils.buildPage(pageReqVO), + pageReqVO.getCreateTime(), pageReqVO.getProdName(), pageReqVO.getShopId(), pageReqVO.getStatus(), pageReqVO.getCategoryId()); + return new PageResult<>(prodPageList.getRecords(), prodPageList.getTotal()); + } + + @Override + public PageResult getProdRecycleBinPageList(ProdRecycleBinVO prodRecycleBinVO) { + IPage prodPageList = prodMapper.getProdRecycleBinPageList(MyBatisUtils.buildPage(prodRecycleBinVO), prodRecycleBinVO.getDeleteTime(), prodRecycleBinVO.getProdName()); + for (ProdRestoreListVO prodPage : prodPageList.getRecords()) { + if (prodPage.getDeleteTime() != null) { + prodPage.setRemainingDays(DateUtils.getRemainingDays(prodPage.getDeleteTime())); + } + } + return new PageResult<>(prodPageList.getRecords(), prodPageList.getTotal()); + } + + @Override + public void restoreProdList(List ids) { + prodMapper.batchRestoreProd(ids); + } + +} \ No newline at end of file diff --git a/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/service/impl/ProdServiceOverAreaRulesServiceImpl.java b/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/service/impl/ProdServiceOverAreaRulesServiceImpl.java new file mode 100644 index 0000000..6f5e253 --- /dev/null +++ b/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/service/impl/ProdServiceOverAreaRulesServiceImpl.java @@ -0,0 +1,76 @@ +package com.tashow.cloud.product.service.impl; + +import com.tashow.cloud.productapi.api.product.dto.ProdServiceOverAreaRulesDO; +import com.tashow.cloud.product.mapper.ProdServiceOverAreaRulesMapper; +import com.tashow.cloud.product.service.ProdServiceOverAreaRulesService; +import com.tashow.cloud.productapi.api.product.vo.prodserviceoverarearules.ProdServiceOverAreaRulesPageReqVO; +import com.tashow.cloud.productapi.api.product.vo.prodserviceoverarearules.ProdServiceOverAreaRulesSaveReqVO; +import com.tashow.cloud.productapi.enums.ErrorCodeConstants; +import org.springframework.stereotype.Service; +import jakarta.annotation.Resource; +import org.springframework.validation.annotation.Validated; +import org.springframework.transaction.annotation.Transactional; + +import java.util.*; +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.common.pojo.PageParam; +import com.tashow.cloud.common.util.object.BeanUtils; + + +import static com.tashow.cloud.common.exception.util.ServiceExceptionUtil.exception; + +/** + * 超区规则 Service 实现类 + * + * @author 芋道源码 + */ +@Service +@Validated +public class ProdServiceOverAreaRulesServiceImpl implements ProdServiceOverAreaRulesService { + + @Resource + private ProdServiceOverAreaRulesMapper prodServiceOverAreaRulesMapper; + + @Override + public Long createProdServiceOverAreaRules(ProdServiceOverAreaRulesSaveReqVO createReqVO) { + // 插入 + ProdServiceOverAreaRulesDO prodServiceOverAreaRules = BeanUtils.toBean(createReqVO, ProdServiceOverAreaRulesDO.class); + prodServiceOverAreaRulesMapper.insert(prodServiceOverAreaRules); + // 返回 + return prodServiceOverAreaRules.getId(); + } + + @Override + public void updateProdServiceOverAreaRules(ProdServiceOverAreaRulesSaveReqVO updateReqVO) { + // 校验存在 + validateProdServiceOverAreaRulesExists(updateReqVO.getId()); + // 更新 + ProdServiceOverAreaRulesDO updateObj = BeanUtils.toBean(updateReqVO, ProdServiceOverAreaRulesDO.class); + prodServiceOverAreaRulesMapper.updateById(updateObj); + } + + @Override + public void deleteProdServiceOverAreaRules(Long id) { + // 校验存在 + validateProdServiceOverAreaRulesExists(id); + // 删除 + prodServiceOverAreaRulesMapper.deleteById(id); + } + + private void validateProdServiceOverAreaRulesExists(Long id) { + if (prodServiceOverAreaRulesMapper.selectById(id) == null) { + throw exception(ErrorCodeConstants.PROD_SERVICE_OVER_AREA_RULES_NOT_EXISTS); + } + } + + @Override + public ProdServiceOverAreaRulesDO getProdServiceOverAreaRules(Long id) { + return prodServiceOverAreaRulesMapper.selectById(id); + } + + @Override + public PageResult getProdServiceOverAreaRulesPage(ProdServiceOverAreaRulesPageReqVO pageReqVO) { + return null; + } + +} \ No newline at end of file diff --git a/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/service/impl/ProdTagsServiceImpl.java b/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/service/impl/ProdTagsServiceImpl.java new file mode 100644 index 0000000..ba4003b --- /dev/null +++ b/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/service/impl/ProdTagsServiceImpl.java @@ -0,0 +1,73 @@ +package com.tashow.cloud.product.service.impl; + +import com.tashow.cloud.productapi.api.product.dto.ProdTagsDO; +import com.tashow.cloud.product.mapper.ProdTagsMapper; +import com.tashow.cloud.product.service.ProdTagsService; +import com.tashow.cloud.productapi.api.product.vo.prodtags.ProdTagsPageReqVO; +import com.tashow.cloud.productapi.api.product.vo.prodtags.ProdTagsSaveReqVO; +import com.tashow.cloud.productapi.enums.ErrorCodeConstants; +import org.springframework.stereotype.Service; +import jakarta.annotation.Resource; +import org.springframework.validation.annotation.Validated; +import org.springframework.transaction.annotation.Transactional; +import java.util.*; +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.common.pojo.PageParam; +import com.tashow.cloud.common.util.object.BeanUtils; +import static com.tashow.cloud.common.exception.util.ServiceExceptionUtil.exception; + +/** + * 商品和标签管理 Service 实现类 + * + * @author 芋道源码 + */ +@Service +@Validated +public class ProdTagsServiceImpl implements ProdTagsService { + + @Resource + private ProdTagsMapper prodTagsMapper; + + @Override + public Long createProdTags(ProdTagsSaveReqVO createReqVO) { + // 插入 + ProdTagsDO prodTags = BeanUtils.toBean(createReqVO, ProdTagsDO.class); + prodTagsMapper.insert(prodTags); + // 返回 + return prodTags.getProductId(); + } + + @Override + public void updateProdTags(ProdTagsSaveReqVO updateReqVO) { + // 校验存在 + validateProdTagsExists(updateReqVO.getProductId()); + // 更新 + ProdTagsDO updateObj = BeanUtils.toBean(updateReqVO, ProdTagsDO.class); + prodTagsMapper.updateById(updateObj); + } + + @Override + public void deleteProdTags(Long id) { + // 校验存在 + validateProdTagsExists(id); + // 删除 + prodTagsMapper.deleteById(id); + } + + private void validateProdTagsExists(Long id) { + if (prodTagsMapper.selectById(id) == null) { + throw exception(ErrorCodeConstants.PROD_TAGS_NOT_EXISTS); + } + } + + @Override + public ProdTagsDO getProdTags(Long id) { + return prodTagsMapper.selectById(id); + } + + @Override + public PageResult getProdTagsPage(ProdTagsPageReqVO pageReqVO) { + return null; + } + +} \ No newline at end of file diff --git a/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/service/impl/ProdWeightRangePricesServiceImpl.java b/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/service/impl/ProdWeightRangePricesServiceImpl.java new file mode 100644 index 0000000..b28f9a0 --- /dev/null +++ b/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/service/impl/ProdWeightRangePricesServiceImpl.java @@ -0,0 +1,76 @@ +package com.tashow.cloud.product.service.impl; + +import com.tashow.cloud.productapi.api.product.dto.ProdWeightRangePricesDO; +import com.tashow.cloud.product.mapper.ProdWeightRangePricesMapper; +import com.tashow.cloud.product.service.ProdWeightRangePricesService; +import com.tashow.cloud.productapi.api.product.vo.prodweightrangeprices.ProdWeightRangePricesPageReqVO; +import com.tashow.cloud.productapi.api.product.vo.prodweightrangeprices.ProdWeightRangePricesSaveReqVO; +import com.tashow.cloud.productapi.enums.ErrorCodeConstants; +import org.springframework.stereotype.Service; +import jakarta.annotation.Resource; +import org.springframework.validation.annotation.Validated; +import org.springframework.transaction.annotation.Transactional; + +import java.util.*; +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.common.pojo.PageParam; +import com.tashow.cloud.common.util.object.BeanUtils; + + +import static com.tashow.cloud.common.exception.util.ServiceExceptionUtil.exception; + +/** + * 体重区间价格 Service 实现类 + * + * @author 芋道源码 + */ +@Service +@Validated +public class ProdWeightRangePricesServiceImpl implements ProdWeightRangePricesService { + + @Resource + private ProdWeightRangePricesMapper prodWeightRangePricesMapper; + + @Override + public Long createProdWeightRangePrices(ProdWeightRangePricesSaveReqVO createReqVO) { + // 插入 + ProdWeightRangePricesDO prodWeightRangePrices = BeanUtils.toBean(createReqVO, ProdWeightRangePricesDO.class); + prodWeightRangePricesMapper.insert(prodWeightRangePrices); + // 返回 + return prodWeightRangePrices.getId(); + } + + @Override + public void updateProdWeightRangePrices(ProdWeightRangePricesSaveReqVO updateReqVO) { + // 校验存在 + validateProdWeightRangePricesExists(updateReqVO.getId()); + // 更新 + ProdWeightRangePricesDO updateObj = BeanUtils.toBean(updateReqVO, ProdWeightRangePricesDO.class); + prodWeightRangePricesMapper.updateById(updateObj); + } + + @Override + public void deleteProdWeightRangePrices(Long id) { + // 校验存在 + validateProdWeightRangePricesExists(id); + // 删除 + prodWeightRangePricesMapper.deleteById(id); + } + + private void validateProdWeightRangePricesExists(Long id) { + if (prodWeightRangePricesMapper.selectById(id) == null) { + throw exception(ErrorCodeConstants.PROD_WEIGHT_RANGE_PRICES_NOT_EXISTS); + } + } + + @Override + public ProdWeightRangePricesDO getProdWeightRangePrices(Long id) { + return prodWeightRangePricesMapper.selectById(id); + } + + @Override + public PageResult getProdWeightRangePricesPage(ProdWeightRangePricesPageReqVO pageReqVO) { + return null; + } + +} \ No newline at end of file diff --git a/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/service/impl/ProductOrderLimitServiceImpl.java b/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/service/impl/ProductOrderLimitServiceImpl.java new file mode 100644 index 0000000..abae5d3 --- /dev/null +++ b/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/service/impl/ProductOrderLimitServiceImpl.java @@ -0,0 +1,74 @@ +package com.tashow.cloud.product.service.impl; + +import com.tashow.cloud.productapi.api.product.dto.ProductOrderLimitDO; +import com.tashow.cloud.product.mapper.ProductOrderLimitMapper; +import com.tashow.cloud.product.service.ProductOrderLimitService; +import com.tashow.cloud.productapi.api.product.vo.productorderlimit.ProductOrderLimitPageReqVO; +import com.tashow.cloud.productapi.api.product.vo.productorderlimit.ProductOrderLimitSaveReqVO; +import com.tashow.cloud.productapi.enums.ErrorCodeConstants; +import org.springframework.stereotype.Service; +import jakarta.annotation.Resource; +import org.springframework.validation.annotation.Validated; +import org.springframework.transaction.annotation.Transactional; + +import java.util.*; +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.common.pojo.PageParam; +import com.tashow.cloud.common.util.object.BeanUtils; +import static com.tashow.cloud.common.exception.util.ServiceExceptionUtil.exception; + +/** + * 商品接单上限设置 Service 实现类 + * + * @author 芋道源码 + */ +@Service +@Validated +public class ProductOrderLimitServiceImpl implements ProductOrderLimitService { + + @Resource + private ProductOrderLimitMapper productOrderLimitMapper; + + @Override + public Long createProductOrderLimit(ProductOrderLimitSaveReqVO createReqVO) { + // 插入 + ProductOrderLimitDO productOrderLimit = BeanUtils.toBean(createReqVO, ProductOrderLimitDO.class); + productOrderLimitMapper.insert(productOrderLimit); + // 返回 + return productOrderLimit.getId(); + } + + @Override + public void updateProductOrderLimit(ProductOrderLimitSaveReqVO updateReqVO) { + // 校验存在 + validateProductOrderLimitExists(updateReqVO.getId()); + // 更新 + ProductOrderLimitDO updateObj = BeanUtils.toBean(updateReqVO, ProductOrderLimitDO.class); + productOrderLimitMapper.updateById(updateObj); + } + + @Override + public void deleteProductOrderLimit(Long id) { + // 校验存在 + validateProductOrderLimitExists(id); + // 删除 + productOrderLimitMapper.deleteById(id); + } + + private void validateProductOrderLimitExists(Long id) { + if (productOrderLimitMapper.selectById(id) == null) { + throw exception(ErrorCodeConstants.PRODUCT_ORDER_LIMIT_NOT_EXISTS); + } + } + + @Override + public ProductOrderLimitDO getProductOrderLimit(Long id) { + return productOrderLimitMapper.selectById(id); + } + + @Override + public PageResult getProductOrderLimitPage(ProductOrderLimitPageReqVO pageReqVO) { + return null; + } + +} \ No newline at end of file diff --git a/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/service/impl/ShopDetailServiceImpl.java b/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/service/impl/ShopDetailServiceImpl.java new file mode 100644 index 0000000..b7bdb09 --- /dev/null +++ b/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/service/impl/ShopDetailServiceImpl.java @@ -0,0 +1,78 @@ +package com.tashow.cloud.product.service.impl; + +import com.tashow.cloud.productapi.api.product.dto.ShopDetailDO; +import com.tashow.cloud.product.mapper.ShopDetailMapper; +import com.tashow.cloud.product.service.ShopDetailService; +import com.tashow.cloud.productapi.api.product.vo.shopdetail +.ShopDetailPageReqVO; +import com.tashow.cloud.productapi.api.product.vo.shopdetail +.ShopDetailSaveReqVO; +import com.tashow.cloud.productapi.enums.ErrorCodeConstants; +import org.springframework.stereotype.Service; +import jakarta.annotation.Resource; +import org.springframework.validation.annotation.Validated; +import org.springframework.transaction.annotation.Transactional; + +import java.util.*; +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.common.pojo.PageParam; +import com.tashow.cloud.common.util.object.BeanUtils; + + +import static com.tashow.cloud.common.exception.util.ServiceExceptionUtil.exception; + +/** + * 店铺信息 Service 实现类 + * + * @author 芋道源码 + */ +@Service +@Validated +public class ShopDetailServiceImpl implements ShopDetailService { + + @Resource + private ShopDetailMapper shopDetailMapper; + + @Override + public Long createShopDetail(ShopDetailSaveReqVO createReqVO) { + // 插入 + ShopDetailDO shopDetail = BeanUtils.toBean(createReqVO, ShopDetailDO.class); + shopDetailMapper.insert(shopDetail); + // 返回 + return shopDetail.getShopId(); + } + + @Override + public void updateShopDetail(ShopDetailSaveReqVO updateReqVO) { + // 校验存在 + validateShopDetailExists(updateReqVO.getShopId()); + // 更新 + ShopDetailDO updateObj = BeanUtils.toBean(updateReqVO, ShopDetailDO.class); + shopDetailMapper.updateById(updateObj); + } + + @Override + public void deleteShopDetail(Long id) { + // 校验存在 + validateShopDetailExists(id); + // 删除 + shopDetailMapper.deleteById(id); + } + + private void validateShopDetailExists(Long id) { + if (shopDetailMapper.selectById(id) == null) { + throw exception(ErrorCodeConstants.SHOP_DETAIL_NOT_EXISTS); + } + } + + @Override + public ShopDetailDO getShopDetail(Long id) { + return shopDetailMapper.selectById(id); + } + + @Override + public PageResult getShopDetailPage(ShopDetailPageReqVO pageReqVO) { + return null; + } + +} \ No newline at end of file diff --git a/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/service/impl/SkuServiceDeliverServiceImpl.java b/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/service/impl/SkuServiceDeliverServiceImpl.java new file mode 100644 index 0000000..374965b --- /dev/null +++ b/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/service/impl/SkuServiceDeliverServiceImpl.java @@ -0,0 +1,76 @@ +package com.tashow.cloud.product.service.impl; + +import com.tashow.cloud.productapi.api.product.dto.SkuServiceDeliverDO; +import com.tashow.cloud.product.mapper.SkuServiceDeliverMapper; +import com.tashow.cloud.product.service.SkuServiceDeliverService; +import com.tashow.cloud.productapi.api.product.vo.skuservicedeliver.SkuServiceDeliverPageReqVO; +import com.tashow.cloud.productapi.api.product.vo.skuservicedeliver.SkuServiceDeliverSaveReqVO; +import com.tashow.cloud.productapi.enums.ErrorCodeConstants; +import org.springframework.stereotype.Service; +import jakarta.annotation.Resource; +import org.springframework.validation.annotation.Validated; +import org.springframework.transaction.annotation.Transactional; + +import java.util.*; +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.common.pojo.PageParam; +import com.tashow.cloud.common.util.object.BeanUtils; + + +import static com.tashow.cloud.common.exception.util.ServiceExceptionUtil.exception; + +/** + * 服务交付方式 Service 实现类 + * + * @author 芋道源码 + */ +@Service +@Validated +public class SkuServiceDeliverServiceImpl implements SkuServiceDeliverService { + + @Resource + private SkuServiceDeliverMapper skuServiceDeliverMapper; + + @Override + public Long createSkuServiceDeliver(SkuServiceDeliverSaveReqVO createReqVO) { + // 插入 + SkuServiceDeliverDO skuServiceDeliver = BeanUtils.toBean(createReqVO, SkuServiceDeliverDO.class); + skuServiceDeliverMapper.insert(skuServiceDeliver); + // 返回 + return skuServiceDeliver.getId(); + } + + @Override + public void updateSkuServiceDeliver(SkuServiceDeliverSaveReqVO updateReqVO) { + // 校验存在 + validateSkuServiceDeliverExists(updateReqVO.getId()); + // 更新 + SkuServiceDeliverDO updateObj = BeanUtils.toBean(updateReqVO, SkuServiceDeliverDO.class); + skuServiceDeliverMapper.updateById(updateObj); + } + + @Override + public void deleteSkuServiceDeliver(Long id) { + // 校验存在 + validateSkuServiceDeliverExists(id); + // 删除 + skuServiceDeliverMapper.deleteById(id); + } + + private void validateSkuServiceDeliverExists(Long id) { + if (skuServiceDeliverMapper.selectById(id) == null) { + throw exception(ErrorCodeConstants.SKU_SERVICE_DELIVER_NOT_EXISTS); + } + } + + @Override + public SkuServiceDeliverDO getSkuServiceDeliver(Long id) { + return skuServiceDeliverMapper.selectById(id); + } + + @Override + public PageResult getSkuServiceDeliverPage(SkuServiceDeliverPageReqVO pageReqVO) { + return null; + } + +} \ No newline at end of file diff --git a/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/service/impl/SkuServiceDetailsServiceImpl.java b/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/service/impl/SkuServiceDetailsServiceImpl.java new file mode 100644 index 0000000..bc7d53d --- /dev/null +++ b/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/service/impl/SkuServiceDetailsServiceImpl.java @@ -0,0 +1,74 @@ +package com.tashow.cloud.product.service.impl; + +import com.tashow.cloud.productapi.api.product.dto.SkuServiceDetailsDO; +import com.tashow.cloud.product.mapper.SkuServiceDetailsMapper; +import com.tashow.cloud.product.service.SkuServiceDetailsService; +import com.tashow.cloud.productapi.api.product.vo.skuservicedetails.SkuServiceDetailsPageReqVO; +import com.tashow.cloud.productapi.api.product.vo.skuservicedetails.SkuServiceDetailsSaveReqVO; +import com.tashow.cloud.productapi.enums.ErrorCodeConstants; +import org.springframework.stereotype.Service; +import jakarta.annotation.Resource; +import org.springframework.validation.annotation.Validated; +import org.springframework.transaction.annotation.Transactional; + +import java.util.*; +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.common.pojo.PageParam; +import com.tashow.cloud.common.util.object.BeanUtils; +import static com.tashow.cloud.common.exception.util.ServiceExceptionUtil.exception; + +/** + * 服务详情 Service 实现类 + * + * @author 芋道源码 + */ +@Service +@Validated +public class SkuServiceDetailsServiceImpl implements SkuServiceDetailsService { + + @Resource + private SkuServiceDetailsMapper skuServiceDetailsMapper; + + @Override + public Long createSkuServiceDetails(SkuServiceDetailsSaveReqVO createReqVO) { + // 插入 + SkuServiceDetailsDO skuServiceDetails = BeanUtils.toBean(createReqVO, SkuServiceDetailsDO.class); + skuServiceDetailsMapper.insert(skuServiceDetails); + // 返回 + return skuServiceDetails.getId(); + } + + @Override + public void updateSkuServiceDetails(SkuServiceDetailsSaveReqVO updateReqVO) { + // 校验存在 + validateSkuServiceDetailsExists(updateReqVO.getId()); + // 更新 + SkuServiceDetailsDO updateObj = BeanUtils.toBean(updateReqVO, SkuServiceDetailsDO.class); + skuServiceDetailsMapper.updateById(updateObj); + } + + @Override + public void deleteSkuServiceDetails(Long id) { + // 校验存在 + validateSkuServiceDetailsExists(id); + // 删除 + skuServiceDetailsMapper.deleteById(id); + } + + private void validateSkuServiceDetailsExists(Long id) { + if (skuServiceDetailsMapper.selectById(id) == null) { + throw exception(ErrorCodeConstants.SKU_SERVICE_DETAILS_NOT_EXISTS); + } + } + + @Override + public SkuServiceDetailsDO getSkuServiceDetails(Long id) { + return skuServiceDetailsMapper.selectById(id); + } + + @Override + public PageResult getSkuServiceDetailsPage(SkuServiceDetailsPageReqVO pageReqVO) { + return null; + } + +} \ No newline at end of file diff --git a/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/service/impl/SkuServiceImpl.java b/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/service/impl/SkuServiceImpl.java new file mode 100644 index 0000000..29dc99d --- /dev/null +++ b/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/service/impl/SkuServiceImpl.java @@ -0,0 +1,972 @@ +package com.tashow.cloud.product.service.impl; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.collection.CollectionUtil; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.tashow.cloud.common.util.date.DateUtils; +import com.tashow.cloud.mybatis.mybatis.core.util.MyBatisUtils; +import com.tashow.cloud.productapi.api.product.dto.*; +import com.tashow.cloud.product.mapper.*; +import com.tashow.cloud.product.service.ProdExtendService; +import com.tashow.cloud.product.service.ProdPropService; +import com.tashow.cloud.product.service.ProdPropValueService; +import com.tashow.cloud.product.service.SkuService; +import com.tashow.cloud.productapi.api.product.vo.prodpropvalue.ProPageReqVO; +import com.tashow.cloud.productapi.api.product.vo.prodpropvalue.ProPropRecycleBinVO; +import com.tashow.cloud.productapi.api.product.vo.sku.*; +import com.tashow.cloud.productapi.enums.BaseEnum; +import com.tashow.cloud.productapi.enums.ErrorCodeConstants; +import com.tashow.cloud.productapi.enums.ServiceTypeEnum; +import org.apache.commons.lang3.ObjectUtils; +import org.springframework.stereotype.Service; +import jakarta.annotation.Resource; +import org.springframework.validation.annotation.Validated; +import org.springframework.transaction.annotation.Transactional; + +import java.util.*; +import java.util.stream.Collectors; + +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.common.pojo.PageParam; +import com.tashow.cloud.common.util.object.BeanUtils; + + +import static com.tashow.cloud.common.exception.util.ServiceExceptionUtil.exception; + +/** + * 单品SKU Service 实现类 + * + * @author 芋道源码 + */ +@Service +@Validated +public class SkuServiceImpl implements SkuService { + + @Resource + private SkuMapper skuMapper; + @Resource + private SkuServiceDetailsMapper skuServiceDetailsMapper; + @Resource + private SkuServicesFormMapper skuServicesFormMapper; + @Resource + private SkuServiceMaterialMapper skuServiceMaterialMapper; + @Resource + private SkuServiceTransportMapper skuServiceTransportMapper; + @Resource + private SkuServiceDeliverMapper skuServiceDeliverMapper; + @Resource + private ProdPropService prodPropService; + @Resource + private ProdExtendMapper prodExtendMapper; + @Resource + private ProdPropValueService prodPropValueService; + @Resource + private ProdExtendService prodExtendService; + @Resource + private ProdPropValueMapper prodPropValueMapper; + @Resource + private ProdPropMapper prodPropMapper; + + @Override + public Long createSku(SkuSaveReqVO createReqVO) { + // 插入 + SkuDO sku = BeanUtils.toBean(createReqVO, SkuDO.class); + skuMapper.insert(sku); + // 返回 + return sku.getSkuId(); + } + + @Override + @Transactional + public void createSkuExtend(SkuExtendVO skuExtendVO) { + //接运车辆配置 + if (Objects.equals(skuExtendVO.getTransportCarSwitch(), BaseEnum.YES_ONE.getKey())) { + SkuServicesFormDO skuServicesFormDO = new SkuServicesFormDO(); + skuServicesFormDO.setServiceName(ServiceTypeEnum.TRANSPORT_CAR_CONFIG.getDescription()); + skuServicesFormDO.setIsEnabled(BaseEnum.YES_ONE.getKey()); + skuServicesFormDO.setType(ServiceTypeEnum.TRANSPORT_CAR_CONFIG.getCode()); + skuServicesFormDO.setName(skuExtendVO.getSkuFormName()); + skuServicesFormMapper.insert(skuServicesFormDO); + for (SkuServiceDetailsDO skuServiceDetailsDO : skuExtendVO.getTransportCarList()) { + skuServiceDetailsDO.setServiceId(skuServicesFormDO.getId()); + skuServiceDetailsMapper.insert(skuServiceDetailsDO); + } + SkuServicesFormDO skuServicesForm = new SkuServicesFormDO(); + skuServicesForm.setServiceName(ServiceTypeEnum.TRANSPORT_CAR_MATERIAL.getDescription()); + skuServicesForm.setIsEnabled(BaseEnum.YES_ONE.getKey()); + skuServicesForm.setType(ServiceTypeEnum.TRANSPORT_CAR_MATERIAL.getCode()); + skuServicesForm.setName(skuExtendVO.getSkuFormName()); + skuServicesFormMapper.insert(skuServicesForm); + for (SkuServiceMaterialDO skuServiceMaterialDO : skuExtendVO.getTransportCarMaterialList()) { + skuServiceMaterialDO.setServiceId(skuServicesForm.getId()); + skuServiceMaterialMapper.insert(skuServiceMaterialDO); + } + } + //遗体运输目的地配置 + if (Objects.equals(skuExtendVO.getTrafficSwitch(), BaseEnum.YES_ONE.getKey())) { + SkuServicesFormDO skuServicesFormDO = new SkuServicesFormDO(); + skuServicesFormDO.setServiceName(ServiceTypeEnum.BODY_TRANSPORT_CONFIG.getDescription()); + skuServicesFormDO.setIsEnabled(BaseEnum.YES_ONE.getKey()); + skuServicesFormDO.setType(ServiceTypeEnum.BODY_TRANSPORT_CONFIG.getCode()); + skuServicesFormDO.setName(skuExtendVO.getSkuFormName()); + skuServicesFormMapper.insert(skuServicesFormDO); + for (SkuServiceTransportDO skuServiceTransportDO : skuExtendVO.getTrafficList()) { + skuServiceTransportDO.setServiceId(skuServicesFormDO.getId()); + skuServiceTransportMapper.insert(skuServiceTransportDO); + } + SkuServicesFormDO skuServicesForm = new SkuServicesFormDO(); + skuServicesForm.setServiceName(ServiceTypeEnum.BODY_TRANSPORT_MATERIAL.getDescription()); + skuServicesForm.setIsEnabled(BaseEnum.YES_ONE.getKey()); + skuServicesForm.setType(ServiceTypeEnum.BODY_TRANSPORT_MATERIAL.getCode()); + skuServicesForm.setName(skuExtendVO.getSkuFormName()); + skuServicesFormMapper.insert(skuServicesForm); + for (SkuServiceMaterialDO skuServiceMaterialDO : skuExtendVO.getTrafficMaterialList()) { + skuServiceMaterialDO.setServiceId(skuServicesForm.getId()); + skuServiceMaterialMapper.insert(skuServiceMaterialDO); + } + } + //遗体清洁配置 + if (Objects.equals(skuExtendVO.getCleanSwitch(), BaseEnum.YES_ONE.getKey())) { + SkuServicesFormDO skuServicesFormDO = new SkuServicesFormDO(); + skuServicesFormDO.setServiceName(ServiceTypeEnum.BODY_CLEAN_CONFIG.getDescription()); + skuServicesFormDO.setIsEnabled(BaseEnum.YES_ONE.getKey()); + skuServicesFormDO.setType(ServiceTypeEnum.BODY_CLEAN_CONFIG.getCode()); + skuServicesFormDO.setName(skuExtendVO.getSkuFormName()); + skuServicesFormMapper.insert(skuServicesFormDO); + for (SkuServiceDetailsDO skuServiceDetails : skuExtendVO.getCleanList()) { + skuServiceDetails.setServiceId(skuServicesFormDO.getId()); + skuServiceDetailsMapper.insert(skuServiceDetails); + } + SkuServicesFormDO skuServicesForm = new SkuServicesFormDO(); + skuServicesForm.setServiceName(ServiceTypeEnum.BODY_CLEAN_MATERIAL.getDescription()); + skuServicesForm.setIsEnabled(BaseEnum.YES_ONE.getKey()); + skuServicesForm.setType(ServiceTypeEnum.BODY_CLEAN_MATERIAL.getCode()); + skuServicesForm.setName(skuExtendVO.getSkuFormName()); + skuServicesFormMapper.insert(skuServicesForm); + for (SkuServiceMaterialDO skuServiceMaterialDO : skuExtendVO.getCleanMaterialList()) { + skuServiceMaterialDO.setServiceId(skuServicesForm.getId()); + skuServiceMaterialMapper.insert(skuServiceMaterialDO); + } + } + //追思告别配置 + if (Objects.equals(skuExtendVO.getReflectionSwitch(), BaseEnum.YES_ONE.getKey())) { + SkuServicesFormDO skuServicesFormDO = new SkuServicesFormDO(); + skuServicesFormDO.setServiceName(ServiceTypeEnum.MEMORIAL_CONFIG.getDescription()); + skuServicesFormDO.setIsEnabled(BaseEnum.YES_ONE.getKey()); + skuServicesFormDO.setType(ServiceTypeEnum.MEMORIAL_CONFIG.getCode()); + skuServicesFormDO.setName(skuExtendVO.getSkuFormName()); + skuServicesFormMapper.insert(skuServicesFormDO); + for (SkuServiceDetailsDO skuServiceDetails : skuExtendVO.getReflectionList()) { + skuServiceDetails.setServiceId(skuServicesFormDO.getId()); + skuServiceDetailsMapper.insert(skuServiceDetails); + } + SkuServicesFormDO skuServicesForm = new SkuServicesFormDO(); + skuServicesForm.setServiceName(ServiceTypeEnum.MEMORIAL_MATERIAL.getDescription()); + skuServicesForm.setIsEnabled(BaseEnum.YES_ONE.getKey()); + skuServicesForm.setType(ServiceTypeEnum.MEMORIAL_MATERIAL.getCode()); + skuServicesForm.setName(skuExtendVO.getSkuFormName()); + skuServicesFormMapper.insert(skuServicesForm); + for (SkuServiceMaterialDO skuServiceMaterialDO : skuExtendVO.getReflectionMaterialList()) { + skuServiceMaterialDO.setServiceId(skuServicesForm.getId()); + skuServiceMaterialMapper.insert(skuServiceMaterialDO); + } + } + + //遗体火化配置 + if (Objects.equals(skuExtendVO.getCremationSwitch(), BaseEnum.YES_ONE.getKey())) { + SkuServicesFormDO skuServicesFormDO = new SkuServicesFormDO(); + skuServicesFormDO.setServiceName(ServiceTypeEnum.CREMATION_CONFIG.getDescription()); + skuServicesFormDO.setIsEnabled(BaseEnum.YES_ONE.getKey()); + skuServicesFormDO.setType(ServiceTypeEnum.CREMATION_CONFIG.getCode()); + skuServicesFormDO.setName(skuExtendVO.getSkuFormName()); + skuServicesFormMapper.insert(skuServicesFormDO); + for (SkuServiceDetailsDO skuServiceDetails : skuExtendVO.getCremationList()) { + skuServiceDetails.setServiceId(skuServicesFormDO.getId()); + skuServiceDetailsMapper.insert(skuServiceDetails); + } + SkuServicesFormDO skuServicesForm = new SkuServicesFormDO(); + skuServicesForm.setServiceName(ServiceTypeEnum.CREMATION_MATERIAL.getDescription()); + skuServicesForm.setIsEnabled(BaseEnum.YES_ONE.getKey()); + skuServicesForm.setType(ServiceTypeEnum.CREMATION_MATERIAL.getCode()); + skuServicesForm.setName(skuExtendVO.getSkuFormName()); + skuServicesFormMapper.insert(skuServicesForm); + for (SkuServiceMaterialDO skuServiceMaterialDO : skuExtendVO.getCremationMaterialList()) { + skuServiceMaterialDO.setServiceId(skuServicesForm.getId()); + skuServiceMaterialMapper.insert(skuServiceMaterialDO); + } + } + + //骨灰处理配置 + if (Objects.equals(skuExtendVO.getAshProcessingSwitch(), BaseEnum.YES_ONE.getKey())) { + SkuServicesFormDO skuServicesFormDO = new SkuServicesFormDO(); + skuServicesFormDO.setServiceName(ServiceTypeEnum.ASH_PROCESSING_CONFIG.getDescription()); + skuServicesFormDO.setIsEnabled(BaseEnum.YES_ONE.getKey()); + skuServicesFormDO.setType(ServiceTypeEnum.ASH_PROCESSING_CONFIG.getCode()); + skuServicesFormDO.setName(skuExtendVO.getSkuFormName()); + skuServicesFormMapper.insert(skuServicesFormDO); + for (SkuServiceDetailsDO skuServiceDetails : skuExtendVO.getAshProcessingList()) { + skuServiceDetails.setServiceId(skuServicesFormDO.getId()); + skuServiceDetailsMapper.insert(skuServiceDetails); + } + SkuServicesFormDO skuForm = new SkuServicesFormDO(); + skuForm.setServiceName(ServiceTypeEnum.ASH_PROCESSING_DELIVERY.getDescription()); + skuForm.setIsEnabled(BaseEnum.YES_ONE.getKey()); + skuForm.setType(ServiceTypeEnum.ASH_PROCESSING_DELIVERY.getCode()); + skuForm.setName(skuExtendVO.getSkuFormName()); + skuServicesFormMapper.insert(skuForm); + for (SkuServiceDeliverDO skuServiceDeliverDO : skuExtendVO.getAshProcessingDeliverList()) { + skuServiceDeliverDO.setServiceId(skuForm.getId()); + skuServiceDeliverMapper.insert(skuServiceDeliverDO); + } + SkuServicesFormDO skuServicesForm = new SkuServicesFormDO(); + skuServicesForm.setServiceName(ServiceTypeEnum.ASH_PROCESSING_MATERIAL.getDescription()); + skuServicesForm.setIsEnabled(BaseEnum.YES_ONE.getKey()); + skuServicesForm.setType(ServiceTypeEnum.ASH_PROCESSING_MATERIAL.getCode()); + skuServicesForm.setName(skuExtendVO.getSkuFormName()); + skuServicesFormMapper.insert(skuServicesForm); + for (SkuServiceMaterialDO skuServiceMaterialDO : skuExtendVO.getAshProcessingMaterialList()) { + skuServiceMaterialDO.setServiceId(skuServicesForm.getId()); + skuServiceMaterialMapper.insert(skuServiceMaterialDO); + } + } + + //骨灰装殓配置 + if (Objects.equals(skuExtendVO.getBoneashSwitch(), BaseEnum.YES_ONE.getKey())) { + SkuServicesFormDO skuServicesFormDO = new SkuServicesFormDO(); + skuServicesFormDO.setServiceName(ServiceTypeEnum.BONE_ASH_CONFIG.getDescription()); + skuServicesFormDO.setIsEnabled(BaseEnum.YES_ONE.getKey()); + skuServicesFormDO.setType(ServiceTypeEnum.BONE_ASH_CONFIG.getCode()); + skuServicesFormDO.setName(skuExtendVO.getSkuFormName()); + skuServicesFormMapper.insert(skuServicesFormDO); + for (SkuServiceDetailsDO skuServiceDetails : skuExtendVO.getBoneashList()) { + skuServiceDetails.setServiceId(skuServicesFormDO.getId()); + skuServiceDetailsMapper.insert(skuServiceDetails); + } + } + + //纪念品配置 + if (Objects.equals(skuExtendVO.getSouvenirSwitch(), BaseEnum.YES_ONE.getKey())) { + SkuServicesFormDO skuServicesFormDO = new SkuServicesFormDO(); + skuServicesFormDO.setServiceName(ServiceTypeEnum.SOUVENIR_CONFIG.getDescription()); + skuServicesFormDO.setIsEnabled(BaseEnum.YES_ONE.getKey()); + skuServicesFormDO.setType(ServiceTypeEnum.SOUVENIR_CONFIG.getCode()); + skuServicesFormDO.setName(skuExtendVO.getSkuFormName()); + skuServicesFormMapper.insert(skuServicesFormDO); + for (SkuServiceDetailsDO skuServiceDetails : skuExtendVO.getSouvenirList()) { + skuServiceDetails.setServiceId(skuServicesFormDO.getId()); + skuServiceDetailsMapper.insert(skuServiceDetails); + } + SkuServicesFormDO skuForm = new SkuServicesFormDO(); + skuForm.setServiceName(ServiceTypeEnum.SOUVENIR_DELIVERY.getDescription()); + skuForm.setIsEnabled(BaseEnum.YES_ONE.getKey()); + skuForm.setType(ServiceTypeEnum.SOUVENIR_DELIVERY.getCode()); + skuForm.setName(skuExtendVO.getSkuFormName()); + skuServicesFormMapper.insert(skuForm); + for (SkuServiceDeliverDO skuServiceDeliverDO : skuExtendVO.getSouvenirDeliverList()) { + skuServiceDeliverDO.setServiceId(skuForm.getId()); + skuServiceDeliverMapper.insert(skuServiceDeliverDO); + } + } + } + + @Override + public SkuExtendVO getSkuExtend(Integer formId) { + // 1. 查询所有与 formId 相关的扩展服务配置(已包含详情、物料等关联数据) + List skuServiceExtendVOList = skuServicesFormMapper.selectSkuServiceExtendWithDetails(formId); + + // 2. 创建返回对象 + SkuExtendVO skuExtendVO = new SkuExtendVO(); + // 可以设置 skuFormName,如果 formId 能关联到表单名称的话 + // skuExtendVO.setSkuFormName(...); + + // 3. 遍历查询结果,按类型分类赋值 + for (SkuServiceExtendVO skuServiceExtendVO : skuServiceExtendVOList) { + // 注意:数据库中 type 是 tinyint,但 VO 中定义为 String,需要转换或比较字符串 + // 枚举中 code 是 int,所以最好将 VO 的 type 转为 Integer 进行比较 + Integer typeCode; + try { + typeCode = Integer.valueOf(skuServiceExtendVO.getType()); + } catch (NumberFormatException e) { + // 如果 type 无法转为数字,跳过这条记录或记录日志 + continue; + } + + // 使用 switch 或 if-else 根据 typeCode 赋值 + if (ServiceTypeEnum.TRANSPORT_CAR_CONFIG.getCode() == typeCode) { + // 接运车辆配置 + skuExtendVO.setTransportCarSwitch(skuServiceExtendVO.getIsEnabled()); + skuExtendVO.setTransportCarList(skuServiceExtendVO.getSkuServiceDetailsDOList()); + + } else if (ServiceTypeEnum.TRANSPORT_CAR_MATERIAL.getCode() == typeCode) { + if (ObjectUtils.isEmpty(skuExtendVO.getTransportCarMaterialList())) { + skuExtendVO.setTransportCarMaterialList(skuServiceExtendVO.getSkuServiceMaterialDOList()); + } + } else if (ServiceTypeEnum.BODY_TRANSPORT_CONFIG.getCode() == typeCode) { + // 遗体运输目的地配置 + skuExtendVO.setTrafficSwitch(skuServiceExtendVO.getIsEnabled()); + skuExtendVO.setTrafficList(skuServiceExtendVO.getSkuServiceTransportDOList()); // 注意:这里用 TransportDO + + } else if (ServiceTypeEnum.BODY_TRANSPORT_MATERIAL.getCode() == typeCode) { + // 遗体运输目的地物料 + if (ObjectUtils.isEmpty(skuExtendVO.getTrafficMaterialList())) { + skuExtendVO.setTrafficMaterialList(skuServiceExtendVO.getSkuServiceMaterialDOList()); + } + + } else if (ServiceTypeEnum.BODY_CLEAN_CONFIG.getCode() == typeCode) { + // 遗体清洁配置 + skuExtendVO.setCleanSwitch(skuServiceExtendVO.getIsEnabled()); + skuExtendVO.setCleanList(skuServiceExtendVO.getSkuServiceDetailsDOList()); + + } else if (ServiceTypeEnum.BODY_CLEAN_MATERIAL.getCode() == typeCode) { + // 遗体清洁物料 + if (ObjectUtils.isEmpty(skuExtendVO.getCleanMaterialList())) { + skuExtendVO.setCleanMaterialList(skuServiceExtendVO.getSkuServiceMaterialDOList()); + } + } else if (ServiceTypeEnum.MEMORIAL_CONFIG.getCode() == typeCode) { + // 追思告别配置 + skuExtendVO.setReflectionSwitch(skuServiceExtendVO.getIsEnabled()); + skuExtendVO.setReflectionList(skuServiceExtendVO.getSkuServiceDetailsDOList()); + } else if (ServiceTypeEnum.MEMORIAL_MATERIAL.getCode() == typeCode) { + // 追思告别物料 + if (ObjectUtils.isEmpty(skuExtendVO.getReflectionMaterialList())) { + skuExtendVO.setReflectionMaterialList(skuServiceExtendVO.getSkuServiceMaterialDOList()); + } + } else if (ServiceTypeEnum.CREMATION_CONFIG.getCode() == typeCode) { + // 遗体火化配置 + skuExtendVO.setCremationSwitch(skuServiceExtendVO.getIsEnabled()); + skuExtendVO.setCremationList(skuServiceExtendVO.getSkuServiceDetailsDOList()); + } else if (ServiceTypeEnum.CREMATION_MATERIAL.getCode() == typeCode) { + // 遗体火化物料 + if (ObjectUtils.isEmpty(skuExtendVO.getCremationMaterialList())) { + skuExtendVO.setCremationMaterialList(skuServiceExtendVO.getSkuServiceMaterialDOList()); + } + + } else if (ServiceTypeEnum.ASH_PROCESSING_CONFIG.getCode() == typeCode) { + // 骨灰处理配置 + skuExtendVO.setAshProcessingSwitch(skuServiceExtendVO.getIsEnabled()); + skuExtendVO.setAshProcessingList(skuServiceExtendVO.getSkuServiceDetailsDOList()); + + } else if (ServiceTypeEnum.ASH_PROCESSING_DELIVERY.getCode() == typeCode) { + // 骨灰处理配送方式 (补充) + if (ObjectUtils.isEmpty(skuExtendVO.getAshProcessingDeliverList())) { + skuExtendVO.setAshProcessingDeliverList(skuServiceExtendVO.getSkuServiceDeliverDOList()); + } + } else if (ServiceTypeEnum.ASH_PROCESSING_MATERIAL.getCode() == typeCode) { + // 骨灰处理物料 + if (ObjectUtils.isEmpty(skuExtendVO.getAshProcessingMaterialList())) { + skuExtendVO.setAshProcessingMaterialList(skuServiceExtendVO.getSkuServiceMaterialDOList()); + } + } else if (ServiceTypeEnum.BONE_ASH_CONFIG.getCode() == typeCode) { + // 骨灰装殓配置 + skuExtendVO.setBoneashSwitch(skuServiceExtendVO.getIsEnabled()); + skuExtendVO.setBoneashList(skuServiceExtendVO.getSkuServiceDetailsDOList()); + // 注意:骨灰装殓是否有物料?根据你的 DO 列表,没有 material 字段 + + } else if (ServiceTypeEnum.SOUVENIR_CONFIG.getCode() == typeCode) { + // 纪念品配置 + skuExtendVO.setSouvenirSwitch(skuServiceExtendVO.getIsEnabled()); + skuExtendVO.setSouvenirList(skuServiceExtendVO.getSkuServiceDetailsDOList()); + } else if (ServiceTypeEnum.SOUVENIR_DELIVERY.getCode() == typeCode) { + // 纪念品配送方式 (补充) + if (ObjectUtils.isEmpty(skuExtendVO.getSouvenirDeliverList())) { + skuExtendVO.setSouvenirDeliverList(skuServiceExtendVO.getSkuServiceDeliverDOList()); + } + } + // 可以添加 default 处理未知类型 + } + + // 4. 返回组装好的对象 + return skuExtendVO; + } + + @Override + public void updateSku(SkuSaveReqVO updateReqVO) { + // 校验存在 + validateSkuExists(updateReqVO.getSkuId()); + // 更新 + SkuDO updateObj = BeanUtils.toBean(updateReqVO, SkuDO.class); + skuMapper.updateById(updateObj); + } + + @Override + @Transactional + public void updateProp(SkuPropVO skuPropVO) { + //保存sku + if (CollectionUtil.isNotEmpty(skuPropVO.getSkuList())) { + List skuList = skuPropVO.getSkuList(); + List skuListNew = new ArrayList<>(); + for (SkuDO sku : skuList) { + if (Objects.equals(BaseEnum.YES_ONE.getKey(), sku.getIsExist())) { + sku.setProdId(skuPropVO.getProdId()); + skuListNew.add(sku); + } + } + if (CollUtil.isNotEmpty(skuListNew)) { + skuMapper.insertBatch(skuListNew); + } + } + prodExtendMapper.updateByProdId(skuPropVO.getProdId(), skuPropVO.getIsDisable(), skuPropVO.getIsExpire()); + //保存规格 + prodPropService.updateProdPropAndValues(skuPropVO); + } + + + @Override + public void updatePropVal(Long id, String propValue) { + ProdPropValueDO prodPropValueDO = prodPropValueMapper.selectById(id); + ProdPropDO prodPropDO =prodPropMapper.selectById(prodPropValueDO.getPropId()); + List skuDOList = skuMapper.getSkuListByName(prodPropDO.getProdId(),prodPropValueDO.getPropValue()); + for (SkuDO skuDO : skuDOList) { + if (skuDO.getProperties() != null) { + String[] split = skuDO.getProperties().split(","); + for (String s : split) { + if (s.equals(prodPropValueDO.getPropValue())) { + skuDO.setProperties(propValue); + skuDO.setSkuName(propValue); + skuMapper.updateById(skuDO); + } + } + } + } + prodPropValueDO.setPropValue(propValue); + prodPropValueMapper.updateById(prodPropValueDO); + } + + + @Override + public void deleteProp(Long id) { + ProdPropValueDO prodPropValueDO = prodPropValueService.getById(id); + ProdPropDO prodPropDO =prodPropMapper.selectById(prodPropValueDO.getPropId()); + prodPropValueService.deleteProdPropValue(id); + List skuDOList = skuMapper.getSkuListByName(prodPropDO.getProdId(),prodPropValueDO.getPropValue()); + List skuids = new ArrayList<>(); + for (SkuDO skuDO : skuDOList) { + if (skuDO.getProperties() != null) { + String[] split = skuDO.getProperties().split(","); + for (String s : split) { + if (s.equals(prodPropValueDO.getPropValue())) { + skuids.add(skuDO.getSkuId()); + } + } + } + } + if (CollUtil.isNotEmpty(skuids)) { + skuMapper.batchSkuDeleted(skuids); + } + } + + @Override + public void disableProp(Long id, Integer state) { + ProdPropValueDO prodPropValueDO = prodPropValueService.getById(id); + prodPropValueDO.setState(state); + prodPropValueService.updateById(prodPropValueDO); + ProdPropDO prodPropDO =prodPropMapper.selectById(prodPropValueDO.getPropId()); + if (Objects.equals(BaseEnum.NO_ZERO.getKey(), state)) { + List skuDOList = skuMapper.getSkuListByName(prodPropDO.getProdId(),prodPropValueDO.getPropValue()); + List skuDOList1 = new ArrayList<>(); + for (SkuDO skuDO : skuDOList) { + if (skuDO.getProperties() != null) { + String[] split = skuDO.getProperties().split(","); + for (String s : split) { + if (s.equals(prodPropValueDO.getPropValue())) { + skuDO.setIsShelf(BaseEnum.NO_ZERO.getKey()); + skuDOList1.add(skuDO); + } + } + } + } + if (CollUtil.isNotEmpty(skuDOList1)) { + skuMapper.updateBatch(skuDOList1); + } + } + } + + + @Override + public SkuPropInfoVO getSKuPropList(Long prodId) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.eq(ProdPropDO::getProdId, prodId); + wrapper.eq(ProdPropDO::getState, BaseEnum.YES_ONE.getKey()) + .orderByAsc(ProdPropDO::getSort); + List list = prodPropService.list(wrapper); + list.forEach(prop -> { + List values = prodPropValueService.list( + new LambdaQueryWrapper() + .eq(ProdPropValueDO::getPropId, prop.getId()) + .eq(ProdPropValueDO::getDeleted, BaseEnum.NO_ZERO.getKey()) + .orderByAsc(ProdPropValueDO::getSort) + ); + prop.setProdPropValues(values); + }); + SkuPropInfoVO skuPropInfoVO = new SkuPropInfoVO(); + skuPropInfoVO.setProdId(prodId); + skuPropInfoVO.setProdPropSaveReqVO(list); + ProdExtendDO prodExtendDO = prodExtendService.getOne(new LambdaQueryWrapper().eq(ProdExtendDO::getProdId, prodId)); + skuPropInfoVO.setIsDisable(prodExtendDO.getIsDisable()); + skuPropInfoVO.setIsExpire(prodExtendDO.getIsExpire()); + return skuPropInfoVO; + } + + @Override + public PageResult getSKuPropRecycleBinList(ProPageReqVO proPageReqVO) { + IPage prodPageList = prodPropValueMapper.getSKuPropRecycleBinList(MyBatisUtils.buildPage(proPageReqVO), proPageReqVO.getProdId(), proPageReqVO.getPropValue()); + for (ProPropRecycleBinVO prodPage : prodPageList.getRecords()) { + if (prodPage.getDeleteTime() != null) { + prodPage.setRemainingDays(DateUtils.getRemainingDays(prodPage.getDeleteTime())); + } + } + return new PageResult<>(prodPageList.getRecords(), prodPageList.getTotal()); + } + +/* @Override + public SkuPropInfoVO getSKuPropList(Long prodId, Integer isExpire, Integer state) { + if (prodId == null) { + throw new IllegalArgumentException("prodId 不能为空"); + } + + // 1. 查询商品扩展信息(用于默认值) + ProdExtendDO extendDO = prodExtendService.getOne( + new LambdaQueryWrapper().eq(ProdExtendDO::getProdId, prodId) + ); + if (extendDO == null) { + // 如果没有扩展信息,可返回空 VO 或抛异常,根据业务决定 + return buildEmptySkuPropInfoVO(prodId); + } + + // 2. 设置默认值:isExpire 和 state + Integer finalIsExpire = isExpire != null ? isExpire : extendDO.getIsExpire(); + Integer finalState = state != null ? state : extendDO.getIsDisable(); // 注意:这里你用的是 isDisable 当 state?需确认业务逻辑 + + // 3. 查询有效属性列表 + LambdaQueryWrapper propWrapper = new LambdaQueryWrapper<>(); + propWrapper.eq(ProdPropDO::getProdId, prodId) + .eq(ProdPropDO::getState, BaseEnum.YES_ONE.getKey()) // 只查启用的属性 + .orderByAsc(ProdPropDO::getSort); + + List propList = prodPropService.list(propWrapper); + if (propList.isEmpty()) { + return buildSkuPropInfoVO(prodId, propList, extendDO); + } + + // 4. 提取所有属性 ID,用于批量查询属性值 + List propIds = propList.stream() + .map(ProdPropDO::getId) + .collect(Collectors.toList()); + + // 5. 构建属性值查询条件(批量查询) + LambdaQueryWrapper valueWrapper = new LambdaQueryWrapper<>(); + valueWrapper.in(ProdPropValueDO::getPropId, propIds) + .eq(ProdPropValueDO::getState, BaseEnum.YES_ONE.getKey()); // 启用状态 + + // 根据 isExpire 过滤(注意字段是 getIsExpire) + if (BaseEnum.NO_ZERO.getKey().equals(finalIsExpire)) { + valueWrapper.eq(ProdPropValueDO::getIsExpire, BaseEnum.NO_ZERO.getKey()); + } else { + valueWrapper.eq(ProdPropValueDO::getIsExpire, BaseEnum.YES_ONE.getKey()); + } + + // 根据 state 过滤(注意:这里你用的是 getState 字段) + if (BaseEnum.NO_ZERO.getKey().equals(finalState)) { + valueWrapper.eq(ProdPropValueDO::getState, BaseEnum.NO_ZERO.getKey()); + } else { + valueWrapper.eq(ProdPropValueDO::getState, BaseEnum.YES_ONE.getKey()); + } + + valueWrapper.orderByDesc(ProdPropValueDO::getSort); + + List valueList = prodPropValueService.list(valueWrapper); + + // 6. 按 propId 分组,便于后续关联 + Map> valueMap = valueList.stream() + .collect(Collectors.groupingBy(ProdPropValueDO::getPropId)); + + // 7. 关联属性与属性值 + propList.forEach(prop -> + prop.setProdPropValues(valueMap.getOrDefault(prop.getId(), Collections.emptyList())) + ); + + // 8. 构建并返回 VO + return buildSkuPropInfoVO(prodId, propList, extendDO); + }*/ + + /* @Override + public SkuPropInfoVO getSKuPropList(Long prodId,Integer isExpire,Integer state) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.eq(ProdPropDO::getProdId,prodId); + wrapper.eq(ProdPropDO::getState, BaseEnum.YES_ONE.getKey()) + .orderByAsc(ProdPropDO::getSort); + ProdExtendDO prodExtendDO = prodExtendService.getOne(new LambdaQueryWrapper().eq(ProdExtendDO::getProdId, prodId)); + if(isExpire==null){ + isExpire = prodExtendDO.getIsExpire(); + } + if(state==null){ + state = prodExtendDO.getIsDisable(); + } + List list = prodPropService.list(wrapper); + list.forEach(prop -> { + LambdaQueryWrapper wrapper1 = new LambdaQueryWrapper<>(); + wrapper1.eq(ProdPropValueDO::getPropId,prop.getId()); + wrapper1.eq(ProdPropValueDO::getState, BaseEnum.YES_ONE.getKey()); + if (isExpire!= null&& Objects.equals(BaseEnum.NO_ZERO.getKey(),isExpire) ) { + wrapper1.eq(ProdPropValueDO::getIsExpire, BaseEnum.NO_ZERO.getKey()); + }else { + wrapper1.eq(ProdPropValueDO::getIsExpire, BaseEnum.YES_ONE.getKey()); + } + if (state!= null&& Objects.equals(BaseEnum.NO_ZERO.getKey(),state) ) { + wrapper1.eq(ProdPropValueDO::getState, BaseEnum.NO_ZERO.getKey()); + }else { + wrapper1.eq(ProdPropValueDO::getState, BaseEnum.YES_ONE.getKey()); + } + wrapper1.orderByDesc(ProdPropValueDO::getSort); + List lists = prodPropValueService.list(wrapper1); + prop.setProdPropValues(lists); + }); + SkuPropInfoVO skuPropInfoVO = new SkuPropInfoVO(); + skuPropInfoVO.setProdId(prodId); + skuPropInfoVO.setProdPropSaveReqVO(list); + + skuPropInfoVO.setIsDisable(prodExtendDO.getIsDisable()); + skuPropInfoVO.setIsExpire(prodExtendDO.getIsExpire()); + return skuPropInfoVO; + }*/ + + + // 封装构建 VO 的逻辑,避免重复代码 + private SkuPropInfoVO buildSkuPropInfoVO(Long prodId, List propList, ProdExtendDO extendDO) { + SkuPropInfoVO vo = new SkuPropInfoVO(); + vo.setProdId(prodId); + vo.setProdPropSaveReqVO(propList); + vo.setIsDisable(extendDO.getIsDisable()); + vo.setIsExpire(extendDO.getIsExpire()); + return vo; + } + + // 空情况处理 + private SkuPropInfoVO buildEmptySkuPropInfoVO(Long prodId) { + SkuPropInfoVO vo = new SkuPropInfoVO(); + vo.setProdId(prodId); + vo.setProdPropSaveReqVO(Collections.emptyList()); + vo.setIsDisable(BaseEnum.NO_ZERO.getKey()); // 默认值 + vo.setIsExpire(BaseEnum.NO_ZERO.getKey()); + return vo; + } + + + @Override + @Transactional(rollbackFor = Exception.class) + public void deleteSku(Long id) { + // 校验存在 + validateSkuExists(id); + SkuDO prodSku = skuMapper.selectById(id); + // Step 1: 获取该商品下所有未删除的 SKU 的 properties + List activeProperties = skuMapper.selectPropertiesByProdIdAndNotDeleted(prodSku.getProdId()); + + // Step 2: 提取所有正在被使用的属性值(去重) + Set currentlyUsedValues = new HashSet<>(); + for (String props : activeProperties) { + if (props != null && !props.trim().isEmpty()) { + String[] values = props.split(","); + for (String v : values) { + currentlyUsedValues.add(v.trim()); + } + } + } + // 删除 + skuMapper.deleteBySkuId(id); + // Step 3: 查询该商品下所有 rule=1 的属性值(销售属性) + List allPropValues = prodPropValueMapper.selectSalesValuesByProdId(prodSku.getProdId()); + // Step 4: 遍历每个属性值,检查是否还在被使用 + for (ProdPropValueDO pv : allPropValues) { + String value = pv.getPropValue().trim(); + // 如果当前属性值不在“活跃使用”列表中,说明没有未删除的 SKU 使用它 + if (!currentlyUsedValues.contains(value)) { + // 可以安全删除该属性值 + // 可以安全禁用该属性值 + ProdPropValueDO prodPropValueDO = new ProdPropValueDO(); + prodPropValueDO.setId(pv.getId()); + prodPropValueDO.setIsExpire(BaseEnum.YES_ONE.getKey()); + prodPropValueDO.setDeleteTime(new Date()); + prodPropValueMapper.updateById(prodPropValueDO); + } + } + } + + + @Transactional(rollbackFor = Exception.class) + public void deleteSkus(List ids) { + if (ids == null || ids.isEmpty()) { + return; + } + // Step 1: 查询这些 SKU 的基本信息(主要是 prod_id) + List skuList = skuMapper.selectByIds(ids); + if (skuList.isEmpty()) { + return; + } + Long prodId = skuList.get(0).getProdId(); + // 校验是否属于同一个商品(可选) + boolean allSameProd = skuList.stream().allMatch(s -> s.getProdId().equals(prodId)); + if (!allSameProd) { + throw new IllegalArgumentException("批量删除的 SKU 必须属于同一个商品"); + } + // Step 3: 获取该商品下【当前仍然未删除】的 SKU 的 properties + List activeProperties = skuMapper.selectPropertiesByProdIdAndNotDeleted(prodId); + for (Long id : ids) { + // 删除 + skuMapper.deleteBySkuId(id); + } + // Step 4: 提取所有仍在使用的属性值(去重 + trim) + Set currentlyUsedValues = new HashSet<>(); + for (String props : activeProperties) { + if (props != null && !props.trim().isEmpty()) { + String[] values = props.split(","); + for (String v : values) { + currentlyUsedValues.add(v.trim()); + } + } + } + // Step 5: 查询该商品下所有 rule=1 的销售属性值(未删除的) + List allPropValues = prodPropValueMapper.selectSalesValuesByProdId(prodId); + + // Step 6: 收集需要删除的属性值 ID + List valueIdsToDelete = new ArrayList<>(); + for (ProdPropValueDO pv : allPropValues) { + String value = pv.getPropValue().trim(); + if (!currentlyUsedValues.contains(value)) { + valueIdsToDelete.add(pv.getId()); + } + } + // Step 7: 批量删除无用的属性值 + if (!valueIdsToDelete.isEmpty()) { + prodPropValueMapper.batchMarkDeleted(valueIdsToDelete); + } + } + + //恢复SKU + @Override + @Transactional(rollbackFor = Exception.class) + public void restoreSkuList(List ids) { + List skuList = skuMapper.getskuListBySkuIds(ids); + if (skuList.isEmpty()) { + return; + } + + // 获取商品下所有被删除的SKU(除了当前要恢复的 ids) + List skuAllDeletedList = skuMapper.getskuListByDeleted(skuList.get(0).getProdId(), ids); + + Set allDeletedValues = new HashSet<>(); + for (SkuDO sku : skuAllDeletedList) { + if (sku.getProperties() != null && !sku.getProperties().trim().isEmpty()) { + String[] values = sku.getProperties().split(","); + for (String v : values) { + allDeletedValues.add(v.trim()); + } + } + } + + Set currentlyUsedValues = new HashSet<>(); + for (SkuDO sku : skuList) { + skuMapper.updateSkuDeleted(sku.getSkuId()); + if (sku.getProperties() != null && !sku.getProperties().trim().isEmpty()) { + String[] values = sku.getProperties().split(","); + for (String v : values) { + currentlyUsedValues.add(v.trim()); + } + } + } + + // === 核心修改点:提取 currentlyUsedValues 中不在 allDeletedValues 的值(即新增启用的规格值)=== + Set valuesToRestore = new HashSet<>(currentlyUsedValues); + valuesToRestore.removeAll(allDeletedValues); // 只保留“之前被删掉过”的规格值 + + // 如果没有需要恢复的规格值,直接返回 + if (valuesToRestore.isEmpty()) { + return; + } + + // 查询商品下所有属性值 + Long prodId = skuList.get(0).getProdId(); + List allPropValues = prodPropValueMapper.selectRestoreProp(prodId); + + if (!allPropValues.isEmpty()) { + for (ProdPropValueDO pv : allPropValues) { + // 仅当该属性值是“当前使用”且“之前被删除”的(即在 valuesToRestore 中),才恢复 + if (valuesToRestore.contains(pv.getPropValue())) { + pv.setIsExpire(BaseEnum.NO_ZERO.getKey()); // 标记为未过期 + prodPropValueMapper.updateById(pv); + } + } + } + } + + @Override + public void restorePropList(List ids) { + prodPropValueMapper.restorePropValue(ids); + List prodPropValueDOList = prodPropValueMapper.getskuListByPropValueIds(ids); + for (ProdPropValueDO prodPropValueDO : prodPropValueDOList) { + int maxPropValue =prodPropValueMapper.getMaxPropValue(prodPropValueDO.getPropId()); + prodPropValueDO.setSort(maxPropValue+1); + prodPropValueService.updateById(prodPropValueDO); + } + } + + + @Override + public void updatSkuIsShelf(Long id, Integer isShelf) { + // 校验存在 + validateSkuExists(id); + + SkuDO prodSku = skuMapper.selectById(id); + // Step 1: 获取该商品下所有未禁用的 SKU 的 properties + List activeProperties = skuMapper.selectPropertiesByProdIdShelf(prodSku.getProdId()); + prodSku.setIsShelf(isShelf); + skuMapper.updateById(prodSku); + // Step 2: 提取所有正在被使用的属性值(去重) + Set currentlyUsedValues = new HashSet<>(); + for (String props : activeProperties) { + if (props != null && !props.trim().isEmpty()) { + String[] values = props.split(","); + for (String v : values) { + currentlyUsedValues.add(v.trim()); + } + } + } + // Step 3: 查询该商品下所有 rule=1 的属性值(销售属性) + List allPropValues = prodPropValueMapper.selectSalesValuesByState(prodSku.getProdId()); + // Step 4: 遍历每个属性值,检查是否还在被使用 + for (ProdPropValueDO pv : allPropValues) { + String value = pv.getPropValue().trim(); + // 如果当前属性值不在“活跃使用”列表中,说明没有未删除的 SKU 使用它 + if (!currentlyUsedValues.contains(value)) { + // 可以安全禁用该属性值 + ProdPropValueDO prodPropValueDO = new ProdPropValueDO(); + prodPropValueDO.setId(pv.getId()); + prodPropValueDO.setState(BaseEnum.NO_ZERO.getKey()); + prodPropValueMapper.updateById(prodPropValueDO); + } + } + } + + @Override + public void updatSkuIsShelfs(List ids, Integer isShelf) { + if (ids == null || ids.isEmpty()) { + return; + } + + // Step 1: 查询这些 SKU 的基本信息(主要是 prod_id) + List skuList = skuMapper.selectByIds(ids); + if (skuList.isEmpty()) { + return; + } + + Long prodId = skuList.get(0).getProdId(); + // 校验是否属于同一个商品(可选) + boolean allSameProd = skuList.stream().allMatch(s -> s.getProdId().equals(prodId)); + if (!allSameProd) { + throw new IllegalArgumentException("批量删除的 SKU 必须属于同一个商品"); + } + for (Long id : ids) { + SkuDO sku = new SkuDO(); + sku.setSkuId(id); + sku.setIsShelf(isShelf); + skuMapper.updateById(sku); + } + // ================================ + //新增逻辑:判断商品整体上下架状态,并更新 tz_prod_prop_value.state + // ================================ + // 查询该商品下所有未删除的 SKU 的 is_shelf 状态 + List allSkuShelfStatus = skuMapper.selectShelfStatusByProdId(prodId); + + if (allSkuShelfStatus.isEmpty()) { + return; // 没有 SKU,无需处理 + } + + boolean allShelf = allSkuShelfStatus.stream().allMatch(status -> status == 1); // 全部上架 + boolean allOffShelf = allSkuShelfStatus.stream().allMatch(status -> status == 0); // 全部下架 + + Integer targetState = null; + if (allShelf) { + targetState = 1; // 启用 + } else if (allOffShelf) { + targetState = 0; // 禁用 + } + // 混合状态:不修改 state + // 如果需要更新状态,则批量更新 tz_prod_prop_value 的 state 字段 + if (targetState != null) { + prodPropValueMapper.updateStateByProdId(prodId, targetState); + } + } + + + private void validateSkuExists(Long id) { + if (skuMapper.selectById(id) == null) { + throw exception(ErrorCodeConstants.SKU_NOT_EXISTS); + } + } + + @Override + public SkuDO getSku(Long id) { + return skuMapper.selectById(id); + } + + @Override + public PageResult getSkuRecycleBinPageList(SkuPageReqVO pageReqVO) { + IPage prodPageList = skuMapper.getSkuRecycleBinPageList(MyBatisUtils.buildPage(pageReqVO), pageReqVO.getProdId(), pageReqVO.getProperties()); + for (SkuRecycleBinVO prodPage : prodPageList.getRecords()) { + if (prodPage.getDeleteTime() != null) { + prodPage.setRemainingDays(DateUtils.getRemainingDays(prodPage.getDeleteTime())); + } + } + return new PageResult<>(prodPageList.getRecords(), prodPageList.getTotal()); + } + + @Override + public PageResult getSkuPageList(SkuPageReqVO pageReqVO) { + IPage prodPageList = skuMapper.getSkuPageList(MyBatisUtils.buildPage(pageReqVO), pageReqVO.getProdId(), pageReqVO.getSkuId(), pageReqVO.getProperties()); + + return new PageResult<>(prodPageList.getRecords(), prodPageList.getTotal()); + } + + + @Override + public PageResult getSkuPage(SkuPageReqVO pageReqVO) { + return null; + } + + @Override + @Transactional + public void updateServiceDetails(List skuServiceDetailsList) { + if (ObjectUtils.isNotEmpty(skuServiceDetailsList)) { + skuServiceDetailsMapper.delete(SkuServiceDetailsDO::getServiceId, skuServiceDetailsList.get(0).getServiceId()); + skuServiceDetailsMapper.insertBatch(skuServiceDetailsList); + } + } + + @Override + @Transactional + public void updateMaterial(List skuServiceMaterialList) { + if (ObjectUtils.isNotEmpty(skuServiceMaterialList)) { + skuServiceMaterialMapper.delete(SkuServiceMaterialDO::getServiceId, skuServiceMaterialList.get(0).getServiceId()); + skuServiceMaterialMapper.insertBatch(skuServiceMaterialList); + } + } + + @Override + @Transactional + public void updateTransportAdress(List skuServiceTransportDOList) { + if (ObjectUtils.isNotEmpty(skuServiceTransportDOList)) { + skuServiceTransportMapper.delete(SkuServiceTransportDO::getServiceId, skuServiceTransportDOList.get(0).getServiceId()); + skuServiceTransportMapper.insertBatch(skuServiceTransportDOList); + } + } + + @Override + public void updateDeliver(List skuServiceDeliverList) { + if (ObjectUtils.isNotEmpty(skuServiceDeliverList)) { + skuServiceDeliverMapper.delete(SkuServiceDeliverDO::getServiceId, skuServiceDeliverList.get(0).getServiceId()); + skuServiceDeliverMapper.insertBatch(skuServiceDeliverList); + } + } + + +} \ No newline at end of file diff --git a/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/service/impl/SkuServiceMaterialServiceImpl.java b/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/service/impl/SkuServiceMaterialServiceImpl.java new file mode 100644 index 0000000..9131578 --- /dev/null +++ b/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/service/impl/SkuServiceMaterialServiceImpl.java @@ -0,0 +1,76 @@ +package com.tashow.cloud.product.service.impl; + +import com.tashow.cloud.productapi.api.product.dto.SkuServiceMaterialDO; +import com.tashow.cloud.product.mapper.SkuServiceMaterialMapper; +import com.tashow.cloud.product.service.SkuServiceMaterialService; +import com.tashow.cloud.productapi.api.product.vo.skuservicematerial.SkuServiceMaterialPageReqVO; +import com.tashow.cloud.productapi.api.product.vo.skuservicematerial.SkuServiceMaterialSaveReqVO; +import com.tashow.cloud.productapi.enums.ErrorCodeConstants; +import org.springframework.stereotype.Service; +import jakarta.annotation.Resource; +import org.springframework.validation.annotation.Validated; +import org.springframework.transaction.annotation.Transactional; + +import java.util.*; +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.common.pojo.PageParam; +import com.tashow.cloud.common.util.object.BeanUtils; + + +import static com.tashow.cloud.common.exception.util.ServiceExceptionUtil.exception; + +/** + * 服务物料详情 Service 实现类 + * + * @author 芋道源码 + */ +@Service +@Validated +public class SkuServiceMaterialServiceImpl implements SkuServiceMaterialService { + + @Resource + private SkuServiceMaterialMapper skuServiceMaterialMapper; + + @Override + public Long createSkuServiceMaterial(SkuServiceMaterialSaveReqVO createReqVO) { + // 插入 + SkuServiceMaterialDO skuServiceMaterial = BeanUtils.toBean(createReqVO, SkuServiceMaterialDO.class); + skuServiceMaterialMapper.insert(skuServiceMaterial); + // 返回 + return skuServiceMaterial.getId(); + } + + @Override + public void updateSkuServiceMaterial(SkuServiceMaterialSaveReqVO updateReqVO) { + // 校验存在 + validateSkuServiceMaterialExists(updateReqVO.getId()); + // 更新 + SkuServiceMaterialDO updateObj = BeanUtils.toBean(updateReqVO, SkuServiceMaterialDO.class); + skuServiceMaterialMapper.updateById(updateObj); + } + + @Override + public void deleteSkuServiceMaterial(Long id) { + // 校验存在 + validateSkuServiceMaterialExists(id); + // 删除 + skuServiceMaterialMapper.deleteById(id); + } + + private void validateSkuServiceMaterialExists(Long id) { + if (skuServiceMaterialMapper.selectById(id) == null) { + throw exception(ErrorCodeConstants.SKU_SERVICE_MATERIAL_NOT_EXISTS); + } + } + + @Override + public SkuServiceMaterialDO getSkuServiceMaterial(Long id) { + return skuServiceMaterialMapper.selectById(id); + } + + @Override + public PageResult getSkuServiceMaterialPage(SkuServiceMaterialPageReqVO pageReqVO) { + return null; + } + +} \ No newline at end of file diff --git a/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/service/impl/SkuServiceTransportServiceImpl.java b/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/service/impl/SkuServiceTransportServiceImpl.java new file mode 100644 index 0000000..41627e4 --- /dev/null +++ b/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/service/impl/SkuServiceTransportServiceImpl.java @@ -0,0 +1,74 @@ +package com.tashow.cloud.product.service.impl; + +import com.tashow.cloud.productapi.api.product.dto.SkuServiceTransportDO; +import com.tashow.cloud.product.mapper.SkuServiceTransportMapper; +import com.tashow.cloud.product.service.SkuServiceTransportService; +import com.tashow.cloud.productapi.api.product.vo.skuservicetransport.SkuServiceTransportPageReqVO; +import com.tashow.cloud.productapi.api.product.vo.skuservicetransport.SkuServiceTransportSaveReqVO; +import com.tashow.cloud.productapi.enums.ErrorCodeConstants; +import org.springframework.stereotype.Service; +import jakarta.annotation.Resource; +import org.springframework.validation.annotation.Validated; +import org.springframework.transaction.annotation.Transactional; + +import java.util.*; +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.common.pojo.PageParam; +import com.tashow.cloud.common.util.object.BeanUtils; +import static com.tashow.cloud.common.exception.util.ServiceExceptionUtil.exception; + +/** + * 服务遗体运输 Service 实现类 + * + * @author 芋道源码 + */ +@Service +@Validated +public class SkuServiceTransportServiceImpl implements SkuServiceTransportService { + + @Resource + private SkuServiceTransportMapper skuServiceTransportMapper; + + @Override + public Long createSkuServiceTransport(SkuServiceTransportSaveReqVO createReqVO) { + // 插入 + SkuServiceTransportDO skuServiceTransport = BeanUtils.toBean(createReqVO, SkuServiceTransportDO.class); + skuServiceTransportMapper.insert(skuServiceTransport); + // 返回 + return skuServiceTransport.getId(); + } + + @Override + public void updateSkuServiceTransport(SkuServiceTransportSaveReqVO updateReqVO) { + // 校验存在 + validateSkuServiceTransportExists(updateReqVO.getId()); + // 更新 + SkuServiceTransportDO updateObj = BeanUtils.toBean(updateReqVO, SkuServiceTransportDO.class); + skuServiceTransportMapper.updateById(updateObj); + } + + @Override + public void deleteSkuServiceTransport(Long id) { + // 校验存在 + validateSkuServiceTransportExists(id); + // 删除 + skuServiceTransportMapper.deleteById(id); + } + + private void validateSkuServiceTransportExists(Long id) { + if (skuServiceTransportMapper.selectById(id) == null) { + throw exception(ErrorCodeConstants.SKU_SERVICE_TRANSPORT_NOT_EXISTS); + } + } + + @Override + public SkuServiceTransportDO getSkuServiceTransport(Long id) { + return skuServiceTransportMapper.selectById(id); + } + + @Override + public PageResult getSkuServiceTransportPage(SkuServiceTransportPageReqVO pageReqVO) { + return null; + } + +} \ No newline at end of file diff --git a/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/service/impl/SkuServicesFormServiceImpl.java b/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/service/impl/SkuServicesFormServiceImpl.java new file mode 100644 index 0000000..5f226d1 --- /dev/null +++ b/tashow-module/tashow-module-product/src/main/java/com/tashow/cloud/product/service/impl/SkuServicesFormServiceImpl.java @@ -0,0 +1,73 @@ + package com.tashow.cloud.product.service.impl; + +import com.tashow.cloud.productapi.api.product.dto.SkuServicesFormDO; +import com.tashow.cloud.product.mapper.SkuServicesFormMapper; +import com.tashow.cloud.product.service.SkuServicesFormService; +import com.tashow.cloud.productapi.api.product.vo.skuservicesform.SkuServicesFormPageReqVO; +import com.tashow.cloud.productapi.api.product.vo.skuservicesform.SkuServicesFormSaveReqVO; +import com.tashow.cloud.productapi.enums.ErrorCodeConstants; +import org.springframework.stereotype.Service; +import jakarta.annotation.Resource; +import org.springframework.validation.annotation.Validated; +import org.springframework.transaction.annotation.Transactional; +import java.util.*; +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.common.pojo.PageParam; +import com.tashow.cloud.common.util.object.BeanUtils; +import static com.tashow.cloud.common.exception.util.ServiceExceptionUtil.exception; + +/** + * 商品SKU扩展服务表单 Service 实现类 + * + * @author 芋道源码 + */ +@Service +@Validated +public class SkuServicesFormServiceImpl implements SkuServicesFormService { + + @Resource + private SkuServicesFormMapper skuServicesFormMapper; + + @Override + public Long createSkuServicesForm(SkuServicesFormSaveReqVO createReqVO) { + // 插入 + SkuServicesFormDO skuServicesForm = BeanUtils.toBean(createReqVO, SkuServicesFormDO.class); + skuServicesFormMapper.insert(skuServicesForm); + // 返回 + return skuServicesForm.getId(); + } + + @Override + public void updateSkuServicesForm(SkuServicesFormSaveReqVO updateReqVO) { + // 校验存在 + validateSkuServicesFormExists(updateReqVO.getId()); + // 更新 + SkuServicesFormDO updateObj = BeanUtils.toBean(updateReqVO, SkuServicesFormDO.class); + skuServicesFormMapper.updateById(updateObj); + } + + @Override + public void deleteSkuServicesForm(Long id) { + // 校验存在 + validateSkuServicesFormExists(id); + // 删除 + skuServicesFormMapper.deleteById(id); + } + + private void validateSkuServicesFormExists(Long id) { + if (skuServicesFormMapper.selectById(id) == null) { + throw exception(ErrorCodeConstants.SKU_SERVICES_FORM_NOT_EXISTS); + } + } + + @Override + public SkuServicesFormDO getSkuServicesForm(Long id) { + return skuServicesFormMapper.selectById(id); + } + + @Override + public PageResult getSkuServicesFormPage(SkuServicesFormPageReqVO pageReqVO) { + return null; + } + +} \ No newline at end of file diff --git a/tashow-module/tashow-module-product/src/main/resources/application-local.yaml b/tashow-module/tashow-module-product/src/main/resources/application-local.yaml new file mode 100644 index 0000000..e56317a --- /dev/null +++ b/tashow-module/tashow-module-product/src/main/resources/application-local.yaml @@ -0,0 +1,16 @@ +--- #################### 注册中心 + 配置中心相关配置 #################### + +spring: + cloud: + nacos: + server-addr: 43.139.42.137:8848 # Nacos 服务器地址 + username: nacos # Nacos 账号 + password: nacos # Nacos 密码 + discovery: # 【配置中心】配置项 + namespace: 16bd40df-7cc7-4c2c-82c2-6186ade7bb08 # 命名空间。这里使用 dev 开发环境 + group: DEFAULT_GROUP # 使用的 Nacos 配置分组,默认为 DEFAULT_GROUP + metadata: + version: 1.0.0 # 服务实例的版本号,可用于灰度发布 + config: # 【注册中心】配置项 + namespace: 16bd40df-7cc7-4c2c-82c2-6186ade7bb08 # 命名空间。这里使用 dev 开发环境 + group: DEFAULT_GROUP # 使用的 Nacos 配置分组,默认为 DEFAULT_GROUP diff --git a/tashow-module/tashow-module-product/src/main/resources/application.yaml b/tashow-module/tashow-module-product/src/main/resources/application.yaml new file mode 100644 index 0000000..1d33782 --- /dev/null +++ b/tashow-module/tashow-module-product/src/main/resources/application.yaml @@ -0,0 +1,12 @@ +server: + port: 48083 +spring: + application: + name: product-server + profiles: + active: local + config: + import: + - optional:classpath:application-${spring.profiles.active}.yaml # 加载【本地】配置 + - optional:nacos:application.yaml # 加载【Nacos】的配置 + - optional:nacos:${spring.application.name}-${spring.profiles.active}.yaml # 加载【Nacos】的配置 diff --git a/tashow-module/tashow-module-product/src/main/resources/logback-spring.xml b/tashow-module/tashow-module-product/src/main/resources/logback-spring.xml new file mode 100644 index 0000000..8bef45c --- /dev/null +++ b/tashow-module/tashow-module-product/src/main/resources/logback-spring.xml @@ -0,0 +1,83 @@ + + + + + + + + + +       + + + ${PATTERN_DEFAULT} + + + + + + + + + + ${PATTERN_DEFAULT} + + + + ${LOG_FILE} + + + ${LOGBACK_ROLLINGPOLICY_FILE_NAME_PATTERN:-${LOG_FILE}.%d{yyyy-MM-dd}.%i.gz} + + ${LOGBACK_ROLLINGPOLICY_CLEAN_HISTORY_ON_START:-false} + + ${LOGBACK_ROLLINGPOLICY_MAX_FILE_SIZE:-10MB} + + ${LOGBACK_ROLLINGPOLICY_TOTAL_SIZE_CAP:-0} + + ${LOGBACK_ROLLINGPOLICY_MAX_HISTORY:-30} + + + + + + 0 + + 256 + + + + + + + + ${PATTERN_DEFAULT} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tashow-module/tashow-module-product/src/main/resources/mapper/product/CategoryMapper.xml b/tashow-module/tashow-module-product/src/main/resources/mapper/product/CategoryMapper.xml new file mode 100644 index 0000000..a8dc9eb --- /dev/null +++ b/tashow-module/tashow-module-product/src/main/resources/mapper/product/CategoryMapper.xml @@ -0,0 +1,31 @@ + + + + + + + \ No newline at end of file diff --git a/tashow-module/tashow-module-product/src/main/resources/mapper/product/ProdAdditionalFeeDatesMapper.xml b/tashow-module/tashow-module-product/src/main/resources/mapper/product/ProdAdditionalFeeDatesMapper.xml new file mode 100644 index 0000000..595494e --- /dev/null +++ b/tashow-module/tashow-module-product/src/main/resources/mapper/product/ProdAdditionalFeeDatesMapper.xml @@ -0,0 +1,15 @@ + + + + + + + + delete from tz_prod_additional_fee_dates where prod_id = #{prodId} AND type = #{type} + + \ No newline at end of file diff --git a/tashow-module/tashow-module-product/src/main/resources/mapper/product/ProdAdditionalFeePeriodsMapper.xml b/tashow-module/tashow-module-product/src/main/resources/mapper/product/ProdAdditionalFeePeriodsMapper.xml new file mode 100644 index 0000000..80bd3d5 --- /dev/null +++ b/tashow-module/tashow-module-product/src/main/resources/mapper/product/ProdAdditionalFeePeriodsMapper.xml @@ -0,0 +1,12 @@ + + + + + + + \ No newline at end of file diff --git a/tashow-module/tashow-module-product/src/main/resources/mapper/product/ProdEmergencyResponseIntervalsMapper.xml b/tashow-module/tashow-module-product/src/main/resources/mapper/product/ProdEmergencyResponseIntervalsMapper.xml new file mode 100644 index 0000000..cae46ea --- /dev/null +++ b/tashow-module/tashow-module-product/src/main/resources/mapper/product/ProdEmergencyResponseIntervalsMapper.xml @@ -0,0 +1,14 @@ + + + + + + + delete from tz_prod_emergency_response_intervals where config_id = #{configId} + + \ No newline at end of file diff --git a/tashow-module/tashow-module-product/src/main/resources/mapper/product/ProdEmergencyResponseMapper.xml b/tashow-module/tashow-module-product/src/main/resources/mapper/product/ProdEmergencyResponseMapper.xml new file mode 100644 index 0000000..c17a966 --- /dev/null +++ b/tashow-module/tashow-module-product/src/main/resources/mapper/product/ProdEmergencyResponseMapper.xml @@ -0,0 +1,12 @@ + + + + + + + \ No newline at end of file diff --git a/tashow-module/tashow-module-product/src/main/resources/mapper/product/ProdExtendMapper.xml b/tashow-module/tashow-module-product/src/main/resources/mapper/product/ProdExtendMapper.xml new file mode 100644 index 0000000..50520d3 --- /dev/null +++ b/tashow-module/tashow-module-product/src/main/resources/mapper/product/ProdExtendMapper.xml @@ -0,0 +1,18 @@ + + + + + + + UPDATE tz_prod_extend + SET + is_disable = #{isDisable}, + is_expire = #{isExpire} + WHERE prod_id = #{prodId} + + \ No newline at end of file diff --git a/tashow-module/tashow-module-product/src/main/resources/mapper/product/ProdMapper.xml b/tashow-module/tashow-module-product/src/main/resources/mapper/product/ProdMapper.xml new file mode 100644 index 0000000..1d744e7 --- /dev/null +++ b/tashow-module/tashow-module-product/src/main/resources/mapper/product/ProdMapper.xml @@ -0,0 +1,634 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + UPDATE tz_prod + SET deleted = 0 + WHERE prod_id IN + + #{id} + + + + + + + \ No newline at end of file diff --git a/tashow-module/tashow-module-product/src/main/resources/mapper/product/ProdPropMapper.xml b/tashow-module/tashow-module-product/src/main/resources/mapper/product/ProdPropMapper.xml new file mode 100644 index 0000000..f2b23af --- /dev/null +++ b/tashow-module/tashow-module-product/src/main/resources/mapper/product/ProdPropMapper.xml @@ -0,0 +1,17 @@ + + + + + + + + + + \ No newline at end of file diff --git a/tashow-module/tashow-module-product/src/main/resources/mapper/product/ProdPropValueMapper.xml b/tashow-module/tashow-module-product/src/main/resources/mapper/product/ProdPropValueMapper.xml new file mode 100644 index 0000000..5eb4f81 --- /dev/null +++ b/tashow-module/tashow-module-product/src/main/resources/mapper/product/ProdPropValueMapper.xml @@ -0,0 +1,130 @@ + + + + + + + insert into tz_prod_prop_value (prop_id,prop_value) values + + (#{propId},#{prodPropValue.propValue}) + + + + + + + + + + + + + + + UPDATE tz_prod_prop_value + SET state = #{state} + WHERE prop_id IN ( + SELECT id FROM tz_prod_prop + WHERE prod_id = #{prodId} + AND deleted = 0 + ) + AND deleted = 0 + + + + + UPDATE tz_prod_prop_value + SET is_expire = 1, delete_time = NOW() + WHERE id IN + + #{id} + + + + + + UPDATE tz_prod_prop_value + SET + deleted = 1, delete_time = NOW() + WHERE id = #{id} + + + + + + + + + + + + UPDATE tz_prod_prop_value + SET is_expire = 1, deleted = 0 + WHERE id IN + + #{id} + + + + \ No newline at end of file diff --git a/tashow-module/tashow-module-product/src/main/resources/mapper/product/ProdReservationConfigMapper.xml b/tashow-module/tashow-module-product/src/main/resources/mapper/product/ProdReservationConfigMapper.xml new file mode 100644 index 0000000..0ca09cf --- /dev/null +++ b/tashow-module/tashow-module-product/src/main/resources/mapper/product/ProdReservationConfigMapper.xml @@ -0,0 +1,14 @@ + + + + + + + delete from tz_prod_reservation_config where prod_id = #{prodId} + + \ No newline at end of file diff --git a/tashow-module/tashow-module-product/src/main/resources/mapper/product/ProdServiceAreaRelevanceMapper.xml b/tashow-module/tashow-module-product/src/main/resources/mapper/product/ProdServiceAreaRelevanceMapper.xml new file mode 100644 index 0000000..80a5ae6 --- /dev/null +++ b/tashow-module/tashow-module-product/src/main/resources/mapper/product/ProdServiceAreaRelevanceMapper.xml @@ -0,0 +1,15 @@ + + + + + + + + delete from tz_prod_service_area_relevance where prod_id = #{prodId} + + \ No newline at end of file diff --git a/tashow-module/tashow-module-product/src/main/resources/mapper/product/ProdServiceAreasMapper.xml b/tashow-module/tashow-module-product/src/main/resources/mapper/product/ProdServiceAreasMapper.xml new file mode 100644 index 0000000..c9c6983 --- /dev/null +++ b/tashow-module/tashow-module-product/src/main/resources/mapper/product/ProdServiceAreasMapper.xml @@ -0,0 +1,12 @@ + + + + + + + \ No newline at end of file diff --git a/tashow-module/tashow-module-product/src/main/resources/mapper/product/ProdServiceOverAreaRulesMapper.xml b/tashow-module/tashow-module-product/src/main/resources/mapper/product/ProdServiceOverAreaRulesMapper.xml new file mode 100644 index 0000000..f9d6186 --- /dev/null +++ b/tashow-module/tashow-module-product/src/main/resources/mapper/product/ProdServiceOverAreaRulesMapper.xml @@ -0,0 +1,12 @@ + + + + + + + \ No newline at end of file diff --git a/tashow-module/tashow-module-product/src/main/resources/mapper/product/ProdTagsMapper.xml b/tashow-module/tashow-module-product/src/main/resources/mapper/product/ProdTagsMapper.xml new file mode 100644 index 0000000..31b83fe --- /dev/null +++ b/tashow-module/tashow-module-product/src/main/resources/mapper/product/ProdTagsMapper.xml @@ -0,0 +1,12 @@ + + + + + + + \ No newline at end of file diff --git a/tashow-module/tashow-module-product/src/main/resources/mapper/product/ProdWeightRangePricesMapper.xml b/tashow-module/tashow-module-product/src/main/resources/mapper/product/ProdWeightRangePricesMapper.xml new file mode 100644 index 0000000..645da2c --- /dev/null +++ b/tashow-module/tashow-module-product/src/main/resources/mapper/product/ProdWeightRangePricesMapper.xml @@ -0,0 +1,14 @@ + + + + + + + delete from tz_prod_weight_range_prices where prod_id = #{prodId} + + \ No newline at end of file diff --git a/tashow-module/tashow-module-product/src/main/resources/mapper/product/ProductOrderLimitMapper.xml b/tashow-module/tashow-module-product/src/main/resources/mapper/product/ProductOrderLimitMapper.xml new file mode 100644 index 0000000..080f880 --- /dev/null +++ b/tashow-module/tashow-module-product/src/main/resources/mapper/product/ProductOrderLimitMapper.xml @@ -0,0 +1,12 @@ + + + + + + + \ No newline at end of file diff --git a/tashow-module/tashow-module-product/src/main/resources/mapper/product/ShopDetailMapper.xml b/tashow-module/tashow-module-product/src/main/resources/mapper/product/ShopDetailMapper.xml new file mode 100644 index 0000000..e7edae8 --- /dev/null +++ b/tashow-module/tashow-module-product/src/main/resources/mapper/product/ShopDetailMapper.xml @@ -0,0 +1,12 @@ + + + + + + + \ No newline at end of file diff --git a/tashow-module/tashow-module-product/src/main/resources/mapper/product/SkuMapper.xml b/tashow-module/tashow-module-product/src/main/resources/mapper/product/SkuMapper.xml new file mode 100644 index 0000000..d1aed74 --- /dev/null +++ b/tashow-module/tashow-module-product/src/main/resources/mapper/product/SkuMapper.xml @@ -0,0 +1,121 @@ + + + + + + + + + + + + + + + + + + + + + + UPDATE tz_sku + SET + deleted = 1, + delete_time = now() + WHERE sku_id = #{skuId} + + + + + UPDATE tz_sku + SET deleted = 1, + delete_time = now() + WHERE sku_id IN + + #{id} + + + + + + + + + + + + UPDATE tz_sku + SET deleted = 0 + WHERE sku_id = #{skuId} + + + + + UPDATE tz_sku + SET deleted = 0 + WHERE sku_id IN + + #{id} + + + + + + \ No newline at end of file diff --git a/tashow-module/tashow-module-product/src/main/resources/mapper/product/SkuServiceDeliverMapper.xml b/tashow-module/tashow-module-product/src/main/resources/mapper/product/SkuServiceDeliverMapper.xml new file mode 100644 index 0000000..2cbaf7c --- /dev/null +++ b/tashow-module/tashow-module-product/src/main/resources/mapper/product/SkuServiceDeliverMapper.xml @@ -0,0 +1,14 @@ + + + + + + + delete from tz_sku_service_deliver where service_id = #{serviceId} + + \ No newline at end of file diff --git a/tashow-module/tashow-module-product/src/main/resources/mapper/product/SkuServiceDetailsMapper.xml b/tashow-module/tashow-module-product/src/main/resources/mapper/product/SkuServiceDetailsMapper.xml new file mode 100644 index 0000000..a74ad84 --- /dev/null +++ b/tashow-module/tashow-module-product/src/main/resources/mapper/product/SkuServiceDetailsMapper.xml @@ -0,0 +1,14 @@ + + + + + + + delete from tz_sku_service_details where service_id = #{serviceId} + + \ No newline at end of file diff --git a/tashow-module/tashow-module-product/src/main/resources/mapper/product/SkuServiceMaterialMapper.xml b/tashow-module/tashow-module-product/src/main/resources/mapper/product/SkuServiceMaterialMapper.xml new file mode 100644 index 0000000..ad0ec3c --- /dev/null +++ b/tashow-module/tashow-module-product/src/main/resources/mapper/product/SkuServiceMaterialMapper.xml @@ -0,0 +1,14 @@ + + + + + + + delete from tz_sku_service_material where service_id = #{serviceId} + + \ No newline at end of file diff --git a/tashow-module/tashow-module-product/src/main/resources/mapper/product/SkuServiceTransportMapper.xml b/tashow-module/tashow-module-product/src/main/resources/mapper/product/SkuServiceTransportMapper.xml new file mode 100644 index 0000000..a7aa6ec --- /dev/null +++ b/tashow-module/tashow-module-product/src/main/resources/mapper/product/SkuServiceTransportMapper.xml @@ -0,0 +1,14 @@ + + + + + + + delete from tz_sku_service_transport where service_id = #{serviceId} + + \ No newline at end of file diff --git a/tashow-module/tashow-module-product/src/main/resources/mapper/product/SkuServicesFormMapper.xml b/tashow-module/tashow-module-product/src/main/resources/mapper/product/SkuServicesFormMapper.xml new file mode 100644 index 0000000..cc2896a --- /dev/null +++ b/tashow-module/tashow-module-product/src/main/resources/mapper/product/SkuServicesFormMapper.xml @@ -0,0 +1,80 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/tashow-module/tashow-module-system/Dockerfile b/tashow-module/tashow-module-system/Dockerfile new file mode 100644 index 0000000..5bd5487 --- /dev/null +++ b/tashow-module/tashow-module-system/Dockerfile @@ -0,0 +1,19 @@ +## AdoptOpenJDK 停止发布 OpenJDK 二进制,而 Eclipse Temurin 是它的延伸,提供更好的稳定性 +## 感谢复旦核博士的建议!灰子哥,牛皮! +FROM eclipse-temurin:17-jre + +## 创建目录,并使用它作为工作目录 +RUN mkdir -p /home/java-work/system +WORKDIR /home/java-work/system +## 将后端项目的 Jar 文件,复制到镜像中 +COPY ./target/tashow-module-system.jar app.jar + +## 设置 TZ 时区 +## 设置 JAVA_OPTS 环境变量,可通过 docker run -e "JAVA_OPTS=" 进行覆盖 +ENV TZ=Asia/Shanghai JAVA_OPTS="-Xms512m -Xmx512m" + +## 暴露后端项目的 48080 端口 +EXPOSE 48081 + +## 启动后端项目 +CMD java ${JAVA_OPTS} -Djava.security.egd=file:/dev/./urandom -jar app.jar diff --git a/tashow-module/tashow-module-system/pom.xml b/tashow-module/tashow-module-system/pom.xml new file mode 100644 index 0000000..4326422 --- /dev/null +++ b/tashow-module/tashow-module-system/pom.xml @@ -0,0 +1,186 @@ + + + + com.tashow.cloud + tashow-module + ${revision} + + 4.0.0 + tashow-module-system + jar + + ${project.artifactId} + + system 模块下,我们放通用业务,支撑上层的核心业务。 + 例如说:用户、部门、权限、数据字典等等 + + + + + + com.tashow.cloud + tashow-framework-env + + + com.tashow.cloud + tashow-framework-mq + + + + com.tashow.cloud + tashow-system-api + ${revision} + + + com.tashow.cloud + tashow-infra-api + ${revision} + + + + + com.tashow.cloud + tashow-data-permission + + + com.tashow.cloud + tashow-framework-tenant + + + + + com.tashow.cloud + tashow-framework-security + + + + + com.tashow.cloud + tashow-data-mybatis + + + + com.tashow.cloud + tashow-data-redis + + + + + com.tashow.cloud + tashow-framework-rpc + + + + + com.alibaba.cloud + spring-cloud-starter-alibaba-nacos-discovery + + + + + com.alibaba.cloud + spring-cloud-starter-alibaba-nacos-config + + + + + com.tashow.cloud + tashow-framework-job + + + + + com.tashow.cloud + tashow-framework-mq + + + + + + + + + + + com.tashow.cloud + tashow-data-excel + + + + org.springframework.boot + spring-boot-starter-mail + + + + + com.tashow.cloud + tashow-framework-monitor + + + + + com.xingyuv + spring-boot-starter-justauth + + + + com.github.binarywang + wx-java-mp-spring-boot-starter + + + com.github.binarywang + wx-java-miniapp-spring-boot-starter + + + + com.xingyuv + spring-boot-starter-captcha-plus + + + + org.dromara.hutool + hutool-extra + + + junit + junit + + + org.springframework + spring-test + + + org.springframework.boot + spring-boot-test + + + + + + + + ${project.artifactId} + + + + org.springframework.boot + spring-boot-maven-plugin + ${spring.boot.version} + + + + repackage + + + + + + + + diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/SystemServerApplication.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/SystemServerApplication.java new file mode 100644 index 0000000..6c2e2b8 --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/SystemServerApplication.java @@ -0,0 +1,22 @@ +package com.tashow.cloud.system; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.cloud.openfeign.EnableFeignClients; +import org.springframework.scheduling.annotation.EnableAsync; + +/** + * 项目的启动类 + * @author 芋道源码 + */ +@SpringBootApplication +@EnableAsync // 开启异步 +//@EnableFeignClients(basePackages = "com.tashow.cloud.productapi.api") +public class SystemServerApplication { + + public static void main(String[] args) { + SpringApplication.run(SystemServerApplication.class, args); + System.out.println("系统启动成功"); + } + +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/api/dept/DeptApiImpl.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/api/dept/DeptApiImpl.java new file mode 100644 index 0000000..3cf6462 --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/api/dept/DeptApiImpl.java @@ -0,0 +1,53 @@ +package com.tashow.cloud.system.api.dept; + +import com.tashow.cloud.common.pojo.CommonResult; +import com.tashow.cloud.common.util.object.BeanUtils; +import com.tashow.cloud.systemapi.api.dept.DeptApi; +import com.tashow.cloud.systemapi.api.dept.dto.DeptRespDTO; +import com.tashow.cloud.system.dal.dataobject.dept.DeptDO; +import com.tashow.cloud.system.service.dept.DeptService; +import com.tashow.cloud.systemapi.api.dept.dto.DeptRespDTO; +import com.tashow.cloud.system.dal.dataobject.dept.DeptDO; +import com.tashow.cloud.system.service.dept.DeptService; +import org.springframework.context.annotation.Bean; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.RestController; + +import jakarta.annotation.Resource; +import java.util.Collection; +import java.util.List; + +import static com.tashow.cloud.common.pojo.CommonResult.success; + +@RestController // 提供 RESTful API 接口,给 Feign 调用 +@Validated +public class DeptApiImpl implements DeptApi { + + @Resource + private DeptService deptService; + + @Override + public CommonResult getDept(Long id) { + DeptDO dept = deptService.getDept(id); + return success(BeanUtils.toBean(dept, DeptRespDTO.class)); + } + + @Override + public CommonResult> getDeptList(Collection ids) { + List depts = deptService.getDeptList(ids); + return success(BeanUtils.toBean(depts, DeptRespDTO.class)); + } + + @Override + public CommonResult validateDeptList(Collection ids) { + deptService.validateDeptList(ids); + return success(true); + } + + @Override + public CommonResult> getChildDeptList(Long id) { + List depts = deptService.getChildDeptList(id); + return success(BeanUtils.toBean(depts, DeptRespDTO.class)); + } + +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/api/dept/PostApiImpl.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/api/dept/PostApiImpl.java new file mode 100644 index 0000000..dd14641 --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/api/dept/PostApiImpl.java @@ -0,0 +1,40 @@ +package com.tashow.cloud.system.api.dept; + +import com.tashow.cloud.common.pojo.CommonResult; +import com.tashow.cloud.common.util.object.BeanUtils; +import com.tashow.cloud.systemapi.api.dept.PostApi; +import com.tashow.cloud.systemapi.api.dept.dto.PostRespDTO; +import com.tashow.cloud.system.dal.dataobject.dept.PostDO; +import com.tashow.cloud.system.service.dept.PostService; +import com.tashow.cloud.systemapi.api.dept.dto.PostRespDTO; +import com.tashow.cloud.system.dal.dataobject.dept.PostDO; +import com.tashow.cloud.system.service.dept.PostService; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.RestController; + +import jakarta.annotation.Resource; +import java.util.Collection; +import java.util.List; + +import static com.tashow.cloud.common.pojo.CommonResult.success; + +@RestController // 提供 RESTful API 接口,给 Feign 调用 +@Validated +public class PostApiImpl implements PostApi { + + @Resource + private PostService postService; + + @Override + public CommonResult validPostList(Collection ids) { + postService.validatePostList(ids); + return success(true); + } + + @Override + public CommonResult> getPostList(Collection ids) { + List list = postService.getPostList(ids); + return success(BeanUtils.toBean(list, PostRespDTO.class)); + } + +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/api/dict/DictDataApiImpl.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/api/dict/DictDataApiImpl.java new file mode 100644 index 0000000..33f01e7 --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/api/dict/DictDataApiImpl.java @@ -0,0 +1,52 @@ +package com.tashow.cloud.system.api.dict; + +import com.tashow.cloud.common.pojo.CommonResult; +import com.tashow.cloud.common.util.object.BeanUtils; +import com.tashow.cloud.systemapi.api.dict.DictDataApi; +import com.tashow.cloud.systemapi.api.dict.dto.DictDataRespDTO; +import com.tashow.cloud.system.dal.dataobject.dict.DictDataDO; +import com.tashow.cloud.system.service.dict.DictDataService; +import com.tashow.cloud.systemapi.api.dict.dto.DictDataRespDTO; +import com.tashow.cloud.system.dal.dataobject.dict.DictDataDO; +import com.tashow.cloud.system.service.dict.DictDataService; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.RestController; + +import jakarta.annotation.Resource; +import java.util.Collection; +import java.util.List; + +import static com.tashow.cloud.common.pojo.CommonResult.success; + +@RestController // 提供 RESTful API 接口,给 Feign 调用 +@Validated +public class DictDataApiImpl implements DictDataApi { + + @Resource + private DictDataService dictDataService; + + @Override + public CommonResult validateDictDataList(String dictType, Collection values) { + dictDataService.validateDictDataList(dictType, values); + return success(true); + } + + @Override + public CommonResult getDictData(String dictType, String value) { + DictDataDO dictData = dictDataService.getDictData(dictType, value); + return success(BeanUtils.toBean(dictData, DictDataRespDTO.class)); + } + + @Override + public CommonResult parseDictData(String dictType, String label) { + DictDataDO dictData = dictDataService.parseDictData(dictType, label); + return success(BeanUtils.toBean(dictData, DictDataRespDTO.class)); + } + + @Override + public CommonResult> getDictDataList(String dictType) { + List list = dictDataService.getDictDataListByDictType(dictType); + return success(BeanUtils.toBean(list, DictDataRespDTO.class)); + } + +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/api/logger/LoginLogApiImpl.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/api/logger/LoginLogApiImpl.java new file mode 100644 index 0000000..4faa9a8 --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/api/logger/LoginLogApiImpl.java @@ -0,0 +1,26 @@ +package com.tashow.cloud.system.api.logger; +import com.tashow.cloud.common.pojo.CommonResult; +import com.tashow.cloud.systemapi.api.logger.LoginLogApi; +import com.tashow.cloud.systemapi.api.logger.dto.LoginLogCreateReqDTO; +import com.tashow.cloud.system.service.logger.LoginLogService; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.RestController; + +import jakarta.annotation.Resource; + +import static com.tashow.cloud.common.pojo.CommonResult.success; + +@RestController // 提供 RESTful API 接口,给 Feign 调用 +@Validated +public class LoginLogApiImpl implements LoginLogApi { + + @Resource + private LoginLogService loginLogService; + + @Override + public CommonResult createLoginLog(LoginLogCreateReqDTO reqDTO) { + loginLogService.createLoginLog(reqDTO); + return success(true); + } + +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/api/logger/OperateLogApiImpl.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/api/logger/OperateLogApiImpl.java new file mode 100644 index 0000000..9c877ad --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/api/logger/OperateLogApiImpl.java @@ -0,0 +1,42 @@ +package com.tashow.cloud.system.api.logger; + +import com.tashow.cloud.common.pojo.CommonResult; +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.common.util.object.BeanUtils; +import com.tashow.cloud.systemapi.api.logger.OperateLogApi; +import com.tashow.cloud.systemapi.api.logger.dto.OperateLogCreateReqDTO; +import com.tashow.cloud.systemapi.api.logger.dto.OperateLogPageReqDTO; +import com.tashow.cloud.systemapi.api.logger.dto.OperateLogRespDTO; +import com.tashow.cloud.system.dal.dataobject.logger.OperateLogDO; +import com.tashow.cloud.system.service.logger.OperateLogService; +import com.tashow.cloud.systemapi.api.logger.dto.OperateLogCreateReqDTO; +import com.tashow.cloud.systemapi.api.logger.dto.OperateLogPageReqDTO; +import com.tashow.cloud.systemapi.api.logger.dto.OperateLogRespDTO; +import com.tashow.cloud.system.dal.dataobject.logger.OperateLogDO; +import com.tashow.cloud.system.service.logger.OperateLogService; +import jakarta.annotation.Resource; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.RestController; + +import static com.tashow.cloud.common.pojo.CommonResult.success; + +@RestController // 提供 RESTful API 接口,给 Feign 调用 +@Validated +public class OperateLogApiImpl implements OperateLogApi { + + @Resource + private OperateLogService operateLogService; + + @Override + public CommonResult createOperateLog(OperateLogCreateReqDTO createReqDTO) { + operateLogService.createOperateLog(createReqDTO); + return success(true); + } + + @Override + public CommonResult> getOperateLogPage(OperateLogPageReqDTO pageReqDTO) { + PageResult operateLogPage = operateLogService.getOperateLogPage(pageReqDTO); + return success(BeanUtils.toBean(operateLogPage, OperateLogRespDTO.class)); + } + +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/api/mail/MailSendApiImpl.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/api/mail/MailSendApiImpl.java new file mode 100644 index 0000000..7b4aef8 --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/api/mail/MailSendApiImpl.java @@ -0,0 +1,35 @@ +package com.tashow.cloud.system.api.mail; + +import com.tashow.cloud.common.pojo.CommonResult; +import com.tashow.cloud.systemapi.api.mail.MailSendApi; +import com.tashow.cloud.systemapi.api.mail.dto.MailSendSingleToUserReqDTO; +import com.tashow.cloud.system.service.mail.MailSendService; +import com.tashow.cloud.systemapi.api.mail.dto.MailSendSingleToUserReqDTO; +import com.tashow.cloud.system.service.mail.MailSendService; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.RestController; + +import jakarta.annotation.Resource; + +import static com.tashow.cloud.common.pojo.CommonResult.success; + +@RestController // 提供 RESTful API 接口,给 Feign 调用 +@Validated +public class MailSendApiImpl implements MailSendApi { + + @Resource + private MailSendService mailSendService; + + @Override + public CommonResult sendSingleMailToAdmin(MailSendSingleToUserReqDTO reqDTO) { + return success(mailSendService.sendSingleMailToAdmin(reqDTO.getMail(), reqDTO.getUserId(), + reqDTO.getTemplateCode(), reqDTO.getTemplateParams())); + } + + @Override + public CommonResult sendSingleMailToMember(MailSendSingleToUserReqDTO reqDTO) { + return success(mailSendService.sendSingleMailToMember(reqDTO.getMail(), reqDTO.getUserId(), + reqDTO.getTemplateCode(), reqDTO.getTemplateParams())); + } + +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/api/notify/NotifyMessageSendApiImpl.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/api/notify/NotifyMessageSendApiImpl.java new file mode 100644 index 0000000..32b1f07 --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/api/notify/NotifyMessageSendApiImpl.java @@ -0,0 +1,35 @@ +package com.tashow.cloud.system.api.notify; + +import com.tashow.cloud.common.pojo.CommonResult; +import com.tashow.cloud.systemapi.api.notify.NotifyMessageSendApi; +import com.tashow.cloud.systemapi.api.notify.dto.NotifySendSingleToUserReqDTO; +import com.tashow.cloud.system.service.notify.NotifySendService; +import com.tashow.cloud.systemapi.api.notify.dto.NotifySendSingleToUserReqDTO; +import com.tashow.cloud.system.service.notify.NotifySendService; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.RestController; + +import jakarta.annotation.Resource; + +import static com.tashow.cloud.common.pojo.CommonResult.success; + +@RestController // 提供 RESTful API 接口,给 Feign 调用 +@Validated +public class NotifyMessageSendApiImpl implements NotifyMessageSendApi { + + @Resource + private NotifySendService notifySendService; + + @Override + public CommonResult sendSingleMessageToAdmin(NotifySendSingleToUserReqDTO reqDTO) { + return success(notifySendService.sendSingleNotifyToAdmin(reqDTO.getUserId(), + reqDTO.getTemplateCode(), reqDTO.getTemplateParams())); + } + + @Override + public CommonResult sendSingleMessageToMember(NotifySendSingleToUserReqDTO reqDTO) { + return success(notifySendService.sendSingleNotifyToMember(reqDTO.getUserId(), + reqDTO.getTemplateCode(), reqDTO.getTemplateParams())); + } + +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/api/oauth2/OAuth2TokenApiImpl.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/api/oauth2/OAuth2TokenApiImpl.java new file mode 100644 index 0000000..fdac1ba --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/api/oauth2/OAuth2TokenApiImpl.java @@ -0,0 +1,50 @@ +package com.tashow.cloud.system.api.oauth2; + +import com.tashow.cloud.common.pojo.CommonResult; +import com.tashow.cloud.common.util.object.BeanUtils; +import com.tashow.cloud.systemapi.api.oauth2.OAuth2TokenApi; +import com.tashow.cloud.systemapi.api.oauth2.dto.OAuth2AccessTokenCheckRespDTO; +import com.tashow.cloud.systemapi.api.oauth2.dto.OAuth2AccessTokenCreateReqDTO; +import com.tashow.cloud.systemapi.api.oauth2.dto.OAuth2AccessTokenRespDTO; +import com.tashow.cloud.system.dal.dataobject.oauth2.OAuth2AccessTokenDO; +import com.tashow.cloud.system.service.oauth2.OAuth2TokenService; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.RestController; + +import jakarta.annotation.Resource; + +import static com.tashow.cloud.common.pojo.CommonResult.success; + +@RestController // 提供 RESTful API 接口,给 Feign 调用 +@Validated +public class OAuth2TokenApiImpl implements OAuth2TokenApi { + + @Resource + private OAuth2TokenService oauth2TokenService; + + @Override + public CommonResult createAccessToken(OAuth2AccessTokenCreateReqDTO reqDTO) { + OAuth2AccessTokenDO accessTokenDO = oauth2TokenService.createAccessToken( + reqDTO.getUserId(), reqDTO.getUserType(), reqDTO.getClientId(), reqDTO.getScopes()); + return success(BeanUtils.toBean(accessTokenDO, OAuth2AccessTokenRespDTO.class)); + } + + @Override + public CommonResult checkAccessToken(String accessToken) { + OAuth2AccessTokenDO accessTokenDO = oauth2TokenService.checkAccessToken(accessToken); + return success(BeanUtils.toBean(accessTokenDO, OAuth2AccessTokenCheckRespDTO.class)); + } + + @Override + public CommonResult removeAccessToken(String accessToken) { + OAuth2AccessTokenDO accessTokenDO = oauth2TokenService.removeAccessToken(accessToken); + return success(BeanUtils.toBean(accessTokenDO, OAuth2AccessTokenRespDTO.class)); + } + + @Override + public CommonResult refreshAccessToken(String refreshToken, String clientId) { + OAuth2AccessTokenDO accessTokenDO = oauth2TokenService.refreshAccessToken(refreshToken, clientId); + return success(BeanUtils.toBean(accessTokenDO, OAuth2AccessTokenRespDTO.class)); + } + +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/api/permission/PermissionApiImpl.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/api/permission/PermissionApiImpl.java new file mode 100644 index 0000000..f3b6b05 --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/api/permission/PermissionApiImpl.java @@ -0,0 +1,45 @@ +package com.tashow.cloud.system.api.permission; + +import com.tashow.cloud.common.pojo.CommonResult; +import com.tashow.cloud.systemapi.api.permission.PermissionApi; +import com.tashow.cloud.systemapi.api.permission.dto.DeptDataPermissionRespDTO; +import com.tashow.cloud.system.service.permission.PermissionService; +import com.tashow.cloud.systemapi.api.permission.dto.DeptDataPermissionRespDTO; +import com.tashow.cloud.system.service.permission.PermissionService; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.RestController; + +import jakarta.annotation.Resource; +import java.util.Collection; +import java.util.Set; + +import static com.tashow.cloud.common.pojo.CommonResult.success; + +@RestController // 提供 RESTful API 接口,给 Feign 调用 +@Validated +public class PermissionApiImpl implements PermissionApi { + + @Resource + private PermissionService permissionService; + + @Override + public CommonResult> getUserRoleIdListByRoleIds(Collection roleIds) { + return success(permissionService.getUserRoleIdListByRoleId(roleIds)); + } + + @Override + public CommonResult hasAnyPermissions(Long userId, String... permissions) { + return success(permissionService.hasAnyPermissions(userId, permissions)); + } + + @Override + public CommonResult hasAnyRoles(Long userId, String... roles) { + return success(permissionService.hasAnyRoles(userId, roles)); + } + + @Override + public CommonResult getDeptDataPermission(Long userId) { + return success(permissionService.getDeptDataPermission(userId)); + } + +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/api/permission/RoleApiImpl.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/api/permission/RoleApiImpl.java new file mode 100644 index 0000000..1b653a6 --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/api/permission/RoleApiImpl.java @@ -0,0 +1,27 @@ +package com.tashow.cloud.system.api.permission; + +import com.tashow.cloud.common.pojo.CommonResult; +import com.tashow.cloud.system.service.permission.RoleService; +import com.tashow.cloud.system.service.permission.RoleService; +import com.tashow.cloud.systemapi.api.permission.RoleApi; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.RestController; + +import jakarta.annotation.Resource; +import java.util.Collection; + +import static com.tashow.cloud.common.pojo.CommonResult.success; + +@RestController // 提供 RESTful API 接口,给 Feign 调用 +@Validated +public class RoleApiImpl implements RoleApi { + + @Resource + private RoleService roleService; + + @Override + public CommonResult validRoleList(Collection ids) { + roleService.validateRoleList(ids); + return success(true); + } +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/api/sms/SmsCodeApiImpl.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/api/sms/SmsCodeApiImpl.java new file mode 100644 index 0000000..37db282 --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/api/sms/SmsCodeApiImpl.java @@ -0,0 +1,45 @@ +package com.tashow.cloud.system.api.sms; + +import com.tashow.cloud.common.pojo.CommonResult; +import com.tashow.cloud.systemapi.api.sms.SmsCodeApi; +import com.tashow.cloud.systemapi.api.sms.dto.code.SmsCodeSendReqDTO; +import com.tashow.cloud.systemapi.api.sms.dto.code.SmsCodeUseReqDTO; +import com.tashow.cloud.systemapi.api.sms.dto.code.SmsCodeValidateReqDTO; +import com.tashow.cloud.system.service.sms.SmsCodeService; +import com.tashow.cloud.systemapi.api.sms.dto.code.SmsCodeSendReqDTO; +import com.tashow.cloud.systemapi.api.sms.dto.code.SmsCodeUseReqDTO; +import com.tashow.cloud.systemapi.api.sms.dto.code.SmsCodeValidateReqDTO; +import com.tashow.cloud.system.service.sms.SmsCodeService; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.RestController; + +import jakarta.annotation.Resource; + +import static com.tashow.cloud.common.pojo.CommonResult.success; + +@RestController // 提供 RESTful API 接口,给 Feign 调用 +@Validated +public class SmsCodeApiImpl implements SmsCodeApi { + + @Resource + private SmsCodeService smsCodeService; + + @Override + public CommonResult sendSmsCode(SmsCodeSendReqDTO reqDTO) { + smsCodeService.sendSmsCode(reqDTO); + return success(true); + } + + @Override + public CommonResult useSmsCode(SmsCodeUseReqDTO reqDTO) { + smsCodeService.useSmsCode(reqDTO); + return success(true); + } + + @Override + public CommonResult validateSmsCode(SmsCodeValidateReqDTO reqDTO) { + smsCodeService.validateSmsCode(reqDTO); + return success(true); + } + +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/api/sms/SmsSendApiImpl.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/api/sms/SmsSendApiImpl.java new file mode 100644 index 0000000..37c5c9e --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/api/sms/SmsSendApiImpl.java @@ -0,0 +1,35 @@ +package com.tashow.cloud.system.api.sms; + +import com.tashow.cloud.common.pojo.CommonResult; +import com.tashow.cloud.systemapi.api.sms.SmsSendApi; +import com.tashow.cloud.systemapi.api.sms.dto.send.SmsSendSingleToUserReqDTO; +import com.tashow.cloud.system.service.sms.SmsSendService; +import com.tashow.cloud.systemapi.api.sms.dto.send.SmsSendSingleToUserReqDTO; +import com.tashow.cloud.system.service.sms.SmsSendService; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.RestController; + +import jakarta.annotation.Resource; + +import static com.tashow.cloud.common.pojo.CommonResult.success; + +@RestController // 提供 RESTful API 接口,给 Feign 调用 +@Validated +public class SmsSendApiImpl implements SmsSendApi { + + @Resource + private SmsSendService smsSendService; + + @Override + public CommonResult sendSingleSmsToAdmin(SmsSendSingleToUserReqDTO reqDTO) { + return success(smsSendService.sendSingleSmsToAdmin(reqDTO.getMobile(), reqDTO.getUserId(), + reqDTO.getTemplateCode(), reqDTO.getTemplateParams())); + } + + @Override + public CommonResult sendSingleSmsToMember(SmsSendSingleToUserReqDTO reqDTO) { + return success(smsSendService.sendSingleSmsToMember(reqDTO.getMobile(), reqDTO.getUserId(), + reqDTO.getTemplateCode(), reqDTO.getTemplateParams())); + } + +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/api/social/SocialClientApiImpl.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/api/social/SocialClientApiImpl.java new file mode 100644 index 0000000..ce5682c --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/api/social/SocialClientApiImpl.java @@ -0,0 +1,103 @@ +package com.tashow.cloud.system.api.social; + +import cn.binarywang.wx.miniapp.bean.WxMaPhoneNumberInfo; +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.util.ObjUtil; +import cn.hutool.core.util.StrUtil; +import com.tashow.cloud.common.pojo.CommonResult; +import com.tashow.cloud.common.util.object.BeanUtils; +import com.tashow.cloud.systemapi.api.social.SocialClientApi; +import com.tashow.cloud.systemapi.api.social.dto.*; +import com.tashow.cloud.systemapi.enums.social.SocialTypeEnum; +import com.tashow.cloud.system.service.social.SocialClientService; +import com.tashow.cloud.system.service.social.SocialUserService; +import com.tashow.cloud.systemapi.enums.social.SocialTypeEnum; +import com.tashow.cloud.system.service.social.SocialClientService; +import com.tashow.cloud.system.service.social.SocialUserService; +import lombok.extern.slf4j.Slf4j; +import me.chanjar.weixin.common.bean.WxJsapiSignature; +import me.chanjar.weixin.common.bean.subscribemsg.TemplateInfo; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.RestController; + +import jakarta.annotation.Resource; + +import java.util.List; + +import static cn.hutool.core.collection.CollUtil.findOne; +import static com.tashow.cloud.common.pojo.CommonResult.success; +import static com.tashow.cloud.common.util.collection.CollectionUtils.convertList; + +/** + * 社交应用的 API 实现类 + * + * @author 芋道源码 + */ +@RestController +@Validated +@Slf4j +public class SocialClientApiImpl implements SocialClientApi { + + @Resource + private SocialClientService socialClientService; + @Resource + private SocialUserService socialUserService; + + @Override + public CommonResult getAuthorizeUrl(Integer socialType, Integer userType, String redirectUri) { + return success(socialClientService.getAuthorizeUrl(socialType, userType, redirectUri)); + } + + @Override + public CommonResult createWxMpJsapiSignature(Integer userType, String url) { + WxJsapiSignature signature = socialClientService.createWxMpJsapiSignature(userType, url); + return success(BeanUtils.toBean(signature, SocialWxJsapiSignatureRespDTO.class)); + } + + @Override + public CommonResult getWxMaPhoneNumberInfo(Integer userType, String phoneCode) { + WxMaPhoneNumberInfo info = socialClientService.getWxMaPhoneNumberInfo(userType, phoneCode); + return success(BeanUtils.toBean(info, SocialWxPhoneNumberInfoRespDTO.class)); + } + + @Override + public CommonResult getWxaQrcode(SocialWxQrcodeReqDTO reqVO) { + return success(socialClientService.getWxaQrcode(reqVO)); + } + + @Override + public CommonResult> getWxaSubscribeTemplateList(Integer userType) { + List list = socialClientService.getSubscribeTemplateList(userType); + return success(convertList(list, item -> BeanUtils.toBean(item, SocialWxaSubscribeTemplateRespDTO.class).setId(item.getPriTmplId()))); + } + + @Override + public CommonResult sendWxaSubscribeMessage(SocialWxaSubscribeMessageSendReqDTO reqDTO) { + // 1.1 获得订阅模版列表 + List templateList = socialClientService.getSubscribeTemplateList(reqDTO.getUserType()); + if (CollUtil.isEmpty(templateList)) { + log.warn("[sendSubscribeMessage][reqDTO({}) 发送订阅消息失败,原因:没有找到订阅模板]", reqDTO); + return success(false); + } + // 1.2 获得需要使用的模版 + TemplateInfo template = findOne(templateList, item -> + ObjUtil.equal(item.getTitle(), reqDTO.getTemplateTitle())); + if (template == null) { + log.warn("[sendWxaSubscribeMessage][reqDTO({}) 发送订阅消息失败,原因:没有找到订阅模板]", reqDTO); + return success(false); + } + + // 2. 获得社交用户 + SocialUserRespDTO socialUser = socialUserService.getSocialUserByUserId(reqDTO.getUserType(), reqDTO.getUserId(), + SocialTypeEnum.WECHAT_MINI_APP.getType()); + if (StrUtil.isBlankIfStr(socialUser.getOpenid())) { + log.warn("[sendWxaSubscribeMessage][reqDTO({}) 发送订阅消息失败,原因:会员 openid 缺失]", reqDTO); + return success(false); + } + + // 3. 发送订阅消息 + socialClientService.sendSubscribeMessage(reqDTO, template.getPriTmplId(), socialUser.getOpenid()); + return success(true); + } + +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/api/social/SocialUserApiImpl.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/api/social/SocialUserApiImpl.java new file mode 100644 index 0000000..7285724 --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/api/social/SocialUserApiImpl.java @@ -0,0 +1,49 @@ +package com.tashow.cloud.system.api.social; + +import com.tashow.cloud.common.pojo.CommonResult; +import com.tashow.cloud.systemapi.api.social.SocialUserApi; +import com.tashow.cloud.systemapi.api.social.dto.SocialUserBindReqDTO; +import com.tashow.cloud.systemapi.api.social.dto.SocialUserRespDTO; +import com.tashow.cloud.systemapi.api.social.dto.SocialUserUnbindReqDTO; +import com.tashow.cloud.system.service.social.SocialUserService; +import com.tashow.cloud.systemapi.api.social.dto.SocialUserBindReqDTO; +import com.tashow.cloud.systemapi.api.social.dto.SocialUserRespDTO; +import com.tashow.cloud.systemapi.api.social.dto.SocialUserUnbindReqDTO; +import com.tashow.cloud.system.service.social.SocialUserService; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.RestController; + +import jakarta.annotation.Resource; + +import static com.tashow.cloud.common.pojo.CommonResult.success; + +@RestController // 提供 RESTful API 接口,给 Feign 调用 +@Validated +public class SocialUserApiImpl implements SocialUserApi { + + @Resource + private SocialUserService socialUserService; + + @Override + public CommonResult bindSocialUser(SocialUserBindReqDTO reqDTO) { + return success(socialUserService.bindSocialUser(reqDTO)); + } + + @Override + public CommonResult unbindSocialUser(SocialUserUnbindReqDTO reqDTO) { + socialUserService.unbindSocialUser(reqDTO.getUserId(), reqDTO.getUserType(), + reqDTO.getSocialType(), reqDTO.getOpenid()); + return success(true); + } + + @Override + public CommonResult getSocialUserByUserId(Integer userType, Long userId, Integer socialType) { + return success(socialUserService.getSocialUserByUserId(userType, userId, socialType)); + } + + @Override + public CommonResult getSocialUserByCode(Integer userType, Integer socialType, String code, String state) { + return success(socialUserService.getSocialUserByCode(userType, socialType, code, state)); + } + +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/api/tenant/TenantApiImpl.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/api/tenant/TenantApiImpl.java new file mode 100644 index 0000000..5305a3d --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/api/tenant/TenantApiImpl.java @@ -0,0 +1,33 @@ +package com.tashow.cloud.system.api.tenant; + +import com.tashow.cloud.common.pojo.CommonResult; +import com.tashow.cloud.system.service.tenant.TenantService; +import com.tashow.cloud.system.service.tenant.TenantService; +import com.tashow.cloud.systemapi.api.tenant.TenantApi; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.RestController; + +import jakarta.annotation.Resource; +import java.util.List; + +import static com.tashow.cloud.common.pojo.CommonResult.success; + +@RestController // 提供 RESTful API 接口,给 Feign 调用 +@Validated +public class TenantApiImpl implements TenantApi { + + @Resource + private TenantService tenantService; + + @Override + public CommonResult> getTenantIdList() { + return success(tenantService.getTenantIdList()); + } + + @Override + public CommonResult validTenant(Long id) { + tenantService.validTenant(id); + return success(true); + } + +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/api/user/AdminUserApiImpl.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/api/user/AdminUserApiImpl.java new file mode 100644 index 0000000..851a9da --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/api/user/AdminUserApiImpl.java @@ -0,0 +1,92 @@ +package com.tashow.cloud.system.api.user; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.util.ObjUtil; +import com.tashow.cloud.common.pojo.CommonResult; +import com.tashow.cloud.common.util.object.BeanUtils; +import com.tashow.cloud.permission.core.annotation.DataPermission; +import com.tashow.cloud.systemapi.api.user.AdminUserApi; +import com.tashow.cloud.systemapi.api.user.dto.AdminUserRespDTO; +import com.tashow.cloud.system.dal.dataobject.dept.DeptDO; +import com.tashow.cloud.system.dal.dataobject.user.AdminUserDO; +import com.tashow.cloud.system.service.dept.DeptService; +import com.tashow.cloud.system.service.user.AdminUserService; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.RestController; + +import jakarta.annotation.Resource; + +import java.util.*; + +import static com.tashow.cloud.common.pojo.CommonResult.success; +import static com.tashow.cloud.common.util.collection.CollectionUtils.convertSet; + +@RestController // 提供 RESTful API 接口,给 Feign 调用 +@Validated +public class AdminUserApiImpl implements AdminUserApi { + + @Resource + private AdminUserService userService; + @Resource + private DeptService deptService; + + @Override + public CommonResult getUser(Long id) { + AdminUserDO user = userService.getUser(id); + return success(BeanUtils.toBean(user, AdminUserRespDTO.class)); + } + + @Override + public CommonResult> getUserListBySubordinate(Long id) { + // 1.1 获取用户负责的部门 + AdminUserDO user = userService.getUser(id); + if (user == null) { + return success(Collections.emptyList()); + } + ArrayList deptIds = new ArrayList<>(); + DeptDO dept = deptService.getDept(user.getDeptId()); + if (dept == null) { + return success(Collections.emptyList()); + } + if (ObjUtil.notEqual(dept.getLeaderUserId(), id)) { // 校验为负责人 + return success(Collections.emptyList()); + } + deptIds.add(dept.getId()); + // 1.2 获取所有子部门 + List childDeptList = deptService.getChildDeptList(dept.getId()); + if (CollUtil.isNotEmpty(childDeptList)) { + deptIds.addAll(convertSet(childDeptList, DeptDO::getId)); + } + + // 2. 获取部门对应的用户信息 + List users = userService.getUserListByDeptIds(deptIds); + users.removeIf(item -> ObjUtil.equal(item.getId(), id)); // 排除自己 + return success(BeanUtils.toBean(users, AdminUserRespDTO.class)); + } + + @Override + @DataPermission(enable = false) // 禁用数据权限。原因是,一般基于指定 id 的 API 查询,都是数据拼接为主 + public CommonResult> getUserList(Collection ids) { + List users = userService.getUserList(ids); + return success(BeanUtils.toBean(users, AdminUserRespDTO.class)); + } + + @Override + public CommonResult> getUserListByDeptIds(Collection deptIds) { + List users = userService.getUserListByDeptIds(deptIds); + return success(BeanUtils.toBean(users, AdminUserRespDTO.class)); + } + + @Override + public CommonResult> getUserListByPostIds(Collection postIds) { + List users = userService.getUserListByPostIds(postIds); + return success(BeanUtils.toBean(users, AdminUserRespDTO.class)); + } + + @Override + public CommonResult validateUserList(Collection ids) { + userService.validateUserList(ids); + return success(true); + } + +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/auth/AuthController.http b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/auth/AuthController.http new file mode 100644 index 0000000..f42dfcd --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/auth/AuthController.http @@ -0,0 +1,33 @@ +### 请求 /login 接口 => 成功 +POST {{baseUrl}}/system/auth/login +Content-Type: application/json +tenant-id: {{adminTenantId}} +tag: Yunai.local + +{ + "username": "admin", + "password": "admin123", + "uuid": "3acd87a09a4f48fb9118333780e94883", + "code": "1024" +} + +### 请求 /login 接口 => 成功(无验证码) +POST {{baseUrl}}/system/auth/login +Content-Type: application/json +tenant-id: {{adminTenantId}} + +{ + "username": "admin", + "password": "admin123" +} + +### 请求 /get-permission-info 接口 => 成功 +GET {{baseUrl}}/system/auth/get-permission-info +Authorization: Bearer {{token}} +tenant-id: {{adminTenantId}} + +### 请求 /list-menus 接口 => 成功 +GET {{baseUrl}}/system/list-menus +Authorization: Bearer {{token}} +#Authorization: Bearer a6aa7714a2e44c95aaa8a2c5adc2a67a +tenant-id: {{adminTenantId}} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/auth/AuthController.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/auth/AuthController.java new file mode 100644 index 0000000..95d2498 --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/auth/AuthController.java @@ -0,0 +1,170 @@ +package com.tashow.cloud.system.controller.admin.auth; + +import static com.tashow.cloud.common.pojo.CommonResult.success; +import static com.tashow.cloud.common.util.collection.CollectionUtils.convertSet; +import static com.tashow.cloud.web.web.core.util.WebFrameworkUtils.getLoginUserId; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.util.StrUtil; +import com.tashow.cloud.common.enums.CommonStatusEnum; +import com.tashow.cloud.common.enums.UserTypeEnum; +import com.tashow.cloud.common.pojo.CommonResult; +import com.tashow.cloud.security.security.config.SecurityProperties; +import com.tashow.cloud.security.security.core.util.SecurityFrameworkUtils; +import com.tashow.cloud.system.controller.admin.auth.vo.AuthLoginReqVO; +import com.tashow.cloud.system.controller.admin.auth.vo.AuthLoginRespVO; +import com.tashow.cloud.system.controller.admin.auth.vo.AuthPermissionInfoRespVO; +import com.tashow.cloud.system.controller.admin.auth.vo.AuthRegisterReqVO; +import com.tashow.cloud.system.controller.admin.auth.vo.AuthResetPasswordReqVO; +import com.tashow.cloud.system.controller.admin.auth.vo.AuthSmsLoginReqVO; +import com.tashow.cloud.system.controller.admin.auth.vo.AuthSmsSendReqVO; +import com.tashow.cloud.system.controller.admin.auth.vo.AuthSocialLoginReqVO; +import com.tashow.cloud.system.convert.auth.AuthConvert; +import com.tashow.cloud.system.dal.dataobject.permission.MenuDO; +import com.tashow.cloud.system.dal.dataobject.permission.RoleDO; +import com.tashow.cloud.system.dal.dataobject.user.AdminUserDO; +import com.tashow.cloud.system.service.auth.AdminAuthService; +import com.tashow.cloud.system.service.permission.MenuService; +import com.tashow.cloud.system.service.permission.PermissionService; +import com.tashow.cloud.system.service.permission.RoleService; +import com.tashow.cloud.system.service.social.SocialClientService; +import com.tashow.cloud.system.service.user.AdminUserService; +import com.tashow.cloud.systemapi.enums.logger.LoginLogTypeEnum; +import jakarta.annotation.Resource; +import jakarta.annotation.security.PermitAll; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.validation.Valid; +import java.util.Collections; +import java.util.List; +import java.util.Set; +import lombok.extern.slf4j.Slf4j; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +/** 管理后台 - 认证 */ +@RestController +@RequestMapping("/system/auth") +@Validated +@Slf4j +public class AuthController { + + @Resource private AdminAuthService authService; + @Resource private AdminUserService userService; + @Resource private RoleService roleService; + @Resource private MenuService menuService; + @Resource private PermissionService permissionService; + @Resource private SocialClientService socialClientService; + + @Resource private SecurityProperties securityProperties; + + /** 使用账号密码登录 */ + @PostMapping("/login") + @PermitAll + public CommonResult login(@RequestBody @Valid AuthLoginReqVO reqVO) { + return success(authService.login(reqVO)); + } + + /** 登出系统 */ + @PostMapping("/logout") + @PermitAll + public CommonResult logout(HttpServletRequest request) { + String token = + SecurityFrameworkUtils.obtainAuthorization( + request, securityProperties.getTokenHeader(), securityProperties.getTokenParameter()); + if (StrUtil.isNotBlank(token)) { + authService.logout(token, LoginLogTypeEnum.LOGOUT_SELF.getType()); + } + return success(true); + } + + /** 刷新令牌 */ + @PostMapping("/refresh-token") + @PermitAll + public CommonResult refreshToken( + @RequestParam("refreshToken") String refreshToken) { + return success(authService.refreshToken(refreshToken)); + } + + /** 获取登录用户的权限信息 */ + @GetMapping("/get-permission-info") + public CommonResult getPermissionInfo() { + // 1.1 获得用户信息 + AdminUserDO user = userService.getUser(getLoginUserId()); + if (user == null) { + return success(null); + } + + // 1.2 获得角色列表 + Set roleIds = permissionService.getUserRoleIdListByUserId(getLoginUserId()); + if (CollUtil.isEmpty(roleIds)) { + return success( + AuthConvert.INSTANCE.convert(user, Collections.emptyList(), Collections.emptyList())); + } + List roles = roleService.getRoleList(roleIds); + roles.removeIf( + role -> !CommonStatusEnum.ENABLE.getStatus().equals(role.getStatus())); // 移除禁用的角色 + + // 1.3 获得菜单列表 + Set menuIds = permissionService.getRoleMenuListByRoleId(convertSet(roles, RoleDO::getId)); + List menuList = menuService.getMenuList(menuIds); + menuList = menuService.filterDisableMenus(menuList); + + // 2. 拼接结果返回 + return success(AuthConvert.INSTANCE.convert(user, roles, menuList)); + } + + /** 注册用户 */ + @PostMapping("/register") + @PermitAll + public CommonResult register( + @RequestBody @Valid AuthRegisterReqVO registerReqVO) { + return success(authService.register(registerReqVO)); + } + + // ========== 短信登录相关 ========== + /** 使用短信验证码登录 */ + @PostMapping("/sms-login") + @PermitAll + public CommonResult smsLogin(@RequestBody @Valid AuthSmsLoginReqVO reqVO) { + return success(authService.smsLogin(reqVO)); + } + + /** 发送手机验证码 */ + @PostMapping("/send-sms-code") + @PermitAll + public CommonResult sendLoginSmsCode(@RequestBody @Valid AuthSmsSendReqVO reqVO) { + authService.sendSmsCode(reqVO); + return success(true); + } + + /** 重置密码 */ + @PostMapping("/reset-password") + @PermitAll + public CommonResult resetPassword(@RequestBody @Valid AuthResetPasswordReqVO reqVO) { + authService.resetPassword(reqVO); + return success(true); + } + + // ========== 社交登录相关 ========== + /** 社交授权的跳转 */ + @GetMapping("/social-auth-redirect") + @PermitAll + public CommonResult socialLogin( + @RequestParam("type") Integer type, @RequestParam("redirectUri") String redirectUri) { + return success( + socialClientService.getAuthorizeUrl(type, UserTypeEnum.ADMIN.getValue(), redirectUri)); + } + + /** 社交快捷登录,使用 code 授权码", description = "适合未登录的用户,但是社交账号已绑定用户 */ + @PostMapping("/social-login") + @PermitAll + public CommonResult socialQuickLogin( + @RequestBody @Valid AuthSocialLoginReqVO reqVO) { + return success(authService.socialLogin(reqVO)); + } +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/auth/vo/AuthLoginReqVO.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/auth/vo/AuthLoginReqVO.java new file mode 100644 index 0000000..399b91c --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/auth/vo/AuthLoginReqVO.java @@ -0,0 +1,54 @@ +package com.tashow.cloud.system.controller.admin.auth.vo; + +import cn.hutool.core.util.StrUtil; +import com.tashow.cloud.common.validation.InEnum; +import com.tashow.cloud.systemapi.enums.social.SocialTypeEnum; +import jakarta.validation.constraints.AssertTrue; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.Pattern; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +import org.hibernate.validator.constraints.Length; + +/** 管理后台 - 账号密码登录 Request VO,如果登录并绑定社交用户,需要传递 social 开头的参数 */ +@Data +@NoArgsConstructor +@AllArgsConstructor +@Builder +public class AuthLoginReqVO extends CaptchaVerificationReqVO { + + /** 账号" */ + @NotEmpty(message = "登录账号不能为空") + @Length(min = 4, max = 16, message = "账号长度为 4-16 位") + @Pattern(regexp = "^[A-Za-z0-9]+$", message = "账号格式为数字以及字母") + private String username; + + /** 密码" */ + @NotEmpty(message = "密码不能为空") + @Length(min = 4, max = 16, message = "密码长度为 4-16 位") + private String password; + + // ========== 绑定社交登录时,需要传递如下参数 ========== + + /** 社交平台的类型,参见 SocialTypeEnum 枚举值" */ + @InEnum(SocialTypeEnum.class) + private Integer socialType; + + /** 授权码" */ + private String socialCode; + + /** state" */ + private String socialState; + + @AssertTrue(message = "授权码不能为空") + public boolean isSocialCodeValid() { + return socialType == null || StrUtil.isNotEmpty(socialCode); + } + + @AssertTrue(message = "授权 state 不能为空") + public boolean isSocialState() { + return socialType == null || StrUtil.isNotEmpty(socialState); + } +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/auth/vo/AuthLoginRespVO.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/auth/vo/AuthLoginRespVO.java new file mode 100644 index 0000000..c58883f --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/auth/vo/AuthLoginRespVO.java @@ -0,0 +1,28 @@ +package com.tashow.cloud.system.controller.admin.auth.vo; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.time.LocalDateTime; + +/** 管理后台 - 登录 Response VO */ +@Data +@NoArgsConstructor +@AllArgsConstructor +@Builder +public class AuthLoginRespVO { + + /** 用户编号" */ + private Long userId; + + /** 访问令牌" */ + private String accessToken; + + /** 刷新令牌" */ + private String refreshToken; + + /** 过期时间 */ + private LocalDateTime expiresTime; +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/auth/vo/AuthMenuRespVO.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/auth/vo/AuthMenuRespVO.java new file mode 100644 index 0000000..92b25ca --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/auth/vo/AuthMenuRespVO.java @@ -0,0 +1,48 @@ +package com.tashow.cloud.system.controller.admin.auth.vo; + +import java.util.List; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** 管理后台 - 登录用户的菜单信息 Response VO */ +@Data +@NoArgsConstructor +@AllArgsConstructor +@Builder +public class AuthMenuRespVO { + + /** 菜单名称", example = "芋道 */ + private Long id; + + /** 父菜单 ID" */ + private Long parentId; + + /** 菜单名称", example = "芋道 */ + private String name; + + /** 路由地址,仅菜单类型为菜单或者目录时,才需要传 */ + private String path; + + /** 组件路径,仅菜单类型为菜单时,才需要传 */ + private String component; + + /** 组件名 */ + private String componentName; + + /** 菜单图标,仅菜单类型为菜单或者目录时,才需要传 */ + private String icon; + + /** 是否可见" */ + private Boolean visible; + + /** 是否缓存" */ + private Boolean keepAlive; + + /** 是否总是显示 */ + private Boolean alwaysShow; + + /** 子路由 */ + private List children; +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/auth/vo/AuthPermissionInfoRespVO.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/auth/vo/AuthPermissionInfoRespVO.java new file mode 100644 index 0000000..f367fae --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/auth/vo/AuthPermissionInfoRespVO.java @@ -0,0 +1,97 @@ +package com.tashow.cloud.system.controller.admin.auth.vo; + +import java.util.List; +import java.util.Set; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** 管理后台 - 登录用户的权限信息 Response VO,额外包括用户信息和角色列表 */ +@Data +@NoArgsConstructor +@AllArgsConstructor +@Builder +public class AuthPermissionInfoRespVO { + + /** + * 用户信息 + */ + private UserVO user; + + /** + * 角色标识数组 + */ + private Set roles; + + /** + * 操作权限数组 + */ + private Set permissions; + + /** + * 菜单树 + */ + private List menus; + + /** 用户信息 VO */ + @Data + @NoArgsConstructor + @AllArgsConstructor + @Builder + public static class UserVO { + + /** 用户编号" */ + private Long id; + + /** 用户昵称", example = "芋道源码 */ + private String nickname; + + /** 用户头像" */ + private String avatar; + + /** 部门编号" */ + private Long deptId; + } + + /** 管理后台 - 登录用户的菜单信息 Response VO */ + @Data + @NoArgsConstructor + @AllArgsConstructor + @Builder + public static class MenuVO { + + /** 菜单名称", example = "芋道 */ + private Long id; + + /** 父菜单 ID" */ + private Long parentId; + + /** 菜单名称", example = "芋道 */ + private String name; + + /** 路由地址,仅菜单类型为菜单或者目录时,才需要传 */ + private String path; + + /** 组件路径,仅菜单类型为菜单时,才需要传 */ + private String component; + + /** 组件名 */ + private String componentName; + + /** 菜单图标,仅菜单类型为菜单或者目录时,才需要传 */ + private String icon; + + /** 是否可见" */ + private Boolean visible; + + /** 是否缓存" */ + private Boolean keepAlive; + + /** 是否总是显示 */ + private Boolean alwaysShow; + + /** 子路由 */ + private List children; + } +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/auth/vo/AuthRegisterReqVO.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/auth/vo/AuthRegisterReqVO.java new file mode 100644 index 0000000..0576ddc --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/auth/vo/AuthRegisterReqVO.java @@ -0,0 +1,29 @@ +package com.tashow.cloud.system.controller.admin.auth.vo; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.Pattern; +import jakarta.validation.constraints.Size; +import lombok.Data; +import org.hibernate.validator.constraints.Length; + +/** 管理后台 - Register Request VO */ +@Data +public class AuthRegisterReqVO extends CaptchaVerificationReqVO { + + /** 用户账号" */ + @NotBlank(message = "用户账号不能为空") + @Pattern(regexp = "^[a-zA-Z0-9]{4,30}$", message = "用户账号由 数字、字母 组成") + @Size(min = 4, max = 30, message = "用户账号长度为 4-30 个字符") + private String username; + + /** 用户昵称", example = "芋艿 */ + @NotBlank(message = "用户昵称不能为空") + @Size(max = 30, message = "用户昵称长度不能超过 30 个字符") + private String nickname; + + /** 密码" */ + @NotEmpty(message = "密码不能为空") + @Length(min = 4, max = 16, message = "密码长度为 4-16 位") + private String password; +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/auth/vo/AuthResetPasswordReqVO.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/auth/vo/AuthResetPasswordReqVO.java new file mode 100644 index 0000000..1ed964a --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/auth/vo/AuthResetPasswordReqVO.java @@ -0,0 +1,32 @@ +package com.tashow.cloud.system.controller.admin.auth.vo; + +import com.tashow.cloud.common.validation.Mobile; + +import jakarta.validation.constraints.NotEmpty; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +import org.hibernate.validator.constraints.Length; + +/** 管理后台 - 短信重置账号密码 Request VO */ +@Data +@NoArgsConstructor +@AllArgsConstructor +@Builder +public class AuthResetPasswordReqVO { + + /** 密码" */ + @NotEmpty(message = "密码不能为空") + @Length(min = 4, max = 16, message = "密码长度为 4-16 位") + private String password; + + /** 手机号" */ + @NotEmpty(message = "手机号不能为空") + @Mobile + private String mobile; + + /** 手机短信验证码" */ + @NotEmpty(message = "手机手机短信验证码不能为空") + private String code; +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/auth/vo/AuthSmsLoginReqVO.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/auth/vo/AuthSmsLoginReqVO.java new file mode 100644 index 0000000..f8a4c46 --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/auth/vo/AuthSmsLoginReqVO.java @@ -0,0 +1,25 @@ +package com.tashow.cloud.system.controller.admin.auth.vo; + +import com.tashow.cloud.common.validation.Mobile; +import jakarta.validation.constraints.NotEmpty; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** 管理后台 - 短信验证码的登录 Request VO */ +@Data +@NoArgsConstructor +@AllArgsConstructor +@Builder +public class AuthSmsLoginReqVO { + + /** 手机号" */ + @NotEmpty(message = "手机号不能为空") + @Mobile + private String mobile; + + /** 短信验证码" */ + @NotEmpty(message = "验证码不能为空") + private String code; +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/auth/vo/AuthSmsSendReqVO.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/auth/vo/AuthSmsSendReqVO.java new file mode 100644 index 0000000..bfbe74b --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/auth/vo/AuthSmsSendReqVO.java @@ -0,0 +1,29 @@ +package com.tashow.cloud.system.controller.admin.auth.vo; + +import com.tashow.cloud.common.validation.InEnum; +import com.tashow.cloud.common.validation.Mobile; +import com.tashow.cloud.systemapi.enums.sms.SmsSceneEnum; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** 管理后台 - 发送手机验证码 Request VO */ +@Data +@NoArgsConstructor +@AllArgsConstructor +@Builder +public class AuthSmsSendReqVO extends CaptchaVerificationReqVO { + + /** 手机号" */ + @NotEmpty(message = "手机号不能为空") + @Mobile + private String mobile; + + /** 短信场景" */ + @NotNull(message = "发送场景不能为空") + @InEnum(SmsSceneEnum.class) + private Integer scene; +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/auth/vo/AuthSocialLoginReqVO.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/auth/vo/AuthSocialLoginReqVO.java new file mode 100644 index 0000000..df2414e --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/auth/vo/AuthSocialLoginReqVO.java @@ -0,0 +1,31 @@ +package com.tashow.cloud.system.controller.admin.auth.vo; + +import com.tashow.cloud.common.validation.InEnum; +import com.tashow.cloud.systemapi.enums.social.SocialTypeEnum; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** 管理后台 - 社交绑定登录 Request VO,使用 code 授权码 + 账号密码 */ +@Data +@NoArgsConstructor +@AllArgsConstructor +@Builder +public class AuthSocialLoginReqVO { + + /** 社交平台的类型,参见 UserSocialTypeEnum 枚举值" */ + @InEnum(SocialTypeEnum.class) + @NotNull(message = "社交平台的类型不能为空") + private Integer type; + + /** 授权码" */ + @NotEmpty(message = "授权码不能为空") + private String code; + + /** state" */ + @NotEmpty(message = "state 不能为空") + private String state; +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/auth/vo/CaptchaVerificationReqVO.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/auth/vo/CaptchaVerificationReqVO.java new file mode 100644 index 0000000..35d2387 --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/auth/vo/CaptchaVerificationReqVO.java @@ -0,0 +1,16 @@ +package com.tashow.cloud.system.controller.admin.auth.vo; + +import jakarta.validation.constraints.NotEmpty; +import lombok.Data; + +/** 管理后台 - 验证码 Request VO */ +@Data +public class CaptchaVerificationReqVO { + + // ========== 图片验证码相关 ========== + @NotEmpty(message = "验证码不能为空", groups = CodeEnableGroup.class) + private String captchaVerification; + + /** 开启验证码的 Group */ + public interface CodeEnableGroup {} +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/captcha/CaptchaController.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/captcha/CaptchaController.java new file mode 100644 index 0000000..abed48a --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/captcha/CaptchaController.java @@ -0,0 +1,48 @@ +package com.tashow.cloud.system.controller.admin.captcha; + +import cn.hutool.core.util.StrUtil; +import com.tashow.cloud.common.util.servlet.ServletUtils; +import com.xingyuv.captcha.model.common.ResponseModel; +import com.xingyuv.captcha.model.vo.CaptchaVO; +import com.xingyuv.captcha.service.CaptchaService; +import jakarta.annotation.Resource; +import jakarta.annotation.security.PermitAll; +import jakarta.servlet.http.HttpServletRequest; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +/** 管理后台 - 验证码 */ +@RestController("adminCaptchaController") +@RequestMapping("/system/captcha") +public class CaptchaController { + + @Resource private CaptchaService captchaService; + + /** 获得验证码 */ + @PostMapping({"/get"}) + @PermitAll + public ResponseModel get(@RequestBody CaptchaVO data, HttpServletRequest request) { + assert request.getRemoteHost() != null; + data.setBrowserInfo(getRemoteId(request)); + return captchaService.get(data); + } + + /** 校验验证码 */ + @PostMapping("/check") + @PermitAll + public ResponseModel check(@RequestBody CaptchaVO data, HttpServletRequest request) { + data.setBrowserInfo(getRemoteId(request)); + return captchaService.check(data); + } + + public static String getRemoteId(HttpServletRequest request) { + String ip = ServletUtils.getClientIP(request); + String ua = request.getHeader("user-agent"); + if (StrUtil.isNotBlank(ip)) { + return ip + ua; + } + return request.getRemoteAddr() + ua; + } +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/dept/DeptController.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/dept/DeptController.java new file mode 100644 index 0000000..796f2a8 --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/dept/DeptController.java @@ -0,0 +1,76 @@ +package com.tashow.cloud.system.controller.admin.dept; + +import static com.tashow.cloud.common.pojo.CommonResult.success; + +import com.tashow.cloud.common.enums.CommonStatusEnum; +import com.tashow.cloud.common.pojo.CommonResult; +import com.tashow.cloud.common.util.object.BeanUtils; +import com.tashow.cloud.system.controller.admin.dept.vo.dept.DeptListReqVO; +import com.tashow.cloud.system.controller.admin.dept.vo.dept.DeptRespVO; +import com.tashow.cloud.system.controller.admin.dept.vo.dept.DeptSaveReqVO; +import com.tashow.cloud.system.controller.admin.dept.vo.dept.DeptSimpleRespVO; +import com.tashow.cloud.system.dal.dataobject.dept.DeptDO; +import com.tashow.cloud.system.service.dept.DeptService; +import jakarta.annotation.Resource; +import jakarta.validation.Valid; +import java.util.List; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +/** 管理后台 - 部门 */ +@RestController +@RequestMapping("/system/dept") +@Validated +public class DeptController { + + @Resource private DeptService deptService; + + /** 创建部门 */ + @PostMapping("create") + @PreAuthorize("@ss.hasPermission('system:dept:create')") + public CommonResult createDept(@Valid @RequestBody DeptSaveReqVO createReqVO) { + Long deptId = deptService.createDept(createReqVO); + return success(deptId); + } + + /** 更新部门 */ + @PutMapping("update") + @PreAuthorize("@ss.hasPermission('system:dept:update')") + public CommonResult updateDept(@Valid @RequestBody DeptSaveReqVO updateReqVO) { + deptService.updateDept(updateReqVO); + return success(true); + } + + /** 删除部门 */ + @DeleteMapping("delete") + @PreAuthorize("@ss.hasPermission('system:dept:delete')") + public CommonResult deleteDept(@RequestParam("id") Long id) { + deptService.deleteDept(id); + return success(true); + } + + /** 获取部门列表 */ + @GetMapping("/list") + @PreAuthorize("@ss.hasPermission('system:dept:query')") + public CommonResult> getDeptList(DeptListReqVO reqVO) { + List list = deptService.getDeptList(reqVO); + return success(BeanUtils.toBean(list, DeptRespVO.class)); + } + + /** 获取部门精简信息列表", description = "只包含被开启的部门,主要用于前端的下拉选项 */ + @GetMapping(value = {"/list-all-simple", "/simple-list"}) + public CommonResult> getSimpleDeptList() { + List list = + deptService.getDeptList(new DeptListReqVO().setStatus(CommonStatusEnum.ENABLE.getStatus())); + return success(BeanUtils.toBean(list, DeptSimpleRespVO.class)); + } + + /** 获得部门信息 */ + @GetMapping("/get") + @PreAuthorize("@ss.hasPermission('system:dept:query')") + public CommonResult getDept(@RequestParam("id") Long id) { + DeptDO dept = deptService.getDept(id); + return success(BeanUtils.toBean(dept, DeptRespVO.class)); + } +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/dept/PostController.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/dept/PostController.java new file mode 100644 index 0000000..223a1a5 --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/dept/PostController.java @@ -0,0 +1,101 @@ +package com.tashow.cloud.system.controller.admin.dept; + +import static com.tashow.cloud.common.pojo.CommonResult.success; +import static com.tashow.cloud.web.apilog.core.enums.OperateTypeEnum.EXPORT; + +import com.tashow.cloud.common.enums.CommonStatusEnum; +import com.tashow.cloud.common.pojo.CommonResult; +import com.tashow.cloud.common.pojo.PageParam; +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.common.util.object.BeanUtils; +import com.tashow.cloud.excel.excel.core.util.ExcelUtils; +import com.tashow.cloud.system.controller.admin.dept.vo.post.PostPageReqVO; +import com.tashow.cloud.system.controller.admin.dept.vo.post.PostRespVO; +import com.tashow.cloud.system.controller.admin.dept.vo.post.PostSaveReqVO; +import com.tashow.cloud.system.controller.admin.dept.vo.post.PostSimpleRespVO; +import com.tashow.cloud.system.dal.dataobject.dept.PostDO; +import com.tashow.cloud.system.service.dept.PostService; +import com.tashow.cloud.web.apilog.core.annotation.ApiAccessLog; +import jakarta.annotation.Resource; +import jakarta.servlet.http.HttpServletResponse; +import jakarta.validation.Valid; +import java.io.IOException; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +/** 管理后台 - 岗位 */ +@RestController +@RequestMapping("/system/post") +@Validated +public class PostController { + + @Resource private PostService postService; + + /** 创建岗位 */ + @PostMapping("/create") + @PreAuthorize("@ss.hasPermission('system:post:create')") + public CommonResult createPost(@Valid @RequestBody PostSaveReqVO createReqVO) { + Long postId = postService.createPost(createReqVO); + return success(postId); + } + + /** 修改岗位 */ + @PutMapping("/update") + @PreAuthorize("@ss.hasPermission('system:post:update')") + public CommonResult updatePost(@Valid @RequestBody PostSaveReqVO updateReqVO) { + postService.updatePost(updateReqVO); + return success(true); + } + + /** 删除岗位 */ + @DeleteMapping("/delete") + @PreAuthorize("@ss.hasPermission('system:post:delete')") + public CommonResult deletePost(@RequestParam("id") Long id) { + postService.deletePost(id); + return success(true); + } + + /** 获得岗位信息 */ + @GetMapping(value = "/get") + @PreAuthorize("@ss.hasPermission('system:post:query')") + public CommonResult getPost(@RequestParam("id") Long id) { + PostDO post = postService.getPost(id); + return success(BeanUtils.toBean(post, PostRespVO.class)); + } + + /** 获取岗位全列表", description = "只包含被开启的岗位,主要用于前端的下拉选项 */ + @GetMapping(value = {"/list-all-simple", "simple-list"}) + public CommonResult> getSimplePostList() { + // 获得岗位列表,只要开启状态的 + List list = + postService.getPostList(null, Collections.singleton(CommonStatusEnum.ENABLE.getStatus())); + // 排序后,返回给前端 + list.sort(Comparator.comparing(PostDO::getSort)); + return success(BeanUtils.toBean(list, PostSimpleRespVO.class)); + } + + /** 获得岗位分页列表 */ + @GetMapping("/page") + @PreAuthorize("@ss.hasPermission('system:post:query')") + public CommonResult> getPostPage(@Validated PostPageReqVO pageReqVO) { + PageResult pageResult = postService.getPostPage(pageReqVO); + return success(BeanUtils.toBean(pageResult, PostRespVO.class)); + } + + /** 岗位管理 */ + @GetMapping("/export") + @PreAuthorize("@ss.hasPermission('system:post:export')") + @ApiAccessLog(operateType = EXPORT) + public void export(HttpServletResponse response, @Validated PostPageReqVO reqVO) + throws IOException { + reqVO.setPageSize(PageParam.PAGE_SIZE_NONE); + List list = postService.getPostPage(reqVO).getList(); + // 输出 + ExcelUtils.write( + response, "岗位数据.xls", "岗位列表", PostRespVO.class, BeanUtils.toBean(list, PostRespVO.class)); + } +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/dept/vo/dept/DeptListReqVO.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/dept/vo/dept/DeptListReqVO.java new file mode 100644 index 0000000..bd4fc2c --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/dept/vo/dept/DeptListReqVO.java @@ -0,0 +1,14 @@ +package com.tashow.cloud.system.controller.admin.dept.vo.dept; + +import lombok.Data; + +/** 管理后台 - 部门列表 Request VO */ +@Data +public class DeptListReqVO { + + /** 部门名称,模糊匹配 */ + private String name; + + /** 展示状态,参见 CommonStatusEnum 枚举类 */ + private Integer status; +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/dept/vo/dept/DeptRespVO.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/dept/vo/dept/DeptRespVO.java new file mode 100644 index 0000000..aadfaf4 --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/dept/vo/dept/DeptRespVO.java @@ -0,0 +1,37 @@ +package com.tashow.cloud.system.controller.admin.dept.vo.dept; + +import lombok.Data; + +import java.time.LocalDateTime; + +/** 管理后台 - 部门信息 Response VO */ +@Data +public class DeptRespVO { + + /** 部门编号 */ + private Long id; + + /** 部门名称", example = "芋道 */ + private String name; + + /** 父部门 ID */ + private Long parentId; + + /** 显示顺序" */ + private Integer sort; + + /** 负责人的用户编号 */ + private Long leaderUserId; + + /** 联系电话 */ + private String phone; + + /** 邮箱 */ + private String email; + + /** 状态,见 CommonStatusEnum 枚举" */ + private Integer status; + + /** 创建时间", example = "时间戳格式 */ + private LocalDateTime createTime; +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/dept/vo/dept/DeptSaveReqVO.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/dept/vo/dept/DeptSaveReqVO.java new file mode 100644 index 0000000..4c3ad5f --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/dept/vo/dept/DeptSaveReqVO.java @@ -0,0 +1,46 @@ +package com.tashow.cloud.system.controller.admin.dept.vo.dept; + +import com.tashow.cloud.common.enums.CommonStatusEnum; +import com.tashow.cloud.common.validation.InEnum; +import jakarta.validation.constraints.Email; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Size; +import lombok.Data; + +/** 管理后台 - 部门创建/修改 Request VO */ +@Data +public class DeptSaveReqVO { + + /** 部门编号 */ + private Long id; + + /** 部门名称", example = "芋道 */ + @NotBlank(message = "部门名称不能为空") + @Size(max = 30, message = "部门名称长度不能超过 30 个字符") + private String name; + + /** 父部门 ID */ + private Long parentId; + + /** 显示顺序" */ + @NotNull(message = "显示顺序不能为空") + private Integer sort; + + /** 负责人的用户编号 */ + private Long leaderUserId; + + /** 联系电话 */ + @Size(max = 11, message = "联系电话长度不能超过11个字符") + private String phone; + + /** 邮箱 */ + @Email(message = "邮箱格式不正确") + @Size(max = 50, message = "邮箱长度不能超过 50 个字符") + private String email; + + /** 状态,见 CommonStatusEnum 枚举" */ + @NotNull(message = "状态不能为空") + @InEnum(value = CommonStatusEnum.class, message = "修改状态必须是 {value}") + private Integer status; +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/dept/vo/dept/DeptSimpleRespVO.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/dept/vo/dept/DeptSimpleRespVO.java new file mode 100644 index 0000000..06a622d --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/dept/vo/dept/DeptSimpleRespVO.java @@ -0,0 +1,21 @@ +package com.tashow.cloud.system.controller.admin.dept.vo.dept; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** 管理后台 - 部门精简信息 Response VO */ +@Data +@NoArgsConstructor +@AllArgsConstructor +public class DeptSimpleRespVO { + + /** 部门编号" */ + private Long id; + + /** 部门名称", example = "芋道 */ + private String name; + + /** 父部门 ID" */ + private Long parentId; +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/dept/vo/post/PostPageReqVO.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/dept/vo/post/PostPageReqVO.java new file mode 100644 index 0000000..5a7fd44 --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/dept/vo/post/PostPageReqVO.java @@ -0,0 +1,28 @@ +package com.tashow.cloud.system.controller.admin.dept.vo.post; + +import com.tashow.cloud.common.pojo.PageParam; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 管理后台 - 岗位分页 Request VO + */ +@Data +@EqualsAndHashCode(callSuper = true) +public class PostPageReqVO extends PageParam { + + /** + * 岗位编码,模糊匹配 + */ + private String code; + + /** + * 岗位名称,模糊匹配 + */ + private String name; + + /** + * 展示状态,参见 CommonStatusEnum 枚举类 + */ + private Integer status; +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/dept/vo/post/PostRespVO.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/dept/vo/post/PostRespVO.java new file mode 100644 index 0000000..88ad13f --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/dept/vo/post/PostRespVO.java @@ -0,0 +1,59 @@ +package com.tashow.cloud.system.controller.admin.dept.vo.post; + +import com.alibaba.excel.annotation.ExcelIgnoreUnannotated; +import com.alibaba.excel.annotation.ExcelProperty; +import com.tashow.cloud.excel.excel.core.annotations.DictFormat; +import com.tashow.cloud.excel.excel.core.convert.DictConvert; +import com.tashow.cloud.systemapi.enums.DictTypeConstants; +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * 管理后台 - 岗位信息 Response VO + */ +@Data +@ExcelIgnoreUnannotated +public class PostRespVO { + + /** + * 岗位序号" + */ + @ExcelProperty("岗位序号") + private Long id; + + /** + * 岗位名称", example = "小土豆 + */ + @ExcelProperty("岗位名称") + private String name; + + /** + * 岗位编码" + */ + @ExcelProperty("岗位编码") + private String code; + + /** + * 显示顺序" + */ + @ExcelProperty("岗位排序") + private Integer sort; + + /** + * 状态,参见 CommonStatusEnum 枚举类" + */ + @ExcelProperty(value = "状态", converter = DictConvert.class) + @DictFormat(DictTypeConstants.COMMON_STATUS) + private Integer status; + + /** + * 备注 + */ + private String remark; + + /** + * 创建时间 + */ + private LocalDateTime createTime; +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/dept/vo/post/PostSaveReqVO.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/dept/vo/post/PostSaveReqVO.java new file mode 100644 index 0000000..a706017 --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/dept/vo/post/PostSaveReqVO.java @@ -0,0 +1,51 @@ +package com.tashow.cloud.system.controller.admin.dept.vo.post; + +import com.tashow.cloud.common.enums.CommonStatusEnum; +import com.tashow.cloud.common.validation.InEnum; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Size; +import lombok.Data; + +/** + * 管理后台 - 岗位创建/修改 Request VO + */ +@Data +public class PostSaveReqVO { + + /** + * 岗位编号 + */ + private Long id; + + /** + * 岗位名称", example = "小土豆 + */ + @NotBlank(message = "岗位名称不能为空") + @Size(max = 50, message = "岗位名称长度不能超过 50 个字符") + private String name; + + /** + * 岗位编码" + */ + @NotBlank(message = "岗位编码不能为空") + @Size(max = 64, message = "岗位编码长度不能超过64个字符") + private String code; + + /** + * 显示顺序" + */ + @NotNull(message = "显示顺序不能为空") + private Integer sort; + + /** + * 状态" + */ + @InEnum(CommonStatusEnum.class) + private Integer status; + + /** + * 备注 + */ + private String remark; +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/dept/vo/post/PostSimpleRespVO.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/dept/vo/post/PostSimpleRespVO.java new file mode 100644 index 0000000..690f040 --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/dept/vo/post/PostSimpleRespVO.java @@ -0,0 +1,23 @@ +package com.tashow.cloud.system.controller.admin.dept.vo.post; + +import com.alibaba.excel.annotation.ExcelProperty; +import lombok.Data; + +/** + * 管理后台 - 岗位信息的精简 Response VO + */ +@Data +public class PostSimpleRespVO { + + /** + * 岗位序号" + */ + @ExcelProperty("岗位序号") + private Long id; + + /** + * 岗位名称", example = "小土豆 + */ + @ExcelProperty("岗位名称") + private String name; +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/dict/DictDataController.http b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/dict/DictDataController.http new file mode 100644 index 0000000..5a7ce8e --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/dict/DictDataController.http @@ -0,0 +1,4 @@ +### 请求 /menu/list 接口 => 成功 +GET {{baseUrl}}/system/dict-data/list-all-simple +Authorization: Bearer {{token}} +tenant-id: {{adminTenantId}} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/dict/DictDataController.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/dict/DictDataController.java new file mode 100644 index 0000000..d2ac0e4 --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/dict/DictDataController.java @@ -0,0 +1,121 @@ +package com.tashow.cloud.system.controller.admin.dict; + +import com.tashow.cloud.common.enums.CommonStatusEnum; +import com.tashow.cloud.common.pojo.CommonResult; +import com.tashow.cloud.common.pojo.PageParam; +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.common.util.object.BeanUtils; +import com.tashow.cloud.excel.excel.core.util.ExcelUtils; +import com.tashow.cloud.system.controller.admin.dict.vo.data.DictDataPageReqVO; +import com.tashow.cloud.system.controller.admin.dict.vo.data.DictDataRespVO; +import com.tashow.cloud.system.controller.admin.dict.vo.data.DictDataSaveReqVO; +import com.tashow.cloud.system.controller.admin.dict.vo.data.DictDataSimpleRespVO; +import com.tashow.cloud.system.dal.dataobject.dict.DictDataDO; +import com.tashow.cloud.system.service.dict.DictDataService; +import com.tashow.cloud.web.apilog.core.annotation.ApiAccessLog; +import jakarta.annotation.Resource; +import jakarta.servlet.http.HttpServletResponse; +import jakarta.validation.Valid; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import java.io.IOException; +import java.util.List; + +import static com.tashow.cloud.common.pojo.CommonResult.success; +import static com.tashow.cloud.web.apilog.core.enums.OperateTypeEnum.EXPORT; + +/** + * 管理后台 - 字典数据 + */ +@RestController +@RequestMapping("/system/dict-data") +@Validated +public class DictDataController { + + @Resource + private DictDataService dictDataService; + + /** + * 新增字典数据 + */ + @PostMapping("/create") + @PreAuthorize("@ss.hasPermission('system:dict:create')") + public CommonResult createDictData(@Valid @RequestBody DictDataSaveReqVO createReqVO) { + Long dictDataId = dictDataService.createDictData(createReqVO); + return success(dictDataId); + } + + /** + * 修改字典数据 + */ + @PutMapping("/update") + @PreAuthorize("@ss.hasPermission('system:dict:update')") + public CommonResult updateDictData(@Valid @RequestBody DictDataSaveReqVO updateReqVO) { + dictDataService.updateDictData(updateReqVO); + return success(true); + } + + /** + * 删除字典数据 + */ + @DeleteMapping("/delete") + @PreAuthorize("@ss.hasPermission('system:dict:delete')") + public CommonResult deleteDictData(Long id) { + dictDataService.deleteDictData(id); + return success(true); + } + + /** + * 获得全部字典数据列表", description = "一般用于管理后台缓存字典数据在本地 + */ + @GetMapping(value = {"/list-all-simple", "simple-list"}) + + // 无需添加权限认证,因为前端全局都需要 + public CommonResult> getSimpleDictDataList() { + List list = + dictDataService.getDictDataList(CommonStatusEnum.ENABLE.getStatus(), null); + return success(BeanUtils.toBean(list, DictDataSimpleRespVO.class)); + } + + /** + * /获得字典类型的分页列表 + */ + @GetMapping("/page") + @PreAuthorize("@ss.hasPermission('system:dict:query')") + public CommonResult> getDictTypePage( + @Valid DictDataPageReqVO pageReqVO) { + PageResult pageResult = dictDataService.getDictDataPage(pageReqVO); + return success(BeanUtils.toBean(pageResult, DictDataRespVO.class)); + } + + /** + * /查询字典数据详细 + */ + @GetMapping(value = "/get") + @PreAuthorize("@ss.hasPermission('system:dict:query')") + public CommonResult getDictData(@RequestParam("id") Long id) { + DictDataDO dictData = dictDataService.getDictData(id); + return success(BeanUtils.toBean(dictData, DictDataRespVO.class)); + } + + /** + * 导出字典数据 + */ + @GetMapping("/export") + @PreAuthorize("@ss.hasPermission('system:dict:export')") + @ApiAccessLog(operateType = EXPORT) + public void export(HttpServletResponse response, @Valid DictDataPageReqVO exportReqVO) + throws IOException { + exportReqVO.setPageSize(PageParam.PAGE_SIZE_NONE); + List list = dictDataService.getDictDataPage(exportReqVO).getList(); + // 输出 + ExcelUtils.write( + response, + "字典数据.xls", + "数据", + DictDataRespVO.class, + BeanUtils.toBean(list, DictDataRespVO.class)); + } +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/dict/DictTypeController.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/dict/DictTypeController.java new file mode 100644 index 0000000..24d2ae4 --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/dict/DictTypeController.java @@ -0,0 +1,118 @@ +package com.tashow.cloud.system.controller.admin.dict; + +import com.tashow.cloud.common.pojo.CommonResult; +import com.tashow.cloud.common.pojo.PageParam; +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.common.util.object.BeanUtils; +import com.tashow.cloud.excel.excel.core.util.ExcelUtils; +import com.tashow.cloud.system.controller.admin.dict.vo.type.DictTypePageReqVO; +import com.tashow.cloud.system.controller.admin.dict.vo.type.DictTypeRespVO; +import com.tashow.cloud.system.controller.admin.dict.vo.type.DictTypeSaveReqVO; +import com.tashow.cloud.system.controller.admin.dict.vo.type.DictTypeSimpleRespVO; +import com.tashow.cloud.system.dal.dataobject.dict.DictTypeDO; +import com.tashow.cloud.system.service.dict.DictTypeService; +import com.tashow.cloud.web.apilog.core.annotation.ApiAccessLog; +import jakarta.annotation.Resource; +import jakarta.servlet.http.HttpServletResponse; +import jakarta.validation.Valid; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import java.io.IOException; +import java.util.List; + +import static com.tashow.cloud.common.pojo.CommonResult.success; +import static com.tashow.cloud.web.apilog.core.enums.OperateTypeEnum.EXPORT; + +/** + * 管理后台 - 字典类型 + */ +@RestController +@RequestMapping("/system/dict-type") +@Validated +public class DictTypeController { + + @Resource + private DictTypeService dictTypeService; + + /** + * 创建字典类型 + */ + @PostMapping("/create") + @PreAuthorize("@ss.hasPermission('system:dict:create')") + public CommonResult createDictType(@Valid @RequestBody DictTypeSaveReqVO createReqVO) { + Long dictTypeId = dictTypeService.createDictType(createReqVO); + return success(dictTypeId); + } + + /** + * 修改字典类型 + */ + @PutMapping("/update") + @PreAuthorize("@ss.hasPermission('system:dict:update')") + public CommonResult updateDictType(@Valid @RequestBody DictTypeSaveReqVO updateReqVO) { + dictTypeService.updateDictType(updateReqVO); + return success(true); + } + + /** + * 删除字典类型 + */ + @DeleteMapping("/delete") + @PreAuthorize("@ss.hasPermission('system:dict:delete')") + public CommonResult deleteDictType(Long id) { + dictTypeService.deleteDictType(id); + return success(true); + } + + /** + * 获得字典类型的分页列表 + */ + @GetMapping("/page") + @PreAuthorize("@ss.hasPermission('system:dict:query')") + public CommonResult> pageDictTypes( + @Valid DictTypePageReqVO pageReqVO) { + PageResult pageResult = dictTypeService.getDictTypePage(pageReqVO); + return success(BeanUtils.toBean(pageResult, DictTypeRespVO.class)); + } + + /** + * /查询字典类型详细 + */ + @GetMapping(value = "/get") + @PreAuthorize("@ss.hasPermission('system:dict:query')") + public CommonResult getDictType(@RequestParam("id") Long id) { + DictTypeDO dictType = dictTypeService.getDictType(id); + return success(BeanUtils.toBean(dictType, DictTypeRespVO.class)); + } + + /** + * 获得全部字典类型列表", description = "包括开启 + 禁用的字典类型,主要用于前端的下拉选项 + */ + // 无需添加权限认证,因为前端全局都需要 + @GetMapping(value = {"/list-all-simple", "simple-list"}) + public CommonResult> getSimpleDictTypeList() { + List list = dictTypeService.getDictTypeList(); + return success(BeanUtils.toBean(list, DictTypeSimpleRespVO.class)); + } + + /** + * 导出数据类型 + */ + @GetMapping("/export") + @PreAuthorize("@ss.hasPermission('system:dict:query')") + @ApiAccessLog(operateType = EXPORT) + public void export(HttpServletResponse response, @Valid DictTypePageReqVO exportReqVO) + throws IOException { + exportReqVO.setPageSize(PageParam.PAGE_SIZE_NONE); + List list = dictTypeService.getDictTypePage(exportReqVO).getList(); + // 导出 + ExcelUtils.write( + response, + "字典类型.xls", + "数据", + DictTypeRespVO.class, + BeanUtils.toBean(list, DictTypeRespVO.class)); + } +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/dict/vo/data/DictDataPageReqVO.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/dict/vo/data/DictDataPageReqVO.java new file mode 100644 index 0000000..7b671c6 --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/dict/vo/data/DictDataPageReqVO.java @@ -0,0 +1,34 @@ +package com.tashow.cloud.system.controller.admin.dict.vo.data; + +import com.tashow.cloud.common.enums.CommonStatusEnum; +import com.tashow.cloud.common.pojo.PageParam; +import com.tashow.cloud.common.validation.InEnum; +import jakarta.validation.constraints.Size; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 管理后台 - 字典类型分页列表 Request VO + */ +@Data +@EqualsAndHashCode(callSuper = true) +public class DictDataPageReqVO extends PageParam { + + /** + * 字典标签 + */ + @Size(max = 100, message = "字典标签长度不能超过100个字符") + private String label; + + /** + * 字典类型,模糊匹配 + */ + @Size(max = 100, message = "字典类型类型长度不能超过100个字符") + private String dictType; + + /** + * 展示状态,参见 CommonStatusEnum 枚举类 + */ + @InEnum(value = CommonStatusEnum.class, message = "修改状态必须是 {value}") + private Integer status; +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/dict/vo/data/DictDataRespVO.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/dict/vo/data/DictDataRespVO.java new file mode 100644 index 0000000..9928f62 --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/dict/vo/data/DictDataRespVO.java @@ -0,0 +1,75 @@ +package com.tashow.cloud.system.controller.admin.dict.vo.data; + +import com.alibaba.excel.annotation.ExcelIgnoreUnannotated; +import com.alibaba.excel.annotation.ExcelProperty; +import com.tashow.cloud.excel.excel.core.annotations.DictFormat; +import com.tashow.cloud.excel.excel.core.convert.DictConvert; +import com.tashow.cloud.systemapi.enums.DictTypeConstants; +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * 管理后台 - 字典数据信息 Response VO + */ +@Data +@ExcelIgnoreUnannotated +public class DictDataRespVO { + + /** + * 字典数据编号" + */ + @ExcelProperty("字典编码") + private Long id; + + /** + * 显示顺序" + */ + @ExcelProperty("字典排序") + private Integer sort; + + /** + * 字典标签", example = "芋道 + */ + @ExcelProperty("字典标签") + private String label; + + /** + * 字典值" + */ + @ExcelProperty("字典键值") + private String value; + + /** + * 字典类型" + */ + @ExcelProperty("字典类型") + private String dictType; + + /** + * 状态,见 CommonStatusEnum 枚举" + */ + @ExcelProperty(value = "状态", converter = DictConvert.class) + @DictFormat(DictTypeConstants.COMMON_STATUS) + private Integer status; + + /** + * 颜色类型,default、primary、success、info、warning、danger + */ + private String colorType; + + /** + * css 样式 + */ + private String cssClass; + + /** + * 备注 + */ + private String remark; + + /** + * 创建时间", example = "时间戳格式 + */ + private LocalDateTime createTime; +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/dict/vo/data/DictDataSaveReqVO.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/dict/vo/data/DictDataSaveReqVO.java new file mode 100644 index 0000000..8e82578 --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/dict/vo/data/DictDataSaveReqVO.java @@ -0,0 +1,49 @@ +package com.tashow.cloud.system.controller.admin.dict.vo.data; + +import com.tashow.cloud.common.enums.CommonStatusEnum; +import com.tashow.cloud.common.validation.InEnum; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Size; +import lombok.Data; + +/** 管理后台 - 字典数据创建/修改 Request VO */ +@Data +public class DictDataSaveReqVO { + + /** 字典数据编号 */ + private Long id; + + /** 显示顺序" */ + @NotNull(message = "显示顺序不能为空") + private Integer sort; + + /** 字典标签", example = "芋道 */ + @NotBlank(message = "字典标签不能为空") + @Size(max = 100, message = "字典标签长度不能超过100个字符") + private String label; + + /** 字典值" */ + @NotBlank(message = "字典键值不能为空") + @Size(max = 100, message = "字典键值长度不能超过100个字符") + private String value; + + /** 字典类型" */ + @NotBlank(message = "字典类型不能为空") + @Size(max = 100, message = "字典类型长度不能超过100个字符") + private String dictType; + + /** 状态,见 CommonStatusEnum 枚举" */ + @NotNull(message = "状态不能为空") + @InEnum(value = CommonStatusEnum.class, message = "修改状态必须是 {value}") + private Integer status; + + /** 颜色类型,default、primary、success、info、warning、danger */ + private String colorType; + + /** css 样式 */ + private String cssClass; + + /** 备注 */ + private String remark; +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/dict/vo/data/DictDataSimpleRespVO.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/dict/vo/data/DictDataSimpleRespVO.java new file mode 100644 index 0000000..84b1170 --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/dict/vo/data/DictDataSimpleRespVO.java @@ -0,0 +1,35 @@ +package com.tashow.cloud.system.controller.admin.dict.vo.data; + +import lombok.Data; + +/** + * 管理后台 - 数据字典精简 Response VO + */ +@Data +public class DictDataSimpleRespVO { + + /** + * 字典类型" + */ + private String dictType; + + /** + * 字典键值" + */ + private String value; + + /** + * 字典标签", example = "男 + */ + private String label; + + /** + * 颜色类型,default、primary、success、info、warning、danger + */ + private String colorType; + + /** + * css 样式 + */ + private String cssClass; +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/dict/vo/type/DictTypePageReqVO.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/dict/vo/type/DictTypePageReqVO.java new file mode 100644 index 0000000..ee8e23e --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/dict/vo/type/DictTypePageReqVO.java @@ -0,0 +1,39 @@ +package com.tashow.cloud.system.controller.admin.dict.vo.type; + +import com.tashow.cloud.common.pojo.PageParam; +import jakarta.validation.constraints.Size; +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.springframework.format.annotation.DateTimeFormat; + +import java.time.LocalDateTime; + +import static com.tashow.cloud.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND; + +/** + * 管理后台 - 字典类型分页列表 Request VO + */ +@Data +@EqualsAndHashCode(callSuper = true) +public class DictTypePageReqVO extends PageParam { + + /** + * 字典类型名称,模糊匹配 + */ + private String name; + + /** + * 字典类型,模糊匹配 + */ + @Size(max = 100, message = "字典类型类型长度不能超过100个字符") + private String type; + + /** + * 展示状态,参见 CommonStatusEnum 枚举类 + */ + private Integer status; + + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) + /** 创建时间 */ + private LocalDateTime[] createTime; +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/dict/vo/type/DictTypeRespVO.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/dict/vo/type/DictTypeRespVO.java new file mode 100644 index 0000000..7f01ba5 --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/dict/vo/type/DictTypeRespVO.java @@ -0,0 +1,38 @@ +package com.tashow.cloud.system.controller.admin.dict.vo.type; + +import com.alibaba.excel.annotation.ExcelIgnoreUnannotated; +import com.alibaba.excel.annotation.ExcelProperty; +import com.tashow.cloud.excel.excel.core.annotations.DictFormat; +import com.tashow.cloud.excel.excel.core.convert.DictConvert; +import com.tashow.cloud.systemapi.enums.DictTypeConstants; +import java.time.LocalDateTime; +import lombok.Data; + +/** 管理后台 - 字典类型信息 Response VO */ +@Data +@ExcelIgnoreUnannotated +public class DictTypeRespVO { + + /** 字典类型编号" */ + @ExcelProperty("字典主键") + private Long id; + + /** 字典名称", example = "性别 */ + @ExcelProperty("字典名称") + private String name; + + /** 字典类型" */ + @ExcelProperty("字典类型") + private String type; + + /** 状态,参见 CommonStatusEnum 枚举类" */ + @ExcelProperty(value = "状态", converter = DictConvert.class) + @DictFormat(DictTypeConstants.COMMON_STATUS) + private Integer status; + + /** 备注 */ + private String remark; + + /** 创建时间", example = "时间戳格式 */ + private LocalDateTime createTime; +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/dict/vo/type/DictTypeSaveReqVO.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/dict/vo/type/DictTypeSaveReqVO.java new file mode 100644 index 0000000..8c5827b --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/dict/vo/type/DictTypeSaveReqVO.java @@ -0,0 +1,43 @@ +package com.tashow.cloud.system.controller.admin.dict.vo.type; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Size; +import lombok.Data; + +/** + * 管理后台 - 字典类型创建/修改 Request VO + */ +@Data +public class DictTypeSaveReqVO { + + /** + * 字典类型编号 + */ + private Long id; + + /** + * 字典名称", example = "性别 + */ + @NotBlank(message = "字典名称不能为空") + @Size(max = 100, message = "字典类型名称长度不能超过100个字符") + private String name; + + /** + * 字典类型" + */ + @NotNull(message = "字典类型不能为空") + @Size(max = 100, message = "字典类型类型长度不能超过 100 个字符") + private String type; + + /** + * 状态,参见 CommonStatusEnum 枚举类" + */ + @NotNull(message = "状态不能为空") + private Integer status; + + /** + * 备注 + */ + private String remark; +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/dict/vo/type/DictTypeSimpleRespVO.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/dict/vo/type/DictTypeSimpleRespVO.java new file mode 100644 index 0000000..0a8e682 --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/dict/vo/type/DictTypeSimpleRespVO.java @@ -0,0 +1,25 @@ +package com.tashow.cloud.system.controller.admin.dict.vo.type; + +import lombok.Data; + +/** + * 管理后台 - 字典类型精简信息 Response VO + */ +@Data +public class DictTypeSimpleRespVO { + + /** + * 字典类型编号" + */ + private Long id; + + /** + * 字典类型名称", example = "芋道 + */ + private String name; + + /** + * 字典类型" + */ + private String type; +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/ip/AreaController.http b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/ip/AreaController.http new file mode 100644 index 0000000..1416561 --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/ip/AreaController.http @@ -0,0 +1,5 @@ +### 获得地区树 +GET {{baseUrl}}/system/area/tree +Authorization: Bearer {{token}} +tenant-id: {{adminTenantId}} + diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/ip/AreaController.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/ip/AreaController.java new file mode 100644 index 0000000..b62cbad --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/ip/AreaController.java @@ -0,0 +1,51 @@ +package com.tashow.cloud.system.controller.admin.ip; + +import cn.hutool.core.lang.Assert; +import com.tashow.cloud.common.core.Area; +import com.tashow.cloud.common.pojo.CommonResult; +import com.tashow.cloud.common.util.ip.AreaUtils; +import com.tashow.cloud.common.util.ip.IPUtils; +import com.tashow.cloud.common.util.object.BeanUtils; +import com.tashow.cloud.system.controller.admin.ip.vo.AreaNodeRespVO; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import java.util.List; + +import static com.tashow.cloud.common.pojo.CommonResult.success; + +/** + * 管理后台 - 地区 + */ +@RestController +@RequestMapping("/system/area") +@Validated +public class AreaController { + + /** + * 获得地区树 + */ + @GetMapping("/tree") + public CommonResult> getAreaTree() { + Area area = AreaUtils.getArea(Area.ID_CHINA); + Assert.notNull(area, "获取不到中国"); + return success(BeanUtils.toBean(area.getChildren(), AreaNodeRespVO.class)); + } + + /** + * 获得 IP 对应的地区名 + */ + @GetMapping("/get-by-ip") + public CommonResult getAreaByIp(@RequestParam("ip") String ip) { + // 获得城市 + Area area = IPUtils.getArea(ip); + if (area == null) { + return success("未知"); + } + // 格式化返回 + return success(AreaUtils.format(area.getId())); + } +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/ip/vo/AreaNodeRespVO.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/ip/vo/AreaNodeRespVO.java new file mode 100644 index 0000000..52a0d70 --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/ip/vo/AreaNodeRespVO.java @@ -0,0 +1,18 @@ +package com.tashow.cloud.system.controller.admin.ip.vo; + +import java.util.List; +import lombok.Data; + +/** 管理后台 - 地区节点 Response VO */ +@Data +public class AreaNodeRespVO { + + /** 编号" */ + private Integer id; + + /** 名字", example = "北京 */ + private String name; + + /** 子节点 */ + private List children; +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/logger/LoginLogController.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/logger/LoginLogController.java new file mode 100644 index 0000000..5cff2f6 --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/logger/LoginLogController.java @@ -0,0 +1,68 @@ +package com.tashow.cloud.system.controller.admin.logger; + +import com.tashow.cloud.common.pojo.CommonResult; +import com.tashow.cloud.common.pojo.PageParam; +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.common.util.object.BeanUtils; +import com.tashow.cloud.excel.excel.core.util.ExcelUtils; +import com.tashow.cloud.system.controller.admin.logger.vo.loginlog.LoginLogPageReqVO; +import com.tashow.cloud.system.controller.admin.logger.vo.loginlog.LoginLogRespVO; +import com.tashow.cloud.system.dal.dataobject.logger.LoginLogDO; +import com.tashow.cloud.system.service.logger.LoginLogService; +import com.tashow.cloud.web.apilog.core.annotation.ApiAccessLog; +import jakarta.annotation.Resource; +import jakarta.servlet.http.HttpServletResponse; +import jakarta.validation.Valid; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.io.IOException; +import java.util.List; + +import static com.tashow.cloud.common.pojo.CommonResult.success; +import static com.tashow.cloud.web.apilog.core.enums.OperateTypeEnum.EXPORT; + +/** + * 管理后台 - 登录日志 + */ +@RestController +@RequestMapping("/system/login-log") +@Validated +public class LoginLogController { + + @Resource + private LoginLogService loginLogService; + + /** + * 获得登录日志分页列表 + */ + @GetMapping("/page") + @PreAuthorize("@ss.hasPermission('system:login-log:query')") + public CommonResult> getLoginLogPage( + @Valid LoginLogPageReqVO pageReqVO) { + PageResult pageResult = loginLogService.getLoginLogPage(pageReqVO); + return success(BeanUtils.toBean(pageResult, LoginLogRespVO.class)); + } + + /** + * 导出登录日志 Excel + */ + @GetMapping("/export") + @PreAuthorize("@ss.hasPermission('system:login-log:export')") + @ApiAccessLog(operateType = EXPORT) + public void exportLoginLog(HttpServletResponse response, @Valid LoginLogPageReqVO exportReqVO) + throws IOException { + exportReqVO.setPageSize(PageParam.PAGE_SIZE_NONE); + List list = loginLogService.getLoginLogPage(exportReqVO).getList(); + // 输出 + ExcelUtils.write( + response, + "登录日志.xls", + "数据列表", + LoginLogRespVO.class, + BeanUtils.toBean(list, LoginLogRespVO.class)); + } +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/logger/OperateLogController.http b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/logger/OperateLogController.http new file mode 100644 index 0000000..be3102e --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/logger/OperateLogController.http @@ -0,0 +1,4 @@ +### 请求 /system/operate-log/page 接口 => 成功 +GET {{baseUrl}}/system/operate-log/page +Authorization: Bearer {{token}} +tenant-id: {{adminTenantId}} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/logger/OperateLogController.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/logger/OperateLogController.java new file mode 100644 index 0000000..5d27251 --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/logger/OperateLogController.java @@ -0,0 +1,68 @@ +package com.tashow.cloud.system.controller.admin.logger; + +import com.tashow.cloud.common.pojo.CommonResult; +import com.tashow.cloud.common.pojo.PageParam; +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.common.util.object.BeanUtils; +import com.tashow.cloud.excel.excel.core.util.ExcelUtils; +import com.tashow.cloud.mybatis.translate.core.TranslateUtils; +import com.tashow.cloud.system.controller.admin.logger.vo.operatelog.OperateLogPageReqVO; +import com.tashow.cloud.system.controller.admin.logger.vo.operatelog.OperateLogRespVO; +import com.tashow.cloud.system.dal.dataobject.logger.OperateLogDO; +import com.tashow.cloud.system.service.logger.OperateLogService; +import com.tashow.cloud.web.apilog.core.annotation.ApiAccessLog; +import jakarta.annotation.Resource; +import jakarta.servlet.http.HttpServletResponse; +import jakarta.validation.Valid; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.io.IOException; +import java.util.List; + +import static com.tashow.cloud.common.pojo.CommonResult.success; +import static com.tashow.cloud.web.apilog.core.enums.OperateTypeEnum.EXPORT; + +/** + * 管理后台 - 操作日志 + */ +@RestController +@RequestMapping("/system/operate-log") +@Validated +public class OperateLogController { + + @Resource + private OperateLogService operateLogService; + + /** + * 查看操作日志分页列表 + */ + @GetMapping("/page") + @PreAuthorize("@ss.hasPermission('system:operate-log:query')") + public CommonResult> pageOperateLog( + @Valid OperateLogPageReqVO pageReqVO) { + PageResult pageResult = operateLogService.getOperateLogPage(pageReqVO); + return success(BeanUtils.toBean(pageResult, OperateLogRespVO.class)); + } + + /** + * 导出操作日志 + */ + @GetMapping("/export") + @PreAuthorize("@ss.hasPermission('system:operate-log:export')") + @ApiAccessLog(operateType = EXPORT) + public void exportOperateLog(HttpServletResponse response, @Valid OperateLogPageReqVO exportReqVO) + throws IOException { + exportReqVO.setPageSize(PageParam.PAGE_SIZE_NONE); + List list = operateLogService.getOperateLogPage(exportReqVO).getList(); + ExcelUtils.write( + response, + "操作日志.xls", + "数据列表", + OperateLogRespVO.class, + TranslateUtils.translate(BeanUtils.toBean(list, OperateLogRespVO.class))); + } +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/logger/vo/loginlog/LoginLogPageReqVO.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/logger/vo/loginlog/LoginLogPageReqVO.java new file mode 100644 index 0000000..51e25c1 --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/logger/vo/loginlog/LoginLogPageReqVO.java @@ -0,0 +1,39 @@ +package com.tashow.cloud.system.controller.admin.logger.vo.loginlog; + +import com.tashow.cloud.common.pojo.PageParam; +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.springframework.format.annotation.DateTimeFormat; + +import java.time.LocalDateTime; + +import static com.tashow.cloud.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND; + +/** + * 管理后台 - 登录日志分页列表 Request VO + */ +@Data +@EqualsAndHashCode(callSuper = true) +public class LoginLogPageReqVO extends PageParam { + + /** + * 用户 IP,模拟匹配 + */ + private String userIp; + + /** + * 用户账号,模拟匹配 + */ + private String username; + + /** + * 操作状态 + */ + private Boolean status; + + /** + * 登录时间 + */ + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) + private LocalDateTime[] createTime; +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/logger/vo/loginlog/LoginLogRespVO.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/logger/vo/loginlog/LoginLogRespVO.java new file mode 100644 index 0000000..e50b73b --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/logger/vo/loginlog/LoginLogRespVO.java @@ -0,0 +1,77 @@ +package com.tashow.cloud.system.controller.admin.logger.vo.loginlog; + +import com.alibaba.excel.annotation.ExcelIgnoreUnannotated; +import com.alibaba.excel.annotation.ExcelProperty; +import com.tashow.cloud.excel.excel.core.annotations.DictFormat; +import com.tashow.cloud.excel.excel.core.convert.DictConvert; +import com.tashow.cloud.systemapi.enums.DictTypeConstants; +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * 管理后台 - 登录日志 Response VO + */ +@Data +@ExcelIgnoreUnannotated +public class LoginLogRespVO { + + /** + * 日志编号" + */ + @ExcelProperty("日志主键") + private Long id; + + /** + * 日志类型,参见 LoginLogTypeEnum 枚举类" + */ + @ExcelProperty(value = "日志类型", converter = DictConvert.class) + @DictFormat(DictTypeConstants.LOGIN_TYPE) + private Integer logType; + + /** + * 用户编号 + */ + private Long userId; + + /** + * 用户类型,参见 UserTypeEnum 枚举" + */ + private Integer userType; + + /** + * 链路追踪编号 + */ + private String traceId; + + /** + * 用户账号" + */ + @ExcelProperty("用户账号") + private String username; + + /** + * 登录结果,参见 LoginResultEnum 枚举类" + */ + @ExcelProperty(value = "登录结果", converter = DictConvert.class) + @DictFormat(DictTypeConstants.LOGIN_RESULT) + private Integer result; + + /** + * 用户 IP" + */ + @ExcelProperty("登录 IP") + private String userIp; + + /** + * 浏览器 UserAgent + */ + @ExcelProperty("浏览器 UA") + private String userAgent; + + /** + * 登录时间 + */ + @ExcelProperty("登录时间") + private LocalDateTime createTime; +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/logger/vo/operatelog/OperateLogPageReqVO.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/logger/vo/operatelog/OperateLogPageReqVO.java new file mode 100644 index 0000000..4daf740 --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/logger/vo/operatelog/OperateLogPageReqVO.java @@ -0,0 +1,47 @@ +package com.tashow.cloud.system.controller.admin.logger.vo.operatelog; + +import com.tashow.cloud.common.pojo.PageParam; +import lombok.Data; +import org.springframework.format.annotation.DateTimeFormat; + +import java.time.LocalDateTime; + +import static com.tashow.cloud.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND; + +/** + * 管理后台 - 操作日志分页列表 Request VO + */ +@Data +public class OperateLogPageReqVO extends PageParam { + + /** + * 用户编号 + */ + private Long userId; + + /** + * 操作模块业务编号 + */ + private Long bizId; + + /** + * 操作模块,模拟匹配 + */ + private String type; + + /** + * 操作名,模拟匹配 + */ + private String subType; + + /** + * 操作明细,模拟匹配 + */ + private String action; + + /** + * 开始时间 + */ + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) + private LocalDateTime[] createTime; +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/logger/vo/operatelog/OperateLogRespVO.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/logger/vo/operatelog/OperateLogRespVO.java new file mode 100644 index 0000000..ec612ab --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/logger/vo/operatelog/OperateLogRespVO.java @@ -0,0 +1,68 @@ +package com.tashow.cloud.system.controller.admin.logger.vo.operatelog; + +import com.alibaba.excel.annotation.ExcelIgnoreUnannotated; +import com.alibaba.excel.annotation.ExcelProperty; +import com.fhs.core.trans.anno.Trans; +import com.fhs.core.trans.constant.TransType; +import com.fhs.core.trans.vo.VO; +import com.tashow.cloud.system.dal.dataobject.user.AdminUserDO; +import jakarta.validation.constraints.NotEmpty; +import java.time.LocalDateTime; +import lombok.Data; + +/** 管理后台 - 操作日志 Response VO */ +@Data +@ExcelIgnoreUnannotated +public class OperateLogRespVO implements VO { + + /** 日志编号" */ + @ExcelProperty("日志编号") + private Long id; + + /** 链路追踪编号" */ + private String traceId; + + /** 用户编号" */ + @Trans(type = TransType.SIMPLE, target = AdminUserDO.class, fields = "nickname", ref = "userName") + private Long userId; + + /** 用户昵称", example = "芋艿 */ + @ExcelProperty("操作人") + private String userName; + + /** 操作模块类型", example = "订单 */ + @ExcelProperty("操作模块类型") + private String type; + + /** 操作名", example = "创建订单 */ + @ExcelProperty("操作名") + private String subType; + + /** 操作模块业务编号" */ + @ExcelProperty("操作模块业务编号") + private Long bizId; + + /** 操作明细 */ + private String action; + + /** 拓展字段 */ + private String extra; + + /** 请求方法名" */ + @NotEmpty(message = "请求方法名不能为空") + private String requestMethod; + + /** 请求地址", example = "/xxx/yyy */ + private String requestUrl; + + /** 用户 IP" */ + private String userIp; + + /** 浏览器 UserAgent" */ + private String userAgent; + + /** + * 创建时间 + */ + private LocalDateTime createTime; +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/mail/MailAccountController.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/mail/MailAccountController.java new file mode 100644 index 0000000..6c18a73 --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/mail/MailAccountController.java @@ -0,0 +1,91 @@ +package com.tashow.cloud.system.controller.admin.mail; + +import com.tashow.cloud.common.pojo.CommonResult; +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.common.util.object.BeanUtils; +import com.tashow.cloud.system.controller.admin.mail.vo.account.MailAccountPageReqVO; +import com.tashow.cloud.system.controller.admin.mail.vo.account.MailAccountRespVO; +import com.tashow.cloud.system.controller.admin.mail.vo.account.MailAccountSaveReqVO; +import com.tashow.cloud.system.controller.admin.mail.vo.account.MailAccountSimpleRespVO; +import com.tashow.cloud.system.dal.dataobject.mail.MailAccountDO; +import com.tashow.cloud.system.service.mail.MailAccountService; +import jakarta.annotation.Resource; +import jakarta.validation.Valid; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +import static com.tashow.cloud.common.pojo.CommonResult.success; + +/** + * 管理后台 - 邮箱账号 + */ +@RestController +@RequestMapping("/system/mail-account") +public class MailAccountController { + + @Resource + private MailAccountService mailAccountService; + + /** + * 创建邮箱账号 + */ + @PostMapping("/create") + @PreAuthorize("@ss.hasPermission('system:mail-account:create')") + public CommonResult createMailAccount( + @Valid @RequestBody MailAccountSaveReqVO createReqVO) { + return success(mailAccountService.createMailAccount(createReqVO)); + } + + /** + * 修改邮箱账号 + */ + @PutMapping("/update") + @PreAuthorize("@ss.hasPermission('system:mail-account:update')") + public CommonResult updateMailAccount( + @Valid @RequestBody MailAccountSaveReqVO updateReqVO) { + mailAccountService.updateMailAccount(updateReqVO); + return success(true); + } + + /** + * 删除邮箱账号 + */ + @DeleteMapping("/delete") + @PreAuthorize("@ss.hasPermission('system:mail-account:delete')") + public CommonResult deleteMailAccount(@RequestParam Long id) { + mailAccountService.deleteMailAccount(id); + return success(true); + } + + /** + * 获得邮箱账号 + */ + @GetMapping("/get") + @PreAuthorize("@ss.hasPermission('system:mail-account:query')") + public CommonResult getMailAccount(@RequestParam("id") Long id) { + MailAccountDO account = mailAccountService.getMailAccount(id); + return success(BeanUtils.toBean(account, MailAccountRespVO.class)); + } + + /** + * 获得邮箱账号分页 + */ + @GetMapping("/page") + @PreAuthorize("@ss.hasPermission('system:mail-account:query')") + public CommonResult> getMailAccountPage( + @Valid MailAccountPageReqVO pageReqVO) { + PageResult pageResult = mailAccountService.getMailAccountPage(pageReqVO); + return success(BeanUtils.toBean(pageResult, MailAccountRespVO.class)); + } + + /** + * 获得邮箱账号精简列表 + */ + @GetMapping({"/list-all-simple", "simple-list"}) + public CommonResult> getSimpleMailAccountList() { + List list = mailAccountService.getMailAccountList(); + return success(BeanUtils.toBean(list, MailAccountSimpleRespVO.class)); + } +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/mail/MailLogController.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/mail/MailLogController.java new file mode 100644 index 0000000..0697c6d --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/mail/MailLogController.java @@ -0,0 +1,49 @@ +package com.tashow.cloud.system.controller.admin.mail; + +import com.tashow.cloud.common.pojo.CommonResult; +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.common.util.object.BeanUtils; +import com.tashow.cloud.system.controller.admin.mail.vo.log.MailLogPageReqVO; +import com.tashow.cloud.system.controller.admin.mail.vo.log.MailLogRespVO; +import com.tashow.cloud.system.dal.dataobject.mail.MailLogDO; +import com.tashow.cloud.system.service.mail.MailLogService; +import jakarta.annotation.Resource; +import jakarta.validation.Valid; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import static com.tashow.cloud.common.pojo.CommonResult.success; + +/** + * 管理后台 - 邮件日志 + */ +@RestController +@RequestMapping("/system/mail-log") +public class MailLogController { + + @Resource + private MailLogService mailLogService; + + /** + * 获得邮箱日志分页 + */ + @GetMapping("/page") + @PreAuthorize("@ss.hasPermission('system:mail-log:query')") + public CommonResult> getMailLogPage(@Valid MailLogPageReqVO pageVO) { + PageResult pageResult = mailLogService.getMailLogPage(pageVO); + return success(BeanUtils.toBean(pageResult, MailLogRespVO.class)); + } + + /** + * 获得邮箱日志 + */ + @GetMapping("/get") + @PreAuthorize("@ss.hasPermission('system:mail-log:query')") + public CommonResult getMailTemplate(@RequestParam("id") Long id) { + MailLogDO log = mailLogService.getMailLog(id); + return success(BeanUtils.toBean(log, MailLogRespVO.class)); + } +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/mail/MailTemplateController.http b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/mail/MailTemplateController.http new file mode 100644 index 0000000..9ad2ed2 --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/mail/MailTemplateController.http @@ -0,0 +1,14 @@ +### 请求 /system/mail-template/send-mail 接口 => 成功 +POST {{baseUrl}}/system/mail-template/send-mail +Authorization: Bearer {{token}} +Content-Type: application/json +tenant-id: {{adminTenantId}} + +{ + "templateCode": "test_01", + "mail": "7685413@qq.com", + "templateParams": { + "key01": "value01", + "key02": "value02" + } +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/mail/MailTemplateController.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/mail/MailTemplateController.java new file mode 100644 index 0000000..d10bfe7 --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/mail/MailTemplateController.java @@ -0,0 +1,106 @@ +package com.tashow.cloud.system.controller.admin.mail; + +import com.tashow.cloud.common.pojo.CommonResult; +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.common.util.object.BeanUtils; +import com.tashow.cloud.system.controller.admin.mail.vo.template.*; +import com.tashow.cloud.system.dal.dataobject.mail.MailTemplateDO; +import com.tashow.cloud.system.service.mail.MailSendService; +import com.tashow.cloud.system.service.mail.MailTemplateService; +import jakarta.annotation.Resource; +import jakarta.validation.Valid; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +import static com.tashow.cloud.common.pojo.CommonResult.success; +import static com.tashow.cloud.web.web.core.util.WebFrameworkUtils.getLoginUserId; + +/** + * 管理后台 - 邮件模版 + */ +@RestController +@RequestMapping("/system/mail-template") +public class MailTemplateController { + + @Resource + private MailTemplateService mailTempleService; + @Resource + private MailSendService mailSendService; + + /** + * 创建邮件模版 + */ + @PostMapping("/create") + @PreAuthorize("@ss.hasPermission('system:mail-template:create')") + public CommonResult createMailTemplate( + @Valid @RequestBody MailTemplateSaveReqVO createReqVO) { + return success(mailTempleService.createMailTemplate(createReqVO)); + } + + /** + * 修改邮件模版 + */ + @PutMapping("/update") + @PreAuthorize("@ss.hasPermission('system:mail-template:update')") + public CommonResult updateMailTemplate( + @Valid @RequestBody MailTemplateSaveReqVO updateReqVO) { + mailTempleService.updateMailTemplate(updateReqVO); + return success(true); + } + + /** + * 删除邮件模版 + */ + @DeleteMapping("/delete") + @PreAuthorize("@ss.hasPermission('system:mail-template:delete')") + public CommonResult deleteMailTemplate(@RequestParam("id") Long id) { + mailTempleService.deleteMailTemplate(id); + return success(true); + } + + /** + * 获得邮件模版 + */ + @GetMapping("/get") + @PreAuthorize("@ss.hasPermission('system:mail-template:query')") + public CommonResult getMailTemplate(@RequestParam("id") Long id) { + MailTemplateDO template = mailTempleService.getMailTemplate(id); + return success(BeanUtils.toBean(template, MailTemplateRespVO.class)); + } + + /** + * 获得邮件模版分页 + */ + @GetMapping("/page") + @PreAuthorize("@ss.hasPermission('system:mail-template:query')") + public CommonResult> getMailTemplatePage( + @Valid MailTemplatePageReqVO pageReqVO) { + PageResult pageResult = mailTempleService.getMailTemplatePage(pageReqVO); + return success(BeanUtils.toBean(pageResult, MailTemplateRespVO.class)); + } + + /** + * 获得邮件模版精简列表 + */ + @GetMapping({"/list-all-simple", "simple-list"}) + public CommonResult> getSimpleTemplateList() { + List list = mailTempleService.getMailTemplateList(); + return success(BeanUtils.toBean(list, MailTemplateSimpleRespVO.class)); + } + + /** + * 发送短信 + */ + @PostMapping("/send-mail") + @PreAuthorize("@ss.hasPermission('system:mail-template:send-mail')") + public CommonResult sendMail(@Valid @RequestBody MailTemplateSendReqVO sendReqVO) { + return success( + mailSendService.sendSingleMailToAdmin( + sendReqVO.getMail(), + getLoginUserId(), + sendReqVO.getTemplateCode(), + sendReqVO.getTemplateParams())); + } +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/mail/vo/account/MailAccountPageReqVO.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/mail/vo/account/MailAccountPageReqVO.java new file mode 100644 index 0000000..5db7ff7 --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/mail/vo/account/MailAccountPageReqVO.java @@ -0,0 +1,25 @@ +package com.tashow.cloud.system.controller.admin.mail.vo.account; + +import com.tashow.cloud.common.pojo.PageParam; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; + +/** + * 管理后台 - 邮箱账号分页 Request VO + */ +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class MailAccountPageReqVO extends PageParam { + + /** + * 邮箱" + */ + private String mail; + + /** + * 用户名" + */ + private String username; +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/mail/vo/account/MailAccountRespVO.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/mail/vo/account/MailAccountRespVO.java new file mode 100644 index 0000000..e85e798 --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/mail/vo/account/MailAccountRespVO.java @@ -0,0 +1,57 @@ +package com.tashow.cloud.system.controller.admin.mail.vo.account; + +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * 管理后台 - 邮箱账号 Response VO + */ +@Data +public class MailAccountRespVO { + + /** + * 编号" + */ + private Long id; + + /** + * 邮箱" + */ + private String mail; + + /** + * 用户名" + */ + private String username; + + /** + * 密码" + */ + private String password; + + /** + * SMTP 服务器域名" + */ + private String host; + + /** + * SMTP 服务器端口" + */ + private Integer port; + + /** + * 是否开启 ssl" + */ + private Boolean sslEnable; + + /** + * 是否开启 starttls" + */ + private Boolean starttlsEnable; + + /** + * 创建时间 + */ + private LocalDateTime createTime; +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/mail/vo/account/MailAccountSaveReqVO.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/mail/vo/account/MailAccountSaveReqVO.java new file mode 100644 index 0000000..be704d8 --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/mail/vo/account/MailAccountSaveReqVO.java @@ -0,0 +1,42 @@ +package com.tashow.cloud.system.controller.admin.mail.vo.account; + +import jakarta.validation.constraints.Email; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +/** 管理后台 - 邮箱账号创建/修改 Request VO */ +@Data +public class MailAccountSaveReqVO { + + /** 编号 */ + private Long id; + + /** 邮箱" */ + @NotNull(message = "邮箱不能为空") + @Email(message = "必须是 Email 格式") + private String mail; + + /** 用户名" */ + @NotNull(message = "用户名不能为空") + private String username; + + /** 密码" */ + @NotNull(message = "密码必填") + private String password; + + /** SMTP 服务器域名" */ + @NotNull(message = "SMTP 服务器域名不能为空") + private String host; + + /** SMTP 服务器端口" */ + @NotNull(message = "SMTP 服务器端口不能为空") + private Integer port; + + /** 是否开启 ssl" */ + @NotNull(message = "是否开启 ssl 必填") + private Boolean sslEnable; + + /** 是否开启 starttls" */ + @NotNull(message = "是否开启 starttls 必填") + private Boolean starttlsEnable; +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/mail/vo/account/MailAccountSimpleRespVO.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/mail/vo/account/MailAccountSimpleRespVO.java new file mode 100644 index 0000000..36894bf --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/mail/vo/account/MailAccountSimpleRespVO.java @@ -0,0 +1,20 @@ +package com.tashow.cloud.system.controller.admin.mail.vo.account; + +import lombok.Data; + +/** + * 管理后台 - 邮箱账号的精简 Response VO + */ +@Data +public class MailAccountSimpleRespVO { + + /** + * 邮箱编号" + */ + private Long id; + + /** + * 邮箱" + */ + private String mail; +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/mail/vo/log/MailLogPageReqVO.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/mail/vo/log/MailLogPageReqVO.java new file mode 100644 index 0000000..51a85fc --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/mail/vo/log/MailLogPageReqVO.java @@ -0,0 +1,39 @@ +package com.tashow.cloud.system.controller.admin.mail.vo.log; + +import static com.tashow.cloud.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND; + +import com.tashow.cloud.common.pojo.PageParam; +import java.time.LocalDateTime; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; +import org.springframework.format.annotation.DateTimeFormat; + +/** 管理后台 - 邮箱日志分页 Request VO */ +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class MailLogPageReqVO extends PageParam { + + /** 用户编号 */ + private Long userId; + + /** 用户类型,参见 UserTypeEnum 枚举 */ + private Integer userType; + + /** 接收邮箱地址,模糊匹配 */ + private String toMail; + + /** 邮箱账号编号 */ + private Long accountId; + + /** 模板编号 */ + private Long templateId; + + /** 发送状态,参见 MailSendStatusEnum 枚举 */ + private Integer sendStatus; + + /** 发送时间 */ + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) + private LocalDateTime[] sendTime; +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/mail/vo/log/MailLogRespVO.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/mail/vo/log/MailLogRespVO.java new file mode 100644 index 0000000..8416762 --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/mail/vo/log/MailLogRespVO.java @@ -0,0 +1,98 @@ +package com.tashow.cloud.system.controller.admin.mail.vo.log; + +import lombok.Data; + +import java.time.LocalDateTime; +import java.util.Map; + +/** + * 管理后台 - 邮件日志 Response VO + */ +@Data +public class MailLogRespVO { + + /** + * 编号" + */ + private Long id; + + /** + * 用户编号 + */ + private Long userId; + + /** + * 用户类型,参见 UserTypeEnum 枚举 + */ + private Byte userType; + + /** + * 接收邮箱地址" + */ + private String toMail; + + /** + * 邮箱账号编号" + */ + private Long accountId; + + /** + * 发送邮箱地址" + */ + private String fromMail; + + /** + * 模板编号" + */ + private Long templateId; + + /** + * 模板编码" + */ + private String templateCode; + + /** + * 模版发送人名称 + */ + private String templateNickname; + + /** + * 邮件标题", example = "测试标题 + */ + private String templateTitle; + + /** + * 邮件内容", example = "测试内容 + */ + private String templateContent; + + /** + * 邮件参数 + */ + private Map templateParams; + + /** + * 发送状态,参见 MailSendStatusEnum 枚举" + */ + private Byte sendStatus; + + /** + * 发送时间 + */ + private LocalDateTime sendTime; + + /** + * 发送返回的消息 ID + */ + private String sendMessageId; + + /** + * 发送异常 + */ + private String sendException; + + /** + * 创建时间 + */ + private LocalDateTime createTime; +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/mail/vo/template/MailTemplatePageReqVO.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/mail/vo/template/MailTemplatePageReqVO.java new file mode 100644 index 0000000..ff0ca3f --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/mail/vo/template/MailTemplatePageReqVO.java @@ -0,0 +1,46 @@ +package com.tashow.cloud.system.controller.admin.mail.vo.template; + +import com.tashow.cloud.common.pojo.PageParam; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; +import org.springframework.format.annotation.DateTimeFormat; + +import java.time.LocalDateTime; + +import static com.tashow.cloud.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND; + +/** + * 管理后台 - 邮件模版分页 Request VO + */ +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class MailTemplatePageReqVO extends PageParam { + + /** + * 状态,参见 CommonStatusEnum 枚举 + */ + private Integer status; + + /** + * 标识,模糊匹配 + */ + private String code; + + /** + * 名称,模糊匹配 + */ + private String name; + + /** + * 账号编号 + */ + private Long accountId; + + /** + * 创建时间 + */ + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) + private LocalDateTime[] createTime; +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/mail/vo/template/MailTemplateRespVO.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/mail/vo/template/MailTemplateRespVO.java new file mode 100644 index 0000000..374ca3f --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/mail/vo/template/MailTemplateRespVO.java @@ -0,0 +1,68 @@ +package com.tashow.cloud.system.controller.admin.mail.vo.template; + +import lombok.Data; + +import java.time.LocalDateTime; +import java.util.List; + +/** + * 管理后台 - 邮件末班 Response VO + */ +@Data +public class MailTemplateRespVO { + + /** + * 编号" + */ + private Long id; + + /** + * 模版名称", example = "测试名字 + */ + private String name; + + /** + * 模版编号" + */ + private String code; + + /** + * 发送的邮箱账号编号" + */ + private Long accountId; + + /** + * 发送人名称 + */ + private String nickname; + + /** + * 标题", example = "注册成功 + */ + private String title; + + /** + * 内容", example = "你好,注册成功啦 + */ + private String content; + + /** + * 参数数组 + */ + private List params; + + /** + * 状态,参见 CommonStatusEnum 枚举" + */ + private Integer status; + + /** + * 备注 + */ + private String remark; + + /** + * 创建时间 + */ + private LocalDateTime createTime; +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/mail/vo/template/MailTemplateSaveReqVO.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/mail/vo/template/MailTemplateSaveReqVO.java new file mode 100644 index 0000000..f24fdcb --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/mail/vo/template/MailTemplateSaveReqVO.java @@ -0,0 +1,63 @@ +package com.tashow.cloud.system.controller.admin.mail.vo.template; + +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +/** + * 管理后台 - 邮件模版创建/修改 Request VO + */ +@Data +public class MailTemplateSaveReqVO { + + /** + * 编号 + */ + private Long id; + + /** + * 模版名称", example = "测试名字 + */ + @NotNull(message = "名称不能为空") + private String name; + + /** + * 模版编号" + */ + @NotNull(message = "模版编号不能为空") + private String code; + + /** + * 发送的邮箱账号编号" + */ + @NotNull(message = "发送的邮箱账号编号不能为空") + private Long accountId; + + /** + * 发送人名称 + */ + private String nickname; + + /** + * 标题", example = "注册成功 + */ + @NotEmpty(message = "标题不能为空") + private String title; + + /** + * 内容", example = "你好,注册成功啦 + */ + @NotEmpty(message = "内容不能为空") + private String content; + + /** + * 状态,参见 CommonStatusEnum 枚举" + */ + @NotNull(message = "状态不能为空") + private Integer status; + + /** + * 备注 + */ + private String remark; +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/mail/vo/template/MailTemplateSendReqVO.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/mail/vo/template/MailTemplateSendReqVO.java new file mode 100644 index 0000000..3ecfe6c --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/mail/vo/template/MailTemplateSendReqVO.java @@ -0,0 +1,31 @@ +package com.tashow.cloud.system.controller.admin.mail.vo.template; + +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +import java.util.Map; + +/** + * 管理后台 - 邮件发送 Req VO + */ +@Data +public class MailTemplateSendReqVO { + + /** + * 接收邮箱" + */ + @NotEmpty(message = "接收邮箱不能为空") + private String mail; + + /** + * 模板编码" + */ + @NotNull(message = "模板编码不能为空") + private String templateCode; + + /** + * 模板参数 + */ + private Map templateParams; +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/mail/vo/template/MailTemplateSimpleRespVO.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/mail/vo/template/MailTemplateSimpleRespVO.java new file mode 100644 index 0000000..1d2b97b --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/mail/vo/template/MailTemplateSimpleRespVO.java @@ -0,0 +1,20 @@ +package com.tashow.cloud.system.controller.admin.mail.vo.template; + +import lombok.Data; + +/** + * 管理后台 - 邮件模版的精简 Response VO + */ +@Data +public class MailTemplateSimpleRespVO { + + /** + * 模版编号" + */ + private Long id; + + /** + * 模版名字", example = "哒哒哒 + */ + private String name; +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/notice/NoticeController.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/notice/NoticeController.java new file mode 100644 index 0000000..2bf4fc9 --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/notice/NoticeController.java @@ -0,0 +1,99 @@ +package com.tashow.cloud.system.controller.admin.notice; + +import cn.hutool.core.lang.Assert; +import com.tashow.cloud.common.enums.UserTypeEnum; +import com.tashow.cloud.common.pojo.CommonResult; +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.common.util.object.BeanUtils; +import com.tashow.cloud.infraapi.api.websocket.WebSocketSenderApi; +import com.tashow.cloud.system.controller.admin.notice.vo.NoticePageReqVO; +import com.tashow.cloud.system.controller.admin.notice.vo.NoticeRespVO; +import com.tashow.cloud.system.controller.admin.notice.vo.NoticeSaveReqVO; +import com.tashow.cloud.system.dal.dataobject.notice.NoticeDO; +import com.tashow.cloud.system.service.notice.NoticeService; +import jakarta.annotation.Resource; +import jakarta.validation.Valid; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import static com.tashow.cloud.common.pojo.CommonResult.success; + +/** + * 管理后台 - 通知公告 + */ +@RestController +@RequestMapping("/system/notice") +@Validated +public class NoticeController { + + @Resource + private NoticeService noticeService; + + @Resource + private WebSocketSenderApi webSocketSenderApi; + + /** + * 创建通知公告 + */ + @PostMapping("/create") + @PreAuthorize("@ss.hasPermission('system:notice:create')") + public CommonResult createNotice(@Valid @RequestBody NoticeSaveReqVO createReqVO) { + Long noticeId = noticeService.createNotice(createReqVO); + return success(noticeId); + } + + /** + * 修改通知公告 + */ + @PutMapping("/update") + @PreAuthorize("@ss.hasPermission('system:notice:update')") + public CommonResult updateNotice(@Valid @RequestBody NoticeSaveReqVO updateReqVO) { + noticeService.updateNotice(updateReqVO); + return success(true); + } + + /** + * 删除通知公告 + */ + @DeleteMapping("/delete") + @PreAuthorize("@ss.hasPermission('system:notice:delete')") + public CommonResult deleteNotice(@RequestParam("id") Long id) { + noticeService.deleteNotice(id); + return success(true); + } + + /** + * 获取通知公告列表 + */ + @GetMapping("/page") + @PreAuthorize("@ss.hasPermission('system:notice:query')") + public CommonResult> getNoticePage( + @Validated NoticePageReqVO pageReqVO) { + PageResult pageResult = noticeService.getNoticePage(pageReqVO); + return success(BeanUtils.toBean(pageResult, NoticeRespVO.class)); + } + + /** + * 获得通知公告 + */ + @GetMapping("/get") + @PreAuthorize("@ss.hasPermission('system:notice:query')") + public CommonResult getNotice(@RequestParam("id") Long id) { + NoticeDO notice = noticeService.getNotice(id); + return success(BeanUtils.toBean(notice, NoticeRespVO.class)); + } + + /** + * 推送通知公告" + */ + @PostMapping("/push") + @PreAuthorize("@ss.hasPermission('system:notice:update')") + public CommonResult push(@RequestParam("id") Long id) { + NoticeDO notice = noticeService.getNotice(id); + Assert.notNull(notice, "公告不能为空"); + // 通过 websocket 推送给在线的用户 + webSocketSenderApi.sendObject(UserTypeEnum.ADMIN.getValue(), "notice-push", notice); + return success(true); + } +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/notice/vo/NoticePageReqVO.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/notice/vo/NoticePageReqVO.java new file mode 100644 index 0000000..a57ada8 --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/notice/vo/NoticePageReqVO.java @@ -0,0 +1,17 @@ +package com.tashow.cloud.system.controller.admin.notice.vo; + +import com.tashow.cloud.common.pojo.PageParam; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** 管理后台 - 通知公告分页 Request VO */ +@Data +@EqualsAndHashCode(callSuper = true) +public class NoticePageReqVO extends PageParam { + + /** 通知公告名称,模糊匹配 */ + private String title; + + /** 展示状态,参见 CommonStatusEnum 枚举类 */ + private Integer status; +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/notice/vo/NoticeRespVO.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/notice/vo/NoticeRespVO.java new file mode 100644 index 0000000..f8f04c8 --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/notice/vo/NoticeRespVO.java @@ -0,0 +1,42 @@ +package com.tashow.cloud.system.controller.admin.notice.vo; + +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * 管理后台 - 通知公告信息 Response VO + */ +@Data +public class NoticeRespVO { + + /** + * 通知公告序号" + */ + private Long id; + + /** + * 公告标题", example = "小博主 + */ + private String title; + + /** + * 公告类型", example = "小博主 + */ + private Integer type; + + /** + * 公告内容", example = "半生编码 + */ + private String content; + + /** + * 状态,参见 CommonStatusEnum 枚举类" + */ + private Integer status; + + /** + * 创建时间", example = "时间戳格式 + */ + private LocalDateTime createTime; +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/notice/vo/NoticeSaveReqVO.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/notice/vo/NoticeSaveReqVO.java new file mode 100644 index 0000000..9b1ebe0 --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/notice/vo/NoticeSaveReqVO.java @@ -0,0 +1,29 @@ +package com.tashow.cloud.system.controller.admin.notice.vo; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Size; +import lombok.Data; + +/** 管理后台 - 通知公告创建/修改 Request VO */ +@Data +public class NoticeSaveReqVO { + + /** 岗位公告编号 */ + private Long id; + + /** 公告标题", example = "小博主 */ + @NotBlank(message = "公告标题不能为空") + @Size(max = 50, message = "公告标题不能超过50个字符") + private String title; + + /** 公告类型", example = "小博主 */ + @NotNull(message = "公告类型不能为空") + private Integer type; + + /** 公告内容", example = "半生编码 */ + private String content; + + /** 状态,参见 CommonStatusEnum 枚举类" */ + private Integer status; +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/notify/NotifyMessageController.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/notify/NotifyMessageController.java new file mode 100644 index 0000000..d044cc1 --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/notify/NotifyMessageController.java @@ -0,0 +1,114 @@ +package com.tashow.cloud.system.controller.admin.notify; + +import com.tashow.cloud.common.enums.UserTypeEnum; +import com.tashow.cloud.common.pojo.CommonResult; +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.common.util.object.BeanUtils; +import com.tashow.cloud.system.controller.admin.notify.vo.message.NotifyMessageMyPageReqVO; +import com.tashow.cloud.system.controller.admin.notify.vo.message.NotifyMessagePageReqVO; +import com.tashow.cloud.system.controller.admin.notify.vo.message.NotifyMessageRespVO; +import com.tashow.cloud.system.dal.dataobject.notify.NotifyMessageDO; +import com.tashow.cloud.system.service.notify.NotifyMessageService; +import com.tashow.cloud.web.apilog.core.annotation.ApiAccessLog; +import jakarta.annotation.Resource; +import jakarta.validation.Valid; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +import static com.tashow.cloud.common.pojo.CommonResult.success; +import static com.tashow.cloud.web.web.core.util.WebFrameworkUtils.getLoginUserId; + +/** + * 管理后台 - 我的站内信 + */ +@RestController +@RequestMapping("/system/notify-message") +@Validated +public class NotifyMessageController { + + @Resource + private NotifyMessageService notifyMessageService; + + // ========== 管理所有的站内信 ========== + + /** + * 获得站内信 + */ + @GetMapping("/get") + @PreAuthorize("@ss.hasPermission('system:notify-message:query')") + public CommonResult getNotifyMessage(@RequestParam("id") Long id) { + NotifyMessageDO message = notifyMessageService.getNotifyMessage(id); + return success(BeanUtils.toBean(message, NotifyMessageRespVO.class)); + } + + /** + * 获得站内信分页 + */ + @GetMapping("/page") + @PreAuthorize("@ss.hasPermission('system:notify-message:query')") + public CommonResult> getNotifyMessagePage( + @Valid NotifyMessagePageReqVO pageVO) { + PageResult pageResult = notifyMessageService.getNotifyMessagePage(pageVO); + return success(BeanUtils.toBean(pageResult, NotifyMessageRespVO.class)); + } + + // ========== 查看自己的站内信 ========== + + /** + * 获得我的站内信分页 + */ + @GetMapping("/my-page") + public CommonResult> getMyMyNotifyMessagePage( + @Valid NotifyMessageMyPageReqVO pageVO) { + PageResult pageResult = + notifyMessageService.getMyMyNotifyMessagePage( + pageVO, getLoginUserId(), UserTypeEnum.ADMIN.getValue()); + return success(BeanUtils.toBean(pageResult, NotifyMessageRespVO.class)); + } + + /** + * 标记站内信为已读 + */ + @PutMapping("/update-read") + public CommonResult updateNotifyMessageRead(@RequestParam("ids") List ids) { + notifyMessageService.updateNotifyMessageRead( + ids, getLoginUserId(), UserTypeEnum.ADMIN.getValue()); + return success(Boolean.TRUE); + } + + /** + * 标记所有站内信为已读 + */ + @PutMapping("/update-all-read") + public CommonResult updateAllNotifyMessageRead() { + notifyMessageService.updateAllNotifyMessageRead( + getLoginUserId(), UserTypeEnum.ADMIN.getValue()); + return success(Boolean.TRUE); + } + + /** + * 获取当前用户的最新站内信列表,默认 10 条 + */ + @GetMapping("/get-unread-list") + public CommonResult> getUnreadNotifyMessageList( + @RequestParam(name = "size", defaultValue = "10") Integer size) { + List list = + notifyMessageService.getUnreadNotifyMessageList( + getLoginUserId(), UserTypeEnum.ADMIN.getValue(), size); + return success(BeanUtils.toBean(list, NotifyMessageRespVO.class)); + } + + /** + * 获得当前用户的未读站内信数量 + */ + @GetMapping("/get-unread-count") + @ApiAccessLog(enable = false) // 由于前端会不断轮询该接口,记录日志没有意义 + public CommonResult getUnreadNotifyMessageCount() { + return success( + notifyMessageService.getUnreadNotifyMessageCount( + getLoginUserId(), UserTypeEnum.ADMIN.getValue())); + } +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/notify/NotifyTemplateController.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/notify/NotifyTemplateController.java new file mode 100644 index 0000000..4b78fe1 --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/notify/NotifyTemplateController.java @@ -0,0 +1,104 @@ +package com.tashow.cloud.system.controller.admin.notify; + +import com.tashow.cloud.common.enums.UserTypeEnum; +import com.tashow.cloud.common.pojo.CommonResult; +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.common.util.object.BeanUtils; +import com.tashow.cloud.system.controller.admin.notify.vo.template.NotifyTemplatePageReqVO; +import com.tashow.cloud.system.controller.admin.notify.vo.template.NotifyTemplateRespVO; +import com.tashow.cloud.system.controller.admin.notify.vo.template.NotifyTemplateSaveReqVO; +import com.tashow.cloud.system.controller.admin.notify.vo.template.NotifyTemplateSendReqVO; +import com.tashow.cloud.system.dal.dataobject.notify.NotifyTemplateDO; +import com.tashow.cloud.system.service.notify.NotifySendService; +import com.tashow.cloud.system.service.notify.NotifyTemplateService; +import jakarta.annotation.Resource; +import jakarta.validation.Valid; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import static com.tashow.cloud.common.pojo.CommonResult.success; + +/** + * 管理后台 - 站内信模版 + */ +@RestController +@RequestMapping("/system/notify-template") +@Validated +public class NotifyTemplateController { + + @Resource + private NotifyTemplateService notifyTemplateService; + + @Resource + private NotifySendService notifySendService; + + /** + * 创建站内信模版 + */ + @PostMapping("/create") + @PreAuthorize("@ss.hasPermission('system:notify-template:create')") + public CommonResult createNotifyTemplate( + @Valid @RequestBody NotifyTemplateSaveReqVO createReqVO) { + return success(notifyTemplateService.createNotifyTemplate(createReqVO)); + } + + /** + * 更新站内信模版 + */ + @PutMapping("/update") + @PreAuthorize("@ss.hasPermission('system:notify-template:update')") + public CommonResult updateNotifyTemplate( + @Valid @RequestBody NotifyTemplateSaveReqVO updateReqVO) { + notifyTemplateService.updateNotifyTemplate(updateReqVO); + return success(true); + } + + /** + * 删除站内信模版 + */ + @DeleteMapping("/delete") + @PreAuthorize("@ss.hasPermission('system:notify-template:delete')") + public CommonResult deleteNotifyTemplate(@RequestParam("id") Long id) { + notifyTemplateService.deleteNotifyTemplate(id); + return success(true); + } + + /** + * 获得站内信模版 + */ + @GetMapping("/get") + @PreAuthorize("@ss.hasPermission('system:notify-template:query')") + public CommonResult getNotifyTemplate(@RequestParam("id") Long id) { + NotifyTemplateDO template = notifyTemplateService.getNotifyTemplate(id); + return success(BeanUtils.toBean(template, NotifyTemplateRespVO.class)); + } + + /** + * 获得站内信模版分页 + */ + @GetMapping("/page") + @PreAuthorize("@ss.hasPermission('system:notify-template:query')") + public CommonResult> getNotifyTemplatePage( + @Valid NotifyTemplatePageReqVO pageVO) { + PageResult pageResult = notifyTemplateService.getNotifyTemplatePage(pageVO); + return success(BeanUtils.toBean(pageResult, NotifyTemplateRespVO.class)); + } + + /** + * 发送站内信 + */ + @PostMapping("/send-notify") + @PreAuthorize("@ss.hasPermission('system:notify-template:send-notify')") + public CommonResult sendNotify(@Valid @RequestBody NotifyTemplateSendReqVO sendReqVO) { + if (UserTypeEnum.MEMBER.getValue().equals(sendReqVO.getUserType())) { + return success( + notifySendService.sendSingleNotifyToMember( + sendReqVO.getUserId(), sendReqVO.getTemplateCode(), sendReqVO.getTemplateParams())); + } else { + return success( + notifySendService.sendSingleNotifyToAdmin( + sendReqVO.getUserId(), sendReqVO.getTemplateCode(), sendReqVO.getTemplateParams())); + } + } +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/notify/vo/message/NotifyMessageMyPageReqVO.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/notify/vo/message/NotifyMessageMyPageReqVO.java new file mode 100644 index 0000000..fa07d73 --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/notify/vo/message/NotifyMessageMyPageReqVO.java @@ -0,0 +1,33 @@ +package com.tashow.cloud.system.controller.admin.notify.vo.message; + +import com.tashow.cloud.common.pojo.PageParam; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; +import org.springframework.format.annotation.DateTimeFormat; + +import java.time.LocalDateTime; + +import static com.tashow.cloud.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND; + +/** + * 管理后台 - 站内信分页 Request VO + */ +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class NotifyMessageMyPageReqVO extends PageParam { + + /** + * 是否已读 + */ + private Boolean readStatus; + + /** + * 创建时间 + */ + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) + private LocalDateTime[] createTime; + +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/notify/vo/message/NotifyMessagePageReqVO.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/notify/vo/message/NotifyMessagePageReqVO.java new file mode 100644 index 0000000..cbec8e2 --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/notify/vo/message/NotifyMessagePageReqVO.java @@ -0,0 +1,48 @@ +package com.tashow.cloud.system.controller.admin.notify.vo.message; + +import com.tashow.cloud.common.pojo.PageParam; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; +import org.springframework.format.annotation.DateTimeFormat; + +import java.time.LocalDateTime; + +import static com.tashow.cloud.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND; + +/** + * 管理后台 - 站内信分页 Request VO + */ +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class NotifyMessagePageReqVO extends PageParam { + + /** + * 用户编号 + */ + private Long userId; + + /** + * 用户类型 + */ + private Integer userType; + + /** + * 模板编码 + */ + private String templateCode; + + /** + * 模版类型 + */ + private Integer templateType; + + /** + * 创建时间 + */ + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) + private LocalDateTime[] createTime; + +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/notify/vo/message/NotifyMessageRespVO.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/notify/vo/message/NotifyMessageRespVO.java new file mode 100644 index 0000000..31adf47 --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/notify/vo/message/NotifyMessageRespVO.java @@ -0,0 +1,50 @@ +package com.tashow.cloud.system.controller.admin.notify.vo.message; + +import java.time.LocalDateTime; +import java.util.Map; +import lombok.Data; + +/** 管理后台 - 站内信 Response VO */ +@Data +public class NotifyMessageRespVO { + + /** ID" */ + private Long id; + + /** 用户编号" */ + private Long userId; + + /** 用户类型,参见 UserTypeEnum 枚举" */ + private Byte userType; + + /** 模版编号" */ + private Long templateId; + + /** 模板编码" */ + private String templateCode; + + /** 模版发送人名称", example = "芋艿 */ + private String templateNickname; + + /** 模版内容", example = "测试内容 */ + private String templateContent; + + /** 模版类型" */ + private Integer templateType; + + /** + * 模版参数 + */ + private Map templateParams; + + /** 是否已读" */ + private Boolean readStatus; + + /** 阅读时间 */ + private LocalDateTime readTime; + + /** + * 创建时间 + */ + private LocalDateTime createTime; +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/notify/vo/template/NotifyTemplatePageReqVO.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/notify/vo/template/NotifyTemplatePageReqVO.java new file mode 100644 index 0000000..22cddb2 --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/notify/vo/template/NotifyTemplatePageReqVO.java @@ -0,0 +1,43 @@ +package com.tashow.cloud.system.controller.admin.notify.vo.template; + +import com.tashow.cloud.common.pojo.PageParam; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; +import org.springframework.format.annotation.DateTimeFormat; + +import java.time.LocalDateTime; + +import static com.tashow.cloud.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND; + +/** + * 管理后台 - 站内信模版分页 Request VO + */ +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class NotifyTemplatePageReqVO extends PageParam { + + /** + * 模版编码 + */ + private String code; + + /** + * 模版名称 + */ + private String name; + + /** + * 状态,参见 CommonStatusEnum 枚举类 + */ + private Integer status; + + /** + * 创建时间 + */ + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) + private LocalDateTime[] createTime; + +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/notify/vo/template/NotifyTemplateRespVO.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/notify/vo/template/NotifyTemplateRespVO.java new file mode 100644 index 0000000..dbd827f --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/notify/vo/template/NotifyTemplateRespVO.java @@ -0,0 +1,42 @@ +package com.tashow.cloud.system.controller.admin.notify.vo.template; + +import java.time.LocalDateTime; +import java.util.List; +import lombok.Data; + +/** 管理后台 - 站内信模版 Response VO */ +@Data +public class NotifyTemplateRespVO { + + /** ID" */ + private Long id; + + /** 模版名称", example = "测试模版 */ + private String name; + + /** 模版编码" */ + private String code; + + /** 模版类型,对应 system_notify_template_type 字典" */ + private Integer type; + + /** 发送人名称", example = "土豆 */ + private String nickname; + + /** 模版内容", example = "我是模版内容 */ + private String content; + + /** 参数数组 */ + private List params; + + /** 状态,参见 CommonStatusEnum 枚举" */ + private Integer status; + + /** 备注 */ + private String remark; + + /** + * 创建时间 + */ + private LocalDateTime createTime; +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/notify/vo/template/NotifyTemplateSaveReqVO.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/notify/vo/template/NotifyTemplateSaveReqVO.java new file mode 100644 index 0000000..f7507f4 --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/notify/vo/template/NotifyTemplateSaveReqVO.java @@ -0,0 +1,64 @@ +package com.tashow.cloud.system.controller.admin.notify.vo.template; + +import com.tashow.cloud.common.enums.CommonStatusEnum; +import com.tashow.cloud.common.validation.InEnum; + +import lombok.Data; + +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; + +/** + * 管理后台 - 站内信模版创建/修改 Request VO + */ +@Data +public class NotifyTemplateSaveReqVO { + + /** + * ID + */ + private Long id; + + /** + * 模版名称", example = "测试模版 + */ + @NotEmpty(message = "模版名称不能为空") + private String name; + + /** + * 模版编码" + */ + @NotNull(message = "模版编码不能为空") + private String code; + + /** + * 模版类型,对应 system_notify_template_type 字典" + */ + @NotNull(message = "模版类型不能为空") + private Integer type; + + /** + * 发送人名称", example = "土豆 + */ + @NotEmpty(message = "发送人名称不能为空") + private String nickname; + + /** + * 模版内容", example = "我是模版内容 + */ + @NotEmpty(message = "模版内容不能为空") + private String content; + + /** + * 状态,参见 CommonStatusEnum 枚举" + */ + @NotNull(message = "状态不能为空") + @InEnum(value = CommonStatusEnum.class, message = "状态必须是 {value}") + private Integer status; + + /** + * 备注 + */ + private String remark; + +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/notify/vo/template/NotifyTemplateSendReqVO.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/notify/vo/template/NotifyTemplateSendReqVO.java new file mode 100644 index 0000000..9e47fce --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/notify/vo/template/NotifyTemplateSendReqVO.java @@ -0,0 +1,39 @@ +package com.tashow.cloud.system.controller.admin.notify.vo.template; + + +import lombok.Data; + +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import java.util.Map; + +/** + * 管理后台 - 站内信模板的发送 Request VO + */ +@Data +public class NotifyTemplateSendReqVO { + + /** + * 用户id" + */ + @NotNull(message = "用户id不能为空") + private Long userId; + + /** + * 用户类型" + */ + @NotNull(message = "用户类型不能为空") + private Integer userType; + + /** + * 模板编码" + */ + @NotEmpty(message = "模板编码不能为空") + private String templateCode; + + /** + * 模板参数 + */ + private Map templateParams; + +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/oauth2/OAuth2ClientController.http b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/oauth2/OAuth2ClientController.http new file mode 100644 index 0000000..f8d7b89 --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/oauth2/OAuth2ClientController.http @@ -0,0 +1,23 @@ +### 请求 /login 接口 => 成功 +POST {{baseUrl}}/system/oauth2-client/create +Content-Type: application/json +Authorization: Bearer {{token}} +tenant-id: {{adminTenantId}} + +{ + "id": "1", + "secret": "admin123", + "name": "芋道源码", + "logo": "https://www.iocoder.cn/images/favicon.ico", + "description": "我是描述", + "status": 0, + "accessTokenValiditySeconds": 180, + "refreshTokenValiditySeconds": 8640, + "redirectUris": ["https://www.iocoder.cn"], + "autoApprove": true, + "authorizedGrantTypes": ["password"], + "scopes": ["user_info"], + "authorities": ["system:user:query"], + "resource_ids": ["1024"], + "additionalInformation": "{}" +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/oauth2/OAuth2ClientController.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/oauth2/OAuth2ClientController.java new file mode 100644 index 0000000..04b4b97 --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/oauth2/OAuth2ClientController.java @@ -0,0 +1,81 @@ +package com.tashow.cloud.system.controller.admin.oauth2; + +import com.tashow.cloud.common.pojo.CommonResult; +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.common.util.object.BeanUtils; +import com.tashow.cloud.system.controller.admin.oauth2.vo.client.OAuth2ClientPageReqVO; +import com.tashow.cloud.system.controller.admin.oauth2.vo.client.OAuth2ClientRespVO; +import com.tashow.cloud.system.controller.admin.oauth2.vo.client.OAuth2ClientSaveReqVO; +import com.tashow.cloud.system.dal.dataobject.oauth2.OAuth2ClientDO; +import com.tashow.cloud.system.service.oauth2.OAuth2ClientService; +import jakarta.annotation.Resource; +import jakarta.validation.Valid; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import static com.tashow.cloud.common.pojo.CommonResult.success; + +/** + * 管理后台 - OAuth2 客户端 + */ +@RestController +@RequestMapping("/system/oauth2-client") +@Validated +public class OAuth2ClientController { + + @Resource + private OAuth2ClientService oAuth2ClientService; + + /** + * 创建 OAuth2 客户端 + */ + @PostMapping("/create") + @PreAuthorize("@ss.hasPermission('system:oauth2-client:create')") + public CommonResult createOAuth2Client( + @Valid @RequestBody OAuth2ClientSaveReqVO createReqVO) { + return success(oAuth2ClientService.createOAuth2Client(createReqVO)); + } + + /** + * 更新 OAuth2 客户端 + */ + @PutMapping("/update") + @PreAuthorize("@ss.hasPermission('system:oauth2-client:update')") + public CommonResult updateOAuth2Client( + @Valid @RequestBody OAuth2ClientSaveReqVO updateReqVO) { + oAuth2ClientService.updateOAuth2Client(updateReqVO); + return success(true); + } + + /** + * 删除 OAuth2 客户端 + */ + @DeleteMapping("/delete") + @PreAuthorize("@ss.hasPermission('system:oauth2-client:delete')") + public CommonResult deleteOAuth2Client(@RequestParam("id") Long id) { + oAuth2ClientService.deleteOAuth2Client(id); + return success(true); + } + + /** + * 获得 OAuth2 客户端 + */ + @GetMapping("/get") + @PreAuthorize("@ss.hasPermission('system:oauth2-client:query')") + public CommonResult getOAuth2Client(@RequestParam("id") Long id) { + OAuth2ClientDO client = oAuth2ClientService.getOAuth2Client(id); + return success(BeanUtils.toBean(client, OAuth2ClientRespVO.class)); + } + + /** + * 获得 OAuth2 客户端分页 + */ + @GetMapping("/page") + @PreAuthorize("@ss.hasPermission('system:oauth2-client:query')") + public CommonResult> getOAuth2ClientPage( + @Valid OAuth2ClientPageReqVO pageVO) { + PageResult pageResult = oAuth2ClientService.getOAuth2ClientPage(pageVO); + return success(BeanUtils.toBean(pageResult, OAuth2ClientRespVO.class)); + } +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/oauth2/OAuth2OpenController.http b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/oauth2/OAuth2OpenController.http new file mode 100644 index 0000000..f770ed9 --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/oauth2/OAuth2OpenController.http @@ -0,0 +1,54 @@ +### 请求 /system/oauth2/authorize 接口 => 成功 +GET {{baseUrl}}/system/oauth2/authorize?clientId=default +Authorization: Bearer {{token}} +tenant-id: {{adminTenantId}} + +### 请求 /system/oauth2/authorize + token 接口 => 成功 +POST {{baseUrl}}/system/oauth2/authorize +Content-Type: application/x-www-form-urlencoded +Authorization: Bearer {{token}} +tenant-id: {{adminTenantId}} + +response_type=token&client_id=default&scope={"user.read": true}&redirect_uri=https://www.iocoder.cn&auto_approve=true + +### 请求 /system/oauth2/authorize + code 接口 => 成功 +POST {{baseUrl}}/system/oauth2/authorize +Content-Type: application/x-www-form-urlencoded +Authorization: Bearer {{token}} +tenant-id: {{adminTenantId}} + +response_type=code&client_id=default&scope={"user.read": true}&redirect_uri=https://www.iocoder.cn&auto_approve=false + +### 请求 /system/oauth2/token + code 接口 => 成功 +POST {{baseUrl}}/system/oauth2/token +Content-Type: application/x-www-form-urlencoded +Authorization: Basic ZGVmYXVsdDphZG1pbjEyMw== +tenant-id: {{adminTenantId}} + +grant_type=authorization_code&redirect_uri=https://www.iocoder.cn&code=189956c07a174588a97157eabef2f93a + +### 请求 /system/oauth2/token + password 接口 => 成功 +POST {{baseUrl}}/system/oauth2/token +Content-Type: application/x-www-form-urlencoded +Authorization: Basic ZGVmYXVsdDphZG1pbjEyMw== +tenant-id: {{adminTenantId}} + +grant_type=password&username=admin&password=admin123&scope=user.read + +### 请求 /system/oauth2/token + refresh_token 接口 => 成功 +POST {{baseUrl}}/system/oauth2/token +Content-Type: application/x-www-form-urlencoded +Authorization: Basic ZGVmYXVsdDphZG1pbjEyMw== +tenant-id: {{adminTenantId}} + +grant_type=refresh_token&refresh_token=00895465d6994f72a9d926ceeed0f588 + +### 请求 /system/oauth2/token + DELETE 接口 => 成功 +DELETE {{baseUrl}}/system/oauth2/token?token=ca8a188f464441d6949c51493a2b7596 +Authorization: Basic ZGVmYXVsdDphZG1pbjEyMw== +tenant-id: {{adminTenantId}} + +### 请求 /system/oauth2/check-token 接口 => 成功 +POST {{baseUrl}}/system/oauth2/check-token?token=620d307c5b4148df8a98dd6c6c547106 +Authorization: Basic ZGVmYXVsdDphZG1pbjEyMw== +tenant-id: {{adminTenantId}} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/oauth2/OAuth2OpenController.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/oauth2/OAuth2OpenController.java new file mode 100644 index 0000000..51cc218 --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/oauth2/OAuth2OpenController.java @@ -0,0 +1,302 @@ +package com.tashow.cloud.system.controller.admin.oauth2; + +import cn.hutool.core.lang.Assert; +import cn.hutool.core.util.ArrayUtil; +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.StrUtil; +import com.tashow.cloud.common.enums.UserTypeEnum; +import com.tashow.cloud.common.pojo.CommonResult; +import com.tashow.cloud.common.util.http.HttpUtils; +import com.tashow.cloud.common.util.json.JsonUtils; +import com.tashow.cloud.system.controller.admin.oauth2.vo.open.OAuth2OpenAccessTokenRespVO; +import com.tashow.cloud.system.controller.admin.oauth2.vo.open.OAuth2OpenAuthorizeInfoRespVO; +import com.tashow.cloud.system.controller.admin.oauth2.vo.open.OAuth2OpenCheckTokenRespVO; +import com.tashow.cloud.system.convert.oauth2.OAuth2OpenConvert; +import com.tashow.cloud.system.dal.dataobject.oauth2.OAuth2AccessTokenDO; +import com.tashow.cloud.system.dal.dataobject.oauth2.OAuth2ApproveDO; +import com.tashow.cloud.system.dal.dataobject.oauth2.OAuth2ClientDO; +import com.tashow.cloud.system.service.oauth2.OAuth2ApproveService; +import com.tashow.cloud.system.service.oauth2.OAuth2ClientService; +import com.tashow.cloud.system.service.oauth2.OAuth2GrantService; +import com.tashow.cloud.system.service.oauth2.OAuth2TokenService; +import com.tashow.cloud.system.util.oauth2.OAuth2Utils; +import com.tashow.cloud.systemapi.enums.oauth2.OAuth2GrantTypeEnum; +import jakarta.annotation.Resource; +import jakarta.annotation.security.PermitAll; +import jakarta.servlet.http.HttpServletRequest; +import lombok.extern.slf4j.Slf4j; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import java.util.Collections; +import java.util.List; +import java.util.Map; + +import static com.tashow.cloud.common.exception.enums.GlobalErrorCodeConstants.BAD_REQUEST; +import static com.tashow.cloud.common.exception.util.ServiceExceptionUtil.exception0; +import static com.tashow.cloud.common.pojo.CommonResult.success; +import static com.tashow.cloud.common.util.collection.CollectionUtils.convertList; +import static com.tashow.cloud.web.web.core.util.WebFrameworkUtils.getLoginUserId; + +/** + * 提供给外部应用调用为主 + * + *

一般来说,管理后台的 /system-api/* 是不直接提供给外部应用使用,主要是外部应用能够访问的数据与接口是有限的,而管理后台的 RBAC 无法很好的控制。 + * 参考大量的开放平台,都是独立的一套 OpenAPI,对应到【本系统】就是在 Controller 下新建 open 包,实现 /open-api/* 接口,然后通过 scope 进行控制。 + * 另外,一个公司如果有多个管理后台,它们 client_id 产生的 access token 相互之间是无法互通的,即无法访问它们系统的 API 接口,直到两个 client_id + * 产生信任授权。 + * + *

考虑到【本系统】暂时不想做的过于复杂,默认只有获取到 access token 之后,可以访问【本系统】管理后台的 /system-api/* 所有接口,除非手动添加 scope 控制。 + * scope 的使用示例,可见 {@link com.tashow.cloud.system.controller.admin.oauth2.OAuth2UserController} 类 + * + * @author 芋道源码 + */ + +/** + * 管理后台 - OAuth2.0 授权 + */ +@RestController +@RequestMapping("/system/oauth2") +@Validated +@Slf4j +public class OAuth2OpenController { + + @Resource + private OAuth2GrantService oauth2GrantService; + @Resource + private OAuth2ClientService oauth2ClientService; + @Resource + private OAuth2ApproveService oauth2ApproveService; + @Resource + private OAuth2TokenService oauth2TokenService; + + /** + * 对应 Spring Security OAuth 的 TokenEndpoint 类的 postAccessToken 方法 + * + *

授权码 authorization_code 模式时:code + redirectUri + state 参数 密码 password 模式时:username + password + * + scope 参数 刷新 refresh_token 模式时:refreshToken 参数 客户端 client_credentials 模式:scope 参数 简化 implicit + * 模式时:不支持 + * + *

注意,默认需要传递 client_id + client_secret 参数 + */ + /** + * 获得访问令牌", description = "适合 code 授权码模式,或者 implicit 简化模式;在 sso.vue 单点登录界面被【获取】调用 + */ + @PostMapping("/token") + @PermitAll + public CommonResult postAccessToken( + HttpServletRequest request, + @RequestParam("grant_type") String grantType, + @RequestParam(value = "code", required = false) String code, // 授权码模式 + @RequestParam(value = "redirect_uri", required = false) String redirectUri, // 授权码模式 + @RequestParam(value = "state", required = false) String state, // 授权码模式 + @RequestParam(value = "username", required = false) String username, // 密码模式 + @RequestParam(value = "password", required = false) String password, // 密码模式 + @RequestParam(value = "scope", required = false) String scope, // 密码模式 + @RequestParam(value = "refresh_token", required = false) String refreshToken) { // 刷新模式 + List scopes = OAuth2Utils.buildScopes(scope); + // 1.1 校验授权类型 + OAuth2GrantTypeEnum grantTypeEnum = OAuth2GrantTypeEnum.getByGrantType(grantType); + if (grantTypeEnum == null) { + throw exception0(BAD_REQUEST.getCode(), StrUtil.format("未知授权类型({})", grantType)); + } + if (grantTypeEnum == OAuth2GrantTypeEnum.IMPLICIT) { + throw exception0(BAD_REQUEST.getCode(), "Token 接口不支持 implicit 授权模式"); + } + + // 1.2 校验客户端 + String[] clientIdAndSecret = obtainBasicAuthorization(request); + OAuth2ClientDO client = + oauth2ClientService.validOAuthClientFromCache( + clientIdAndSecret[0], clientIdAndSecret[1], grantType, scopes, redirectUri); + + // 2. 根据授权模式,获取访问令牌 + OAuth2AccessTokenDO accessTokenDO; + switch (grantTypeEnum) { + case AUTHORIZATION_CODE: + accessTokenDO = + oauth2GrantService.grantAuthorizationCodeForAccessToken( + client.getClientId(), code, redirectUri, state); + break; + case PASSWORD: + accessTokenDO = + oauth2GrantService.grantPassword(username, password, client.getClientId(), scopes); + break; + case CLIENT_CREDENTIALS: + accessTokenDO = oauth2GrantService.grantClientCredentials(client.getClientId(), scopes); + break; + case REFRESH_TOKEN: + accessTokenDO = oauth2GrantService.grantRefreshToken(refreshToken, client.getClientId()); + break; + default: + throw new IllegalArgumentException("未知授权类型:" + grantType); + } + Assert.notNull(accessTokenDO, "访问令牌不能为空"); // 防御性检查 + return success(OAuth2OpenConvert.INSTANCE.convert(accessTokenDO)); + } + + /** + * 删除访问令牌 + */ + @DeleteMapping("/token") + @PermitAll + public CommonResult revokeToken( + HttpServletRequest request, @RequestParam("token") String token) { + // 校验客户端 + String[] clientIdAndSecret = obtainBasicAuthorization(request); + OAuth2ClientDO client = + oauth2ClientService.validOAuthClientFromCache( + clientIdAndSecret[0], clientIdAndSecret[1], null, null, null); + + // 删除访问令牌 + return success(oauth2GrantService.revokeToken(client.getClientId(), token)); + } + + /** 校验访问令牌 */ + /** + * 对应 Spring Security OAuth 的 CheckTokenEndpoint 类的 checkToken 方法 + */ + @PostMapping("/check-token") + @PermitAll + public CommonResult checkToken( + HttpServletRequest request, @RequestParam("token") String token) { + // 校验客户端 + String[] clientIdAndSecret = obtainBasicAuthorization(request); + oauth2ClientService.validOAuthClientFromCache( + clientIdAndSecret[0], clientIdAndSecret[1], null, null, null); + + // 校验令牌 + OAuth2AccessTokenDO accessTokenDO = oauth2TokenService.checkAccessToken(token); + Assert.notNull(accessTokenDO, "访问令牌不能为空"); // 防御性检查 + return success(OAuth2OpenConvert.INSTANCE.convert2(accessTokenDO)); + } + + /** 获得授权信息", description = "适合 code 授权码模式,或者 implicit 简化模式;在 sso.vue 单点登录界面被【获取】调用 */ + /** + * 对应 Spring Security OAuth 的 AuthorizationEndpoint 类的 authorize 方法 + */ + @GetMapping("/authorize") + public CommonResult authorize( + @RequestParam("clientId") String clientId) { + // 0. 校验用户已经登录。通过 Spring Security 实现 + + // 1. 获得 Client 客户端的信息 + OAuth2ClientDO client = oauth2ClientService.validOAuthClientFromCache(clientId); + // 2. 获得用户已经授权的信息 + List approves = + oauth2ApproveService.getApproveList(getLoginUserId(), getUserType(), clientId); + // 拼接返回 + return success(OAuth2OpenConvert.INSTANCE.convert(client, approves)); + } + + /** + * 对应 Spring Security OAuth 的 AuthorizationEndpoint 类的 approveOrDeny 方法 + * + *

场景一:【自动授权 autoApprove = true】 刚进入 sso.vue 界面,调用该接口,用户历史已经给该应用做过对应的授权,或者 OAuth2Client 支持该 + * scope 的自动授权 场景二:【手动授权 autoApprove = false】 在 sso.vue 界面,用户选择好 scope 授权范围,调用该接口,进行授权。此时,approved + * 为 true 或者 false + * + *

因为前后端分离,Axios 无法很好的处理 302 重定向,所以和 Spring Security OAuth 略有不同,返回结果是重定向的 URL,剩余交给前端处理 + */ + /** + * 申请授权", description = "适合 code 授权码模式,或者 implicit 简化模式;在 sso.vue 单点登录界面被【提交】调用 + */ + @PostMapping("/authorize") + public CommonResult approveOrDeny( + @RequestParam("response_type") String responseType, + @RequestParam("client_id") String clientId, + @RequestParam(value = "scope", required = false) String scope, + @RequestParam("redirect_uri") String redirectUri, + @RequestParam(value = "auto_approve") Boolean autoApprove, + @RequestParam(value = "state", required = false) String state) { + @SuppressWarnings("unchecked") + Map scopes = JsonUtils.parseObject(scope, Map.class); + scopes = ObjectUtil.defaultIfNull(scopes, Collections.emptyMap()); + // 0. 校验用户已经登录。通过 Spring Security 实现 + + // 1.1 校验 responseType 是否满足 code 或者 token 值 + OAuth2GrantTypeEnum grantTypeEnum = getGrantTypeEnum(responseType); + // 1.2 校验 redirectUri 重定向域名是否合法 + 校验 scope 是否在 Client 授权范围内 + OAuth2ClientDO client = + oauth2ClientService.validOAuthClientFromCache( + clientId, null, grantTypeEnum.getGrantType(), scopes.keySet(), redirectUri); + + // 2.1 假设 approved 为 null,说明是场景一 + if (Boolean.TRUE.equals(autoApprove)) { + // 如果无法自动授权通过,则返回空 url,前端不进行跳转 + if (!oauth2ApproveService.checkForPreApproval( + getLoginUserId(), getUserType(), clientId, scopes.keySet())) { + return success(null); + } + } else { // 2.2 假设 approved 非 null,说明是场景二 + // 如果计算后不通过,则跳转一个错误链接 + if (!oauth2ApproveService.updateAfterApproval( + getLoginUserId(), getUserType(), clientId, scopes)) { + return success( + OAuth2Utils.buildUnsuccessfulRedirect( + redirectUri, responseType, state, "access_denied", "User denied access")); + } + } + + // 3.1 如果是 code 授权码模式,则发放 code 授权码,并重定向 + List approveScopes = + convertList(scopes.entrySet(), Map.Entry::getKey, Map.Entry::getValue); + if (grantTypeEnum == OAuth2GrantTypeEnum.AUTHORIZATION_CODE) { + return success( + getAuthorizationCodeRedirect( + getLoginUserId(), client, approveScopes, redirectUri, state)); + } + // 3.2 如果是 token 则是 implicit 简化模式,则发送 accessToken 访问令牌,并重定向 + return success( + getImplicitGrantRedirect(getLoginUserId(), client, approveScopes, redirectUri, state)); + } + + private static OAuth2GrantTypeEnum getGrantTypeEnum(String responseType) { + if (StrUtil.equals(responseType, "code")) { + return OAuth2GrantTypeEnum.AUTHORIZATION_CODE; + } + if (StrUtil.equalsAny(responseType, "token")) { + return OAuth2GrantTypeEnum.IMPLICIT; + } + throw exception0(BAD_REQUEST.getCode(), "response_type 参数值只允许 code 和 token"); + } + + private String getImplicitGrantRedirect( + Long userId, OAuth2ClientDO client, List scopes, String redirectUri, String state) { + // 1. 创建 access token 访问令牌 + OAuth2AccessTokenDO accessTokenDO = + oauth2GrantService.grantImplicit(userId, getUserType(), client.getClientId(), scopes); + Assert.notNull(accessTokenDO, "访问令牌不能为空"); // 防御性检查 + // 2. 拼接重定向的 URL + // noinspection unchecked + return OAuth2Utils.buildImplicitRedirectUri( + redirectUri, + accessTokenDO.getAccessToken(), + state, + accessTokenDO.getExpiresTime(), + scopes, + JsonUtils.parseObject(client.getAdditionalInformation(), Map.class)); + } + + private String getAuthorizationCodeRedirect( + Long userId, OAuth2ClientDO client, List scopes, String redirectUri, String state) { + // 1. 创建 code 授权码 + String authorizationCode = + oauth2GrantService.grantAuthorizationCodeForCode( + userId, getUserType(), client.getClientId(), scopes, redirectUri, state); + // 2. 拼接重定向的 URL + return OAuth2Utils.buildAuthorizationCodeRedirectUri(redirectUri, authorizationCode, state); + } + + private Integer getUserType() { + return UserTypeEnum.ADMIN.getValue(); + } + + private String[] obtainBasicAuthorization(HttpServletRequest request) { + String[] clientIdAndSecret = HttpUtils.obtainBasicAuthorization(request); + if (ArrayUtil.isEmpty(clientIdAndSecret) || clientIdAndSecret.length != 2) { + throw exception0(BAD_REQUEST.getCode(), "client_id 或 client_secret 未正确传递"); + } + return clientIdAndSecret; + } +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/oauth2/OAuth2TokenController.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/oauth2/OAuth2TokenController.java new file mode 100644 index 0000000..d9355c3 --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/oauth2/OAuth2TokenController.java @@ -0,0 +1,51 @@ +package com.tashow.cloud.system.controller.admin.oauth2; + +import com.tashow.cloud.common.pojo.CommonResult; +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.common.util.object.BeanUtils; +import com.tashow.cloud.system.controller.admin.oauth2.vo.token.OAuth2AccessTokenPageReqVO; +import com.tashow.cloud.system.controller.admin.oauth2.vo.token.OAuth2AccessTokenRespVO; +import com.tashow.cloud.system.dal.dataobject.oauth2.OAuth2AccessTokenDO; +import com.tashow.cloud.system.service.auth.AdminAuthService; +import com.tashow.cloud.system.service.oauth2.OAuth2TokenService; +import com.tashow.cloud.systemapi.enums.logger.LoginLogTypeEnum; +import jakarta.annotation.Resource; +import jakarta.validation.Valid; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; + +import static com.tashow.cloud.common.pojo.CommonResult.success; + +/** + * 管理后台 - OAuth2.0 令牌 + */ +@RestController +@RequestMapping("/system/oauth2-token") +public class OAuth2TokenController { + + @Resource + private OAuth2TokenService oauth2TokenService; + @Resource + private AdminAuthService authService; + + /** + * 获得访问令牌分页", description = "只返回有效期内的 + */ + @GetMapping("/page") + @PreAuthorize("@ss.hasPermission('system:oauth2-token:page')") + public CommonResult> getAccessTokenPage( + @Valid OAuth2AccessTokenPageReqVO reqVO) { + PageResult pageResult = oauth2TokenService.getAccessTokenPage(reqVO); + return success(BeanUtils.toBean(pageResult, OAuth2AccessTokenRespVO.class)); + } + + /** + * 删除访问令牌 + */ + @DeleteMapping("/delete") + @PreAuthorize("@ss.hasPermission('system:oauth2-token:delete')") + public CommonResult deleteAccessToken(@RequestParam("accessToken") String accessToken) { + authService.logout(accessToken, LoginLogTypeEnum.LOGOUT_DELETE.getType()); + return success(true); + } +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/oauth2/OAuth2UserController.http b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/oauth2/OAuth2UserController.http new file mode 100644 index 0000000..9e76c7c --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/oauth2/OAuth2UserController.http @@ -0,0 +1,14 @@ +### 请求 /system/oauth2/user/get 接口 => 成功 +GET {{baseUrl}}/system/oauth2/user/get +Authorization: Bearer 47f9c74ec11041f193b777ebb95c3b0d +tenant-id: {{adminTenantId}} + +### 请求 /system/oauth2/user/update 接口 => 成功 +PUT {{baseUrl}}/system/oauth2/user/update +Content-Type: application/json +Authorization: Bearer 47f9c74ec11041f193b777ebb95c3b0d +tenant-id: {{adminTenantId}} + +{ + "nickname": "芋道源码" +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/oauth2/OAuth2UserController.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/oauth2/OAuth2UserController.java new file mode 100644 index 0000000..374964b --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/oauth2/OAuth2UserController.java @@ -0,0 +1,87 @@ +package com.tashow.cloud.system.controller.admin.oauth2; + +import cn.hutool.core.collection.CollUtil; +import com.tashow.cloud.common.pojo.CommonResult; +import com.tashow.cloud.common.util.object.BeanUtils; +import com.tashow.cloud.system.controller.admin.oauth2.vo.user.OAuth2UserInfoRespVO; +import com.tashow.cloud.system.controller.admin.oauth2.vo.user.OAuth2UserUpdateReqVO; +import com.tashow.cloud.system.controller.admin.user.vo.profile.UserProfileUpdateReqVO; +import com.tashow.cloud.system.dal.dataobject.dept.DeptDO; +import com.tashow.cloud.system.dal.dataobject.dept.PostDO; +import com.tashow.cloud.system.dal.dataobject.user.AdminUserDO; +import com.tashow.cloud.system.service.dept.DeptService; +import com.tashow.cloud.system.service.dept.PostService; +import com.tashow.cloud.system.service.user.AdminUserService; +import jakarta.annotation.Resource; +import jakarta.validation.Valid; +import lombok.extern.slf4j.Slf4j; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +import static com.tashow.cloud.common.pojo.CommonResult.success; +import static com.tashow.cloud.web.web.core.util.WebFrameworkUtils.getLoginUserId; + +/** + * 提供给外部应用调用为主 + * + *

1. 在 getUserInfo 方法上,添加 @PreAuthorize("@ss.hasScope('user.read')") 注解,声明需要满足 scope = user.read + * 2. 在 updateUserInfo 方法上,添加 @PreAuthorize("@ss.hasScope('user.write')") 注解,声明需要满足 scope = + * user.write + * + * @author 芋道源码 + */ + +/** + * 管理后台 - OAuth2.0 用户 + */ +@RestController +@RequestMapping("/system/oauth2/user") +@Validated +@Slf4j +public class OAuth2UserController { + + @Resource + private AdminUserService userService; + @Resource + private DeptService deptService; + @Resource + private PostService postService; + + /** + * 获得用户基本信息 + */ + @GetMapping("/get") + @PreAuthorize("@ss.hasScope('user.read')") // + public CommonResult getUserInfo() { + // 获得用户基本信息 + AdminUserDO user = userService.getUser(getLoginUserId()); + OAuth2UserInfoRespVO resp = BeanUtils.toBean(user, OAuth2UserInfoRespVO.class); + // 获得部门信息 + if (user.getDeptId() != null) { + DeptDO dept = deptService.getDept(user.getDeptId()); + resp.setDept(BeanUtils.toBean(dept, OAuth2UserInfoRespVO.Dept.class)); + } + // 获得岗位信息 + if (CollUtil.isNotEmpty(user.getPostIds())) { + List posts = postService.getPostList(user.getPostIds()); + resp.setPosts(BeanUtils.toBean(posts, OAuth2UserInfoRespVO.Post.class)); + } + return success(resp); + } + + /** + * 更新用户基本信息 + */ + @PutMapping("/update") + @PreAuthorize("@ss.hasScope('user.write')") + public CommonResult updateUserInfo(@Valid @RequestBody OAuth2UserUpdateReqVO reqVO) { + // 这里将 UserProfileUpdateReqVO =》UserProfileUpdateReqVO 对象,实现接口的复用。 + // 主要是,AdminUserService 没有自己的 BO 对象,所以复用只能这么做 + userService.updateUserProfile( + getLoginUserId(), BeanUtils.toBean(reqVO, UserProfileUpdateReqVO.class)); + return success(true); + } +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/oauth2/vo/client/OAuth2ClientPageReqVO.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/oauth2/vo/client/OAuth2ClientPageReqVO.java new file mode 100644 index 0000000..7e77102 --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/oauth2/vo/client/OAuth2ClientPageReqVO.java @@ -0,0 +1,25 @@ +package com.tashow.cloud.system.controller.admin.oauth2.vo.client; + +import com.tashow.cloud.common.pojo.PageParam; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; + +/** + * 管理后台 - OAuth2 客户端分页 Request VO + */ +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class OAuth2ClientPageReqVO extends PageParam { + + /** + * 应用名,模糊匹配 + */ + private String name; + + /** + * 状态,参见 CommonStatusEnum 枚举 + */ + private Integer status; +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/oauth2/vo/client/OAuth2ClientRespVO.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/oauth2/vo/client/OAuth2ClientRespVO.java new file mode 100644 index 0000000..9b07393 --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/oauth2/vo/client/OAuth2ClientRespVO.java @@ -0,0 +1,98 @@ +package com.tashow.cloud.system.controller.admin.oauth2.vo.client; + +import lombok.Data; + +import java.time.LocalDateTime; +import java.util.List; + +/** + * 管理后台 - OAuth2 客户端 Response VO + */ +@Data +public class OAuth2ClientRespVO { + + /** + * 编号" + */ + private Long id; + + /** + * 客户端编号" + */ + private String clientId; + + /** + * 客户端密钥" + */ + private String secret; + + /** + * 应用名", example = "土豆 + */ + private String name; + + /** + * 应用图标" + */ + private String logo; + + /** + * 应用描述 + */ + private String description; + + /** + * 状态,参见 CommonStatusEnum 枚举" + */ + private Integer status; + + /** + * 访问令牌的有效期" + */ + private Integer accessTokenValiditySeconds; + + /** + * 刷新令牌的有效期" + */ + private Integer refreshTokenValiditySeconds; + + /** + * 可重定向的 URI 地址" + */ + private List redirectUris; + + /** + * 授权类型,参见 OAuth2GrantTypeEnum 枚举" + */ + private List authorizedGrantTypes; + + /** + * 授权范围 + */ + private List scopes; + + /** + * 自动通过的授权范围 + */ + private List autoApproveScopes; + + /** + * 权限 + */ + private List authorities; + + /** + * 资源 + */ + private List resourceIds; + + /** + * 附加信息 + */ + private String additionalInformation; + + /** + * 创建时间 + */ + private LocalDateTime createTime; +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/oauth2/vo/client/OAuth2ClientSaveReqVO.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/oauth2/vo/client/OAuth2ClientSaveReqVO.java new file mode 100644 index 0000000..f9700c4 --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/oauth2/vo/client/OAuth2ClientSaveReqVO.java @@ -0,0 +1,79 @@ +package com.tashow.cloud.system.controller.admin.oauth2.vo.client; + +import cn.hutool.core.util.StrUtil; +import com.tashow.cloud.common.util.json.JsonUtils; +import jakarta.validation.constraints.AssertTrue; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import java.util.List; +import lombok.Data; +import org.hibernate.validator.constraints.URL; + +/** 管理后台 - OAuth2 客户端创建/修改 Request VO */ +@Data +public class OAuth2ClientSaveReqVO { + + /** 编号 */ + private Long id; + + /** 客户端编号" */ + @NotNull(message = "客户端编号不能为空") + private String clientId; + + /** 客户端密钥" */ + @NotNull(message = "客户端密钥不能为空") + private String secret; + + /** 应用名", example = "土豆 */ + @NotNull(message = "应用名不能为空") + private String name; + + /** 应用图标" */ + @NotNull(message = "应用图标不能为空") + @URL(message = "应用图标的地址不正确") + private String logo; + + /** 应用描述 */ + private String description; + + /** 状态,参见 CommonStatusEnum 枚举" */ + @NotNull(message = "状态不能为空") + private Integer status; + + /** 访问令牌的有效期" */ + @NotNull(message = "访问令牌的有效期不能为空") + private Integer accessTokenValiditySeconds; + + /** 刷新令牌的有效期" */ + @NotNull(message = "刷新令牌的有效期不能为空") + private Integer refreshTokenValiditySeconds; + + /** 可重定向的 URI 地址" */ + @NotNull(message = "可重定向的 URI 地址不能为空") + private List<@NotEmpty(message = "重定向的 URI 不能为空") @URL(message = "重定向的 URI 格式不正确") String> + redirectUris; + + /** 授权类型,参见 OAuth2GrantTypeEnum 枚举" */ + @NotNull(message = "授权类型不能为空") + private List authorizedGrantTypes; + + /** 授权范围 */ + private List scopes; + + /** 自动通过的授权范围 */ + private List autoApproveScopes; + + /** 权限 */ + private List authorities; + + /** 资源 */ + private List resourceIds; + + /** 附加信息 */ + private String additionalInformation; + + @AssertTrue(message = "附加信息必须是 JSON 格式") + public boolean isAdditionalInformationJson() { + return StrUtil.isEmpty(additionalInformation) || JsonUtils.isJson(additionalInformation); + } +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/oauth2/vo/open/OAuth2OpenAccessTokenRespVO.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/oauth2/vo/open/OAuth2OpenAccessTokenRespVO.java new file mode 100644 index 0000000..2bc3884 --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/oauth2/vo/open/OAuth2OpenAccessTokenRespVO.java @@ -0,0 +1,32 @@ +package com.tashow.cloud.system.controller.admin.oauth2.vo.open; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** 管理后台 - 【开放接口】访问令牌 Response VO */ +@Data +@NoArgsConstructor +@AllArgsConstructor +public class OAuth2OpenAccessTokenRespVO { + + /** 访问令牌" */ + @JsonProperty("access_token") + private String accessToken; + + /** 刷新令牌" */ + @JsonProperty("refresh_token") + private String refreshToken; + + /** 令牌类型" */ + @JsonProperty("token_type") + private String tokenType; + + /** 过期时间,单位:秒" */ + @JsonProperty("expires_in") + private Long expiresIn; + + /** 授权范围,如果多个授权范围,使用空格分隔 */ + private String scope; +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/oauth2/vo/open/OAuth2OpenAuthorizeInfoRespVO.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/oauth2/vo/open/OAuth2OpenAuthorizeInfoRespVO.java new file mode 100644 index 0000000..633a0b0 --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/oauth2/vo/open/OAuth2OpenAuthorizeInfoRespVO.java @@ -0,0 +1,40 @@ +package com.tashow.cloud.system.controller.admin.oauth2.vo.open; + +import com.tashow.cloud.common.core.KeyValue; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.List; + +/** + * 管理后台 - 授权页的信息 Response VO + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +public class OAuth2OpenAuthorizeInfoRespVO { + + /** + * 客户端 + */ + private Client client; + + private List> scopes; + + @Data + @NoArgsConstructor + @AllArgsConstructor + public static class Client { + + /** + * 应用名", example = "土豆 + */ + private String name; + + /** + * 应用图标" + */ + private String logo; + } +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/oauth2/vo/open/OAuth2OpenCheckTokenRespVO.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/oauth2/vo/open/OAuth2OpenCheckTokenRespVO.java new file mode 100644 index 0000000..47ea537 --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/oauth2/vo/open/OAuth2OpenCheckTokenRespVO.java @@ -0,0 +1,40 @@ +package com.tashow.cloud.system.controller.admin.oauth2.vo.open; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.util.List; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** 管理后台 - 【开放接口】校验令牌 Response VO */ +@Data +@NoArgsConstructor +@AllArgsConstructor +public class OAuth2OpenCheckTokenRespVO { + + /** 用户编号" */ + @JsonProperty("user_id") + private Long userId; + + /** 用户类型,参见 UserTypeEnum 枚举" */ + @JsonProperty("user_type") + private Integer userType; + + /** 租户编号" */ + @JsonProperty("tenant_id") + private Long tenantId; + + /** 客户端编号" */ + @JsonProperty("client_id") + private String clientId; + + /** 授权范围" */ + private List scopes; + + /** 访问令牌" */ + @JsonProperty("access_token") + private String accessToken; + + /** 过期时间,时间戳 / 1000,即单位:秒" */ + private Long exp; +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/oauth2/vo/token/OAuth2AccessTokenPageReqVO.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/oauth2/vo/token/OAuth2AccessTokenPageReqVO.java new file mode 100644 index 0000000..0acdd90 --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/oauth2/vo/token/OAuth2AccessTokenPageReqVO.java @@ -0,0 +1,28 @@ +package com.tashow.cloud.system.controller.admin.oauth2.vo.token; + +import com.tashow.cloud.common.pojo.PageParam; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 管理后台 - 访问令牌分页 Request VO + */ +@Data +@EqualsAndHashCode(callSuper = true) +public class OAuth2AccessTokenPageReqVO extends PageParam { + + /** + * 用户编号" + */ + private Long userId; + + /** + * 用户类型,参见 UserTypeEnum 枚举" + */ + private Integer userType; + + /** + * 客户端编号" + */ + private String clientId; +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/oauth2/vo/token/OAuth2AccessTokenRespVO.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/oauth2/vo/token/OAuth2AccessTokenRespVO.java new file mode 100644 index 0000000..3c2e022 --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/oauth2/vo/token/OAuth2AccessTokenRespVO.java @@ -0,0 +1,56 @@ +package com.tashow.cloud.system.controller.admin.oauth2.vo.token; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.time.LocalDateTime; + +/** + * 管理后台 - 访问令牌 Response VO + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +public class OAuth2AccessTokenRespVO { + + /** + * 编号" + */ + private Long id; + + /** + * 访问令牌" + */ + private String accessToken; + + /** + * 刷新令牌" + */ + private String refreshToken; + + /** + * 用户编号" + */ + private Long userId; + + /** + * 用户类型,参见 UserTypeEnum 枚举" + */ + private Integer userType; + + /** + * 客户端编号" + */ + private String clientId; + + /** + * 创建时间 + */ + private LocalDateTime createTime; + + /** + * 过期时间 + */ + private LocalDateTime expiresTime; +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/oauth2/vo/user/OAuth2UserInfoRespVO.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/oauth2/vo/user/OAuth2UserInfoRespVO.java new file mode 100644 index 0000000..0dda8b9 --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/oauth2/vo/user/OAuth2UserInfoRespVO.java @@ -0,0 +1,62 @@ +package com.tashow.cloud.system.controller.admin.oauth2.vo.user; + +import java.util.List; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** 管理后台 - OAuth2 获得用户基本信息 Response VO */ +@Data +@NoArgsConstructor +@AllArgsConstructor +public class OAuth2UserInfoRespVO { + + /** 用户编号" */ + private Long id; + + /** 用户账号", example = "芋艿 */ + private String username; + + /** 用户昵称", example = "芋道 */ + private String nickname; + + /** 用户邮箱 */ + private String email; + + /** 手机号码 */ + private String mobile; + + /** 用户性别,参见 SexEnum 枚举类 */ + private Integer sex; + + /** 用户头像 */ + private String avatar; + + /** 所在部门 */ + private Dept dept; + + /** 所属岗位数组 */ + private List posts; + + /** 部门 */ + @Data + public static class Dept { + + /** 部门编号" */ + private Long id; + + /** 部门名称", example = "研发部 */ + private String name; + } + + /** 岗位 */ + @Data + public static class Post { + + /** 岗位编号" */ + private Long id; + + /** 岗位名称", example = "开发 */ + private String name; + } +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/oauth2/vo/user/OAuth2UserUpdateReqVO.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/oauth2/vo/user/OAuth2UserUpdateReqVO.java new file mode 100644 index 0000000..c47bc1a --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/oauth2/vo/user/OAuth2UserUpdateReqVO.java @@ -0,0 +1,41 @@ +package com.tashow.cloud.system.controller.admin.oauth2.vo.user; + +import jakarta.validation.constraints.Email; +import jakarta.validation.constraints.Size; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import org.hibernate.validator.constraints.Length; + +/** + * 管理后台 - OAuth2 更新用户基本信息 Request VO + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +public class OAuth2UserUpdateReqVO { + + /** + * 用户昵称", example = "芋艿 + */ + @Size(max = 30, message = "用户昵称长度不能超过 30 个字符") + private String nickname; + + /** + * 用户邮箱 + */ + @Email(message = "邮箱格式不正确") + @Size(max = 50, message = "邮箱长度不能超过 50 个字符") + private String email; + + /** + * 手机号码 + */ + @Length(min = 11, max = 11, message = "手机号长度必须 11 位") + private String mobile; + + /** + * 用户性别,参见 SexEnum 枚举类 + */ + private Integer sex; +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/permission/MenuController.http b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/permission/MenuController.http new file mode 100644 index 0000000..fbaff1e --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/permission/MenuController.http @@ -0,0 +1,4 @@ +### 请求 /menu/list 接口 => 成功 +GET {{baseUrl}}/system/menu/list +Authorization: Bearer {{token}} +tenant-id: {{adminTenantId}} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/permission/MenuController.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/permission/MenuController.java new file mode 100644 index 0000000..2e824ca --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/permission/MenuController.java @@ -0,0 +1,97 @@ +package com.tashow.cloud.system.controller.admin.permission; + +import com.tashow.cloud.common.enums.CommonStatusEnum; +import com.tashow.cloud.common.pojo.CommonResult; +import com.tashow.cloud.common.util.object.BeanUtils; +import com.tashow.cloud.system.controller.admin.permission.vo.menu.MenuListReqVO; +import com.tashow.cloud.system.controller.admin.permission.vo.menu.MenuRespVO; +import com.tashow.cloud.system.controller.admin.permission.vo.menu.MenuSaveVO; +import com.tashow.cloud.system.controller.admin.permission.vo.menu.MenuSimpleRespVO; +import com.tashow.cloud.system.dal.dataobject.permission.MenuDO; +import com.tashow.cloud.system.service.permission.MenuService; +import jakarta.annotation.Resource; +import jakarta.validation.Valid; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import java.util.Comparator; +import java.util.List; + +import static com.tashow.cloud.common.pojo.CommonResult.success; + +/** + * 管理后台 - 菜单 + */ +@RestController +@RequestMapping("/system/menu") +@Validated +public class MenuController { + + @Resource + private MenuService menuService; + + /** + * 创建菜单 + */ + @PostMapping("/create") + @PreAuthorize("@ss.hasPermission('system:menu:create')") + public CommonResult createMenu(@Valid @RequestBody MenuSaveVO createReqVO) { + Long menuId = menuService.createMenu(createReqVO); + return success(menuId); + } + + /** + * 修改菜单 + */ + @PutMapping("/update") + @PreAuthorize("@ss.hasPermission('system:menu:update')") + public CommonResult updateMenu(@Valid @RequestBody MenuSaveVO updateReqVO) { + menuService.updateMenu(updateReqVO); + return success(true); + } + + /** + * 删除菜单 + */ + @DeleteMapping("/delete") + @PreAuthorize("@ss.hasPermission('system:menu:delete')") + public CommonResult deleteMenu(@RequestParam("id") Long id) { + menuService.deleteMenu(id); + return success(true); + } + + /** + * 获取菜单列表", description = "用于【菜单管理】界面 + */ + @GetMapping("/list") + @PreAuthorize("@ss.hasPermission('system:menu:query')") + public CommonResult> getMenuList(MenuListReqVO reqVO) { + List list = menuService.getMenuList(reqVO); + list.sort(Comparator.comparing(MenuDO::getSort)); + return success(BeanUtils.toBean(list, MenuRespVO.class)); + } + + /** + * 获取菜单精简信息列表 + */ + @GetMapping({"/list-all-simple", "simple-list"}) + public CommonResult> getSimpleMenuList() { + List list = + menuService.getMenuListByTenant( + new MenuListReqVO().setStatus(CommonStatusEnum.ENABLE.getStatus())); + list = menuService.filterDisableMenus(list); + list.sort(Comparator.comparing(MenuDO::getSort)); + return success(BeanUtils.toBean(list, MenuSimpleRespVO.class)); + } + + /** + * 获取菜单信息 + */ + @GetMapping("/get") + @PreAuthorize("@ss.hasPermission('system:menu:query')") + public CommonResult getMenu(Long id) { + MenuDO menu = menuService.getMenu(id); + return success(BeanUtils.toBean(menu, MenuRespVO.class)); + } +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/permission/PermissionController.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/permission/PermissionController.java new file mode 100644 index 0000000..35a2fc4 --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/permission/PermissionController.java @@ -0,0 +1,94 @@ +package com.tashow.cloud.system.controller.admin.permission; + +import cn.hutool.core.collection.CollUtil; +import com.tashow.cloud.common.pojo.CommonResult; +import com.tashow.cloud.system.controller.admin.permission.vo.permission.PermissionAssignRoleDataScopeReqVO; +import com.tashow.cloud.system.controller.admin.permission.vo.permission.PermissionAssignRoleMenuReqVO; +import com.tashow.cloud.system.controller.admin.permission.vo.permission.PermissionAssignUserRoleReqVO; +import com.tashow.cloud.system.service.permission.PermissionService; +import com.tashow.cloud.system.service.tenant.TenantService; +import jakarta.annotation.Resource; +import jakarta.validation.Valid; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import java.util.Set; + +import static com.tashow.cloud.common.pojo.CommonResult.success; + +/** + * 权限 Controller,提供赋予用户、角色的权限的 API 接口 + * + * @author 芋道源码 + */ + +/** + * 管理后台 - 权限 + */ +@RestController +@RequestMapping("/system/permission") +public class PermissionController { + + @Resource + private PermissionService permissionService; + @Resource + private TenantService tenantService; + + /** + * 获得角色拥有的菜单编号 + */ + @GetMapping("/list-role-menus") + @PreAuthorize("@ss.hasPermission('system:permission:assign-role-menu')") + public CommonResult> getRoleMenuList(Long roleId) { + return success(permissionService.getRoleMenuListByRoleId(roleId)); + } + + /** + * 赋予角色菜单 + */ + @PostMapping("/assign-role-menu") + @PreAuthorize("@ss.hasPermission('system:permission:assign-role-menu')") + public CommonResult assignRoleMenu( + @Validated @RequestBody PermissionAssignRoleMenuReqVO reqVO) { + // 开启多租户的情况下,需要过滤掉未开通的菜单 + tenantService.handleTenantMenu( + menuIds -> reqVO.getMenuIds().removeIf(menuId -> !CollUtil.contains(menuIds, menuId))); + + // 执行菜单的分配 + permissionService.assignRoleMenu(reqVO.getRoleId(), reqVO.getMenuIds()); + return success(true); + } + + /** + * 赋予角色数据权限 + */ + @PostMapping("/assign-role-data-scope") + @PreAuthorize("@ss.hasPermission('system:permission:assign-role-data-scope')") + public CommonResult assignRoleDataScope( + @Valid @RequestBody PermissionAssignRoleDataScopeReqVO reqVO) { + permissionService.assignRoleDataScope( + reqVO.getRoleId(), reqVO.getDataScope(), reqVO.getDataScopeDeptIds()); + return success(true); + } + + /** + * 获得管理员拥有的角色编号列表 + */ + @GetMapping("/list-user-roles") + @PreAuthorize("@ss.hasPermission('system:permission:assign-user-role')") + public CommonResult> listAdminRoles(@RequestParam("userId") Long userId) { + return success(permissionService.getUserRoleIdListByUserId(userId)); + } + + /** + * 赋予用户角色 + */ + @PostMapping("/assign-user-role") + @PreAuthorize("@ss.hasPermission('system:permission:assign-user-role')") + public CommonResult assignUserRole( + @Validated @RequestBody PermissionAssignUserRoleReqVO reqVO) { + permissionService.assignUserRole(reqVO.getUserId(), reqVO.getRoleIds()); + return success(true); + } +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/permission/RoleController.http b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/permission/RoleController.http new file mode 100644 index 0000000..375180a --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/permission/RoleController.http @@ -0,0 +1,42 @@ +### /role/create 成功 +POST {{baseUrl}}/system/role/create +Authorization: Bearer {{token}} +Content-Type: application/json +tenant-id: {{adminTenantId}} + +{ + "name": "测试角色", + "code": "test", + "sort": 0 +} + +### /role/update 成功 +POST {{baseUrl}}/system/role/update +Authorization: Bearer {{token}} +Content-Type: application/json +tenant-id: {{adminTenantId}} + +{ + "id": 100, + "name": "测试角色", + "code": "test", + "sort": 10 +} +### /resource/delete 成功 +POST {{baseUrl}}/system/role/delete +Content-Type: application/x-www-form-urlencoded +Authorization: Bearer {{token}} +tenant-id: {{adminTenantId}} + +roleId=14 + +### /role/get 成功 +GET {{baseUrl}}/system/role/get?id=100 +Content-Type: application/x-www-form-urlencoded +Authorization: Bearer {{token}} +tenant-id: {{adminTenantId}} + +### /role/page 成功 +GET {{baseUrl}}/system/role/page?pageNo=1&pageSize=10 +Authorization: Bearer {{token}} +tenant-id: {{adminTenantId}} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/permission/RoleController.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/permission/RoleController.java new file mode 100644 index 0000000..abff3b0 --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/permission/RoleController.java @@ -0,0 +1,115 @@ +package com.tashow.cloud.system.controller.admin.permission; + +import com.tashow.cloud.common.enums.CommonStatusEnum; +import com.tashow.cloud.common.pojo.CommonResult; +import com.tashow.cloud.common.pojo.PageParam; +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.common.util.object.BeanUtils; +import com.tashow.cloud.excel.excel.core.util.ExcelUtils; +import com.tashow.cloud.system.controller.admin.permission.vo.role.RolePageReqVO; +import com.tashow.cloud.system.controller.admin.permission.vo.role.RoleRespVO; +import com.tashow.cloud.system.controller.admin.permission.vo.role.RoleSaveReqVO; +import com.tashow.cloud.system.dal.dataobject.permission.RoleDO; +import com.tashow.cloud.system.service.permission.RoleService; +import com.tashow.cloud.web.apilog.core.annotation.ApiAccessLog; +import jakarta.annotation.Resource; +import jakarta.servlet.http.HttpServletResponse; +import jakarta.validation.Valid; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import java.io.IOException; +import java.util.Comparator; +import java.util.List; + +import static com.tashow.cloud.common.pojo.CommonResult.success; +import static com.tashow.cloud.web.apilog.core.enums.OperateTypeEnum.EXPORT; +import static java.util.Collections.singleton; + +/** + * 管理后台 - 角色 + */ +@RestController +@RequestMapping("/system/role") +@Validated +public class RoleController { + + @Resource + private RoleService roleService; + + /** + * 创建角色 + */ + @PostMapping("/create") + @PreAuthorize("@ss.hasPermission('system:role:create')") + public CommonResult createRole(@Valid @RequestBody RoleSaveReqVO createReqVO) { + return success(roleService.createRole(createReqVO, null)); + } + + /** + * 修改角色 + */ + @PutMapping("/update") + @PreAuthorize("@ss.hasPermission('system:role:update')") + public CommonResult updateRole(@Valid @RequestBody RoleSaveReqVO updateReqVO) { + roleService.updateRole(updateReqVO); + return success(true); + } + + /** + * 删除角色 + */ + @DeleteMapping("/delete") + @PreAuthorize("@ss.hasPermission('system:role:delete')") + public CommonResult deleteRole(@RequestParam("id") Long id) { + roleService.deleteRole(id); + return success(true); + } + + /** + * 获得角色信息 + */ + @GetMapping("/get") + @PreAuthorize("@ss.hasPermission('system:role:query')") + public CommonResult getRole(@RequestParam("id") Long id) { + RoleDO role = roleService.getRole(id); + return success(BeanUtils.toBean(role, RoleRespVO.class)); + } + + /** + * 获得角色分页 + */ + @GetMapping("/page") + @PreAuthorize("@ss.hasPermission('system:role:query')") + public CommonResult> getRolePage(RolePageReqVO pageReqVO) { + PageResult pageResult = roleService.getRolePage(pageReqVO); + return success(BeanUtils.toBean(pageResult, RoleRespVO.class)); + } + + /** + * 获取角色精简信息列表", description = "只包含被开启的角色,主要用于前端的下拉选项 + */ + @GetMapping({"/list-all-simple", "/simple-list"}) + public CommonResult> getSimpleRoleList() { + List list = + roleService.getRoleListByStatus(singleton(CommonStatusEnum.ENABLE.getStatus())); + list.sort(Comparator.comparing(RoleDO::getSort)); + return success(BeanUtils.toBean(list, RoleRespVO.class)); + } + + /** + * 导出角色 Excel + */ + @GetMapping("/export-excel") + @ApiAccessLog(operateType = EXPORT) + @PreAuthorize("@ss.hasPermission('system:role:export')") + public void export(HttpServletResponse response, @Validated RolePageReqVO exportReqVO) + throws IOException { + exportReqVO.setPageSize(PageParam.PAGE_SIZE_NONE); + List list = roleService.getRolePage(exportReqVO).getList(); + // 输出 + ExcelUtils.write( + response, "角色数据.xls", "数据", RoleRespVO.class, BeanUtils.toBean(list, RoleRespVO.class)); + } +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/permission/vo/menu/MenuListReqVO.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/permission/vo/menu/MenuListReqVO.java new file mode 100644 index 0000000..29125a8 --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/permission/vo/menu/MenuListReqVO.java @@ -0,0 +1,14 @@ +package com.tashow.cloud.system.controller.admin.permission.vo.menu; + +import lombok.Data; + +/** 管理后台 - 菜单列表 Request VO */ +@Data +public class MenuListReqVO { + + /** 菜单名称,模糊匹配 */ + private String name; + + /** 展示状态,参见 CommonStatusEnum 枚举类 */ + private Integer status; +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/permission/vo/menu/MenuRespVO.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/permission/vo/menu/MenuRespVO.java new file mode 100644 index 0000000..ae22a04 --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/permission/vo/menu/MenuRespVO.java @@ -0,0 +1,99 @@ +package com.tashow.cloud.system.controller.admin.permission.vo.menu; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Size; +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * 管理后台 - 菜单信息 Response VO + */ +@Data +public class MenuRespVO { + + /** + * 菜单编号" + */ + private Long id; + + /** + * 菜单名称", example = "芋道 + */ + @NotBlank(message = "菜单名称不能为空") + @Size(max = 50, message = "菜单名称长度不能超过50个字符") + private String name; + + /** + * 权限标识,仅菜单类型为按钮时,才需要传递 + */ + @Size(max = 100) + private String permission; + + /** + * 类型,参见 MenuTypeEnum 枚举类" + */ + @NotNull(message = "菜单类型不能为空") + private Integer type; + + /** + * 显示顺序" + */ + @NotNull(message = "显示顺序不能为空") + private Integer sort; + + /** + * 父菜单 ID" + */ + @NotNull(message = "父菜单 ID 不能为空") + private Long parentId; + + /** + * 路由地址,仅菜单类型为菜单或者目录时,才需要传 + */ + @Size(max = 200, message = "路由地址不能超过200个字符") + private String path; + + /** + * 菜单图标,仅菜单类型为菜单或者目录时,才需要传 + */ + private String icon; + + /** + * 组件路径,仅菜单类型为菜单时,才需要传 + */ + @Size(max = 200, message = "组件路径不能超过255个字符") + private String component; + + /** + * 组件名 + */ + private String componentName; + + /** + * 状态,见 CommonStatusEnum 枚举" + */ + @NotNull(message = "状态不能为空") + private Integer status; + + /** + * 是否可见 + */ + private Boolean visible; + + /** + * 是否缓存 + */ + private Boolean keepAlive; + + /** + * 是否总是显示 + */ + private Boolean alwaysShow; + + /** + * 创建时间", example = "时间戳格式 + */ + private LocalDateTime createTime; +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/permission/vo/menu/MenuSaveVO.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/permission/vo/menu/MenuSaveVO.java new file mode 100644 index 0000000..bda3622 --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/permission/vo/menu/MenuSaveVO.java @@ -0,0 +1,92 @@ +package com.tashow.cloud.system.controller.admin.permission.vo.menu; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Size; +import lombok.Data; + +/** + * 管理后台 - 菜单创建/修改 Request VO + */ +@Data +public class MenuSaveVO { + + /** + * 菜单编号 + */ + private Long id; + + /** + * 菜单名称", example = "芋道 + */ + @NotBlank(message = "菜单名称不能为空") + @Size(max = 50, message = "菜单名称长度不能超过50个字符") + private String name; + + /** + * 权限标识,仅菜单类型为按钮时,才需要传递 + */ + @Size(max = 100) + private String permission; + + /** + * 类型,参见 MenuTypeEnum 枚举类" + */ + @NotNull(message = "菜单类型不能为空") + private Integer type; + + /** + * 显示顺序" + */ + @NotNull(message = "显示顺序不能为空") + private Integer sort; + + /** + * 父菜单 ID" + */ + @NotNull(message = "父菜单 ID 不能为空") + private Long parentId; + + /** + * 路由地址,仅菜单类型为菜单或者目录时,才需要传 + */ + @Size(max = 200, message = "路由地址不能超过200个字符") + private String path; + + /** + * 菜单图标,仅菜单类型为菜单或者目录时,才需要传 + */ + private String icon; + + /** + * 组件路径,仅菜单类型为菜单时,才需要传 + */ + @Size(max = 200, message = "组件路径不能超过255个字符") + private String component; + + /** + * 组件名 + */ + private String componentName; + + /** + * 状态,见 CommonStatusEnum 枚举" + */ + @NotNull(message = "状态不能为空") + private Integer status; + + /** + * 是否可见 + */ + private Boolean visible; + + /** + * 是否缓存 + */ + private Boolean keepAlive; + + /** + * 是否总是显示 + */ + private Boolean alwaysShow; +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/permission/vo/menu/MenuSimpleRespVO.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/permission/vo/menu/MenuSimpleRespVO.java new file mode 100644 index 0000000..c9f348b --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/permission/vo/menu/MenuSimpleRespVO.java @@ -0,0 +1,20 @@ +package com.tashow.cloud.system.controller.admin.permission.vo.menu; + +import lombok.Data; + +/** 管理后台 - 菜单精简信息 Response VO */ +@Data +public class MenuSimpleRespVO { + + /** 菜单编号" */ + private Long id; + + /** 菜单名称", example = "芋道 */ + private String name; + + /** 父菜单 ID" */ + private Long parentId; + + /** 类型,参见 MenuTypeEnum 枚举类" */ + private Integer type; +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/permission/vo/permission/PermissionAssignRoleDataScopeReqVO.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/permission/vo/permission/PermissionAssignRoleDataScopeReqVO.java new file mode 100644 index 0000000..2a0e78e --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/permission/vo/permission/PermissionAssignRoleDataScopeReqVO.java @@ -0,0 +1,34 @@ +package com.tashow.cloud.system.controller.admin.permission.vo.permission; + +import com.tashow.cloud.common.validation.InEnum; +import com.tashow.cloud.systemapi.enums.permission.DataScopeEnum; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +import java.util.Collections; +import java.util.Set; + +/** + * 管理后台 - 赋予角色数据权限 Request VO + */ +@Data +public class PermissionAssignRoleDataScopeReqVO { + + /** + * 角色编号" + */ + @NotNull(message = "角色编号不能为空") + private Long roleId; + + /** + * 数据范围,参见 DataScopeEnum 枚举类" + */ + @NotNull(message = "数据范围不能为空") + @InEnum(value = DataScopeEnum.class, message = "数据范围必须是 {value}") + private Integer dataScope; + + /** + * 部门编号列表,只有范围类型为 DEPT_CUSTOM 时,该字段才需要 + */ + private Set dataScopeDeptIds = Collections.emptySet(); // 兜底 +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/permission/vo/permission/PermissionAssignRoleMenuReqVO.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/permission/vo/permission/PermissionAssignRoleMenuReqVO.java new file mode 100644 index 0000000..88fbeb6 --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/permission/vo/permission/PermissionAssignRoleMenuReqVO.java @@ -0,0 +1,25 @@ +package com.tashow.cloud.system.controller.admin.permission.vo.permission; + +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +import java.util.Collections; +import java.util.Set; + +/** + * 管理后台 - 赋予角色菜单 Request VO + */ +@Data +public class PermissionAssignRoleMenuReqVO { + + /** + * 角色编号" + */ + @NotNull(message = "角色编号不能为空") + private Long roleId; + + /** + * 菜单编号列表 + */ + private Set menuIds = Collections.emptySet(); // 兜底 +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/permission/vo/permission/PermissionAssignUserRoleReqVO.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/permission/vo/permission/PermissionAssignUserRoleReqVO.java new file mode 100644 index 0000000..1e710b6 --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/permission/vo/permission/PermissionAssignUserRoleReqVO.java @@ -0,0 +1,18 @@ +package com.tashow.cloud.system.controller.admin.permission.vo.permission; + +import jakarta.validation.constraints.NotNull; +import java.util.Collections; +import java.util.Set; +import lombok.Data; + +/** 管理后台 - 赋予用户角色 Request VO */ +@Data +public class PermissionAssignUserRoleReqVO { + + /** 用户编号" */ + @NotNull(message = "用户编号不能为空") + private Long userId; + + /** 角色编号列表 */ + private Set roleIds = Collections.emptySet(); // 兜底 +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/permission/vo/role/RolePageReqVO.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/permission/vo/role/RolePageReqVO.java new file mode 100644 index 0000000..70b3c5d --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/permission/vo/role/RolePageReqVO.java @@ -0,0 +1,28 @@ +package com.tashow.cloud.system.controller.admin.permission.vo.role; + +import static com.tashow.cloud.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND; + +import com.tashow.cloud.common.pojo.PageParam; +import java.time.LocalDateTime; +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.springframework.format.annotation.DateTimeFormat; + +/** 管理后台 - 角色分页 Request VO */ +@Data +@EqualsAndHashCode(callSuper = true) +public class RolePageReqVO extends PageParam { + + /** 角色名称,模糊匹配 */ + private String name; + + /** 角色标识,模糊匹配 */ + private String code; + + /** 展示状态,参见 CommonStatusEnum 枚举类 */ + private Integer status; + + /** 创建时间 */ + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) + private LocalDateTime[] createTime; +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/permission/vo/role/RoleRespVO.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/permission/vo/role/RoleRespVO.java new file mode 100644 index 0000000..e688b88 --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/permission/vo/role/RoleRespVO.java @@ -0,0 +1,56 @@ +package com.tashow.cloud.system.controller.admin.permission.vo.role; + +import com.alibaba.excel.annotation.ExcelIgnoreUnannotated; +import com.alibaba.excel.annotation.ExcelProperty; +import com.tashow.cloud.excel.excel.core.annotations.DictFormat; +import com.tashow.cloud.excel.excel.core.convert.DictConvert; +import com.tashow.cloud.systemapi.enums.DictTypeConstants; +import jakarta.validation.constraints.NotBlank; +import java.time.LocalDateTime; +import java.util.Set; +import lombok.Data; + +/** 管理后台 - 角色信息 Response VO */ +@Data +@ExcelIgnoreUnannotated +public class RoleRespVO { + + /** 角色编号" */ + @ExcelProperty("角色序号") + private Long id; + + /** 角色名称", example = "管理员 */ + @ExcelProperty("角色名称") + private String name; + + /** 角色标志" */ + @NotBlank(message = "角色标志不能为空") + @ExcelProperty("角色标志") + private String code; + + /** 显示顺序" */ + @ExcelProperty("角色排序") + private Integer sort; + + /** 状态,参见 CommonStatusEnum 枚举类" */ + @ExcelProperty(value = "角色状态", converter = DictConvert.class) + @DictFormat(DictTypeConstants.COMMON_STATUS) + private Integer status; + + /** 角色类型,参见 RoleTypeEnum 枚举类" */ + private Integer type; + + /** 备注 */ + private String remark; + + /** 数据范围,参见 DataScopeEnum 枚举类" */ + @ExcelProperty(value = "数据范围", converter = DictConvert.class) + @DictFormat(DictTypeConstants.DATA_SCOPE) + private Integer dataScope; + + /** 数据范围(指定部门数组) */ + private Set dataScopeDeptIds; + + /** 创建时间", example = "时间戳格式 */ + private LocalDateTime createTime; +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/permission/vo/role/RoleSaveReqVO.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/permission/vo/role/RoleSaveReqVO.java new file mode 100644 index 0000000..f690731 --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/permission/vo/role/RoleSaveReqVO.java @@ -0,0 +1,45 @@ +package com.tashow.cloud.system.controller.admin.permission.vo.role; + +import com.mzt.logapi.starter.annotation.DiffLogField; +import com.tashow.cloud.common.enums.CommonStatusEnum; +import com.tashow.cloud.common.validation.InEnum; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Size; +import lombok.Data; + +/** 管理后台 - 角色创建/更新 Request VO */ +@Data +public class RoleSaveReqVO { + + /** 角色编号 */ + private Long id; + + /** 角色名称", example = "管理员 */ + @NotBlank(message = "角色名称不能为空") + @Size(max = 30, message = "角色名称长度不能超过 30 个字符") + @DiffLogField(name = "角色名称") + private String name; + + @NotBlank(message = "角色标志不能为空") + @Size(max = 100, message = "角色标志长度不能超过 100 个字符") + /** 角色标志" */ + @DiffLogField(name = "角色标志") + private String code; + + /** 显示顺序" */ + @NotNull(message = "显示顺序不能为空") + @DiffLogField(name = "显示顺序") + private Integer sort; + + /** 状态" */ + @DiffLogField(name = "状态") + @NotNull(message = "状态不能为空") + @InEnum(value = CommonStatusEnum.class, message = "状态必须是 {value}") + private Integer status; + + /** 备注 */ + @Size(max = 500, message = "备注长度不能超过 500 个字符") + @DiffLogField(name = "备注") + private String remark; +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/permission/vo/role/RoleSimpleRespVO.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/permission/vo/role/RoleSimpleRespVO.java new file mode 100644 index 0000000..fb92b16 --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/permission/vo/role/RoleSimpleRespVO.java @@ -0,0 +1,20 @@ +package com.tashow.cloud.system.controller.admin.permission.vo.role; + +import lombok.Data; + +/** + * 管理后台 - 角色精简信息 Response VO + */ +@Data +public class RoleSimpleRespVO { + + /** + * 角色编号" + */ + private Long id; + + /** + * 角色名称", example = "芋道 + */ + private String name; +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/prod/CategoryController.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/prod/CategoryController.java new file mode 100644 index 0000000..6d3b203 --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/prod/CategoryController.java @@ -0,0 +1,41 @@ +/* +package com.tashow.cloud.system.controller.admin.prod; + +import com.tashow.cloud.common.pojo.CommonResult; + +import com.tashow.cloud.productapi.api.product.CategoryApi; +import com.tashow.cloud.productapi.api.product.dto.CategoryDto; +import jakarta.annotation.Resource; +import jakarta.annotation.security.PermitAll; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +import static com.tashow.cloud.common.pojo.CommonResult.success; + +@RestController +@RequestMapping("/category") +@Validated +public class CategoryController { + + @Resource + private CategoryApi categoryService; + + */ +/** + * 获取菜单页面的表 + * @return + *//* + + + @GetMapping("/categoryList") + @PermitAll + public CommonResult> categoryList(@RequestParam(value = "grade", required = false) Integer grade, + @RequestParam(value = "categoryId", required = false) Long categoryId, + @RequestParam(value = "status", required = false) Integer status) { + return success(categoryService.categoryList(grade, categoryId, status)); + } + + +}*/ diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/sms/SmsCallbackController.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/sms/SmsCallbackController.java new file mode 100644 index 0000000..1444cde --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/sms/SmsCallbackController.java @@ -0,0 +1,71 @@ +package com.tashow.cloud.system.controller.admin.sms; + +import com.tashow.cloud.common.pojo.CommonResult; +import com.tashow.cloud.common.util.servlet.ServletUtils; +import com.tashow.cloud.system.framework.sms.core.enums.SmsChannelEnum; +import com.tashow.cloud.system.service.sms.SmsSendService; +import jakarta.annotation.Resource; +import jakarta.annotation.security.PermitAll; +import jakarta.servlet.http.HttpServletRequest; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import static com.tashow.cloud.common.pojo.CommonResult.success; + +/** + * 管理后台 - 短信回调 + */ +@RestController +@RequestMapping("/system/sms/callback") +public class SmsCallbackController { + + @Resource + private SmsSendService smsSendService; + + /** + * 阿里云短信的回调", description = "参见 https://help.aliyun.com/document_detail/120998.html 文档 + */ + @PostMapping("/aliyun") + @PermitAll + public CommonResult receiveAliyunSmsStatus(HttpServletRequest request) throws Throwable { + String text = ServletUtils.getBody(request); + smsSendService.receiveSmsStatus(SmsChannelEnum.ALIYUN.getCode(), text); + return success(true); + } + + /** + * 腾讯云短信的回调", description = "参见 https://cloud.tencent.com/document/product/382/52077 文档 + */ + @PostMapping("/tencent") + @PermitAll + public CommonResult receiveTencentSmsStatus(HttpServletRequest request) + throws Throwable { + String text = ServletUtils.getBody(request); + smsSendService.receiveSmsStatus(SmsChannelEnum.TENCENT.getCode(), text); + return success(true); + } + + /** + * 华为云短信的回调", description = "参见 https://support.huaweicloud.com/api-msgsms/sms_05_0003.html 文档 + */ + @PostMapping("/huawei") + @PermitAll + public CommonResult receiveHuaweiSmsStatus(@RequestBody String requestBody) + throws Throwable { + smsSendService.receiveSmsStatus(SmsChannelEnum.HUAWEI.getCode(), requestBody); + return success(true); + } + + /** + * 七牛云短信的回调", description = "参见 https://developer.qiniu.com/sms/5910/message-push 文档 + */ + @PostMapping("/qiniu") + @PermitAll + public CommonResult receiveQiniuSmsStatus(@RequestBody String requestBody) + throws Throwable { + smsSendService.receiveSmsStatus(SmsChannelEnum.QINIU.getCode(), requestBody); + return success(true); + } +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/sms/SmsChannelController.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/sms/SmsChannelController.java new file mode 100644 index 0000000..35fe916 --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/sms/SmsChannelController.java @@ -0,0 +1,92 @@ +package com.tashow.cloud.system.controller.admin.sms; + +import com.tashow.cloud.common.pojo.CommonResult; +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.common.util.object.BeanUtils; +import com.tashow.cloud.system.controller.admin.sms.vo.channel.SmsChannelPageReqVO; +import com.tashow.cloud.system.controller.admin.sms.vo.channel.SmsChannelRespVO; +import com.tashow.cloud.system.controller.admin.sms.vo.channel.SmsChannelSaveReqVO; +import com.tashow.cloud.system.controller.admin.sms.vo.channel.SmsChannelSimpleRespVO; +import com.tashow.cloud.system.dal.dataobject.sms.SmsChannelDO; +import com.tashow.cloud.system.service.sms.SmsChannelService; +import jakarta.annotation.Resource; +import jakarta.validation.Valid; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; + +import java.util.Comparator; +import java.util.List; + +import static com.tashow.cloud.common.pojo.CommonResult.success; + +/** + * 管理后台 - 短信渠道 + */ +@RestController +@RequestMapping("system/sms-channel") +public class SmsChannelController { + + @Resource + private SmsChannelService smsChannelService; + + /** + * 创建短信渠道 + */ + @PostMapping("/create") + @PreAuthorize("@ss.hasPermission('system:sms-channel:create')") + public CommonResult createSmsChannel(@Valid @RequestBody SmsChannelSaveReqVO createReqVO) { + return success(smsChannelService.createSmsChannel(createReqVO)); + } + + /** + * 更新短信渠道 + */ + @PutMapping("/update") + @PreAuthorize("@ss.hasPermission('system:sms-channel:update')") + public CommonResult updateSmsChannel( + @Valid @RequestBody SmsChannelSaveReqVO updateReqVO) { + smsChannelService.updateSmsChannel(updateReqVO); + return success(true); + } + + /** + * 删除短信渠道 + */ + @DeleteMapping("/delete") + @PreAuthorize("@ss.hasPermission('system:sms-channel:delete')") + public CommonResult deleteSmsChannel(@RequestParam("id") Long id) { + smsChannelService.deleteSmsChannel(id); + return success(true); + } + + /** + * 获得短信渠道 + */ + @GetMapping("/get") + @PreAuthorize("@ss.hasPermission('system:sms-channel:query')") + public CommonResult getSmsChannel(@RequestParam("id") Long id) { + SmsChannelDO channel = smsChannelService.getSmsChannel(id); + return success(BeanUtils.toBean(channel, SmsChannelRespVO.class)); + } + + /** + * 获得短信渠道分页 + */ + @GetMapping("/page") + @PreAuthorize("@ss.hasPermission('system:sms-channel:query')") + public CommonResult> getSmsChannelPage( + @Valid SmsChannelPageReqVO pageVO) { + PageResult pageResult = smsChannelService.getSmsChannelPage(pageVO); + return success(BeanUtils.toBean(pageResult, SmsChannelRespVO.class)); + } + + /** + * 获得短信渠道精简列表", description = "包含被禁用的短信渠道 + */ + @GetMapping({"/list-all-simple", "/simple-list"}) + public CommonResult> getSimpleSmsChannelList() { + List list = smsChannelService.getSmsChannelList(); + list.sort(Comparator.comparing(SmsChannelDO::getId)); + return success(BeanUtils.toBean(list, SmsChannelSimpleRespVO.class)); + } +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/sms/SmsLogController.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/sms/SmsLogController.java new file mode 100644 index 0000000..7aa580c --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/sms/SmsLogController.java @@ -0,0 +1,63 @@ +package com.tashow.cloud.system.controller.admin.sms; + +import com.tashow.cloud.common.pojo.CommonResult; +import com.tashow.cloud.common.pojo.PageParam; +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.common.util.object.BeanUtils; +import com.tashow.cloud.excel.excel.core.util.ExcelUtils; +import com.tashow.cloud.system.controller.admin.sms.vo.log.SmsLogPageReqVO; +import com.tashow.cloud.system.controller.admin.sms.vo.log.SmsLogRespVO; +import com.tashow.cloud.system.dal.dataobject.sms.SmsLogDO; +import com.tashow.cloud.system.service.sms.SmsLogService; +import com.tashow.cloud.web.apilog.core.annotation.ApiAccessLog; +import jakarta.annotation.Resource; +import jakarta.servlet.http.HttpServletResponse; +import jakarta.validation.Valid; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.io.IOException; +import java.util.List; + +import static com.tashow.cloud.common.pojo.CommonResult.success; +import static com.tashow.cloud.web.apilog.core.enums.OperateTypeEnum.EXPORT; + +/** + * 管理后台 - 短信日志 + */ +@RestController +@RequestMapping("/system/sms-log") +@Validated +public class SmsLogController { + + @Resource + private SmsLogService smsLogService; + + /** + * 获得短信日志分页 + */ + @GetMapping("/page") + @PreAuthorize("@ss.hasPermission('system:sms-log:query')") + public CommonResult> getSmsLogPage(@Valid SmsLogPageReqVO pageReqVO) { + PageResult pageResult = smsLogService.getSmsLogPage(pageReqVO); + return success(BeanUtils.toBean(pageResult, SmsLogRespVO.class)); + } + + /** + * 导出短信日志 Excel + */ + @GetMapping("/export-excel") + @PreAuthorize("@ss.hasPermission('system:sms-log:export')") + @ApiAccessLog(operateType = EXPORT) + public void exportSmsLogExcel(@Valid SmsLogPageReqVO exportReqVO, HttpServletResponse response) + throws IOException { + exportReqVO.setPageSize(PageParam.PAGE_SIZE_NONE); + List list = smsLogService.getSmsLogPage(exportReqVO).getList(); + // 导出 Excel + ExcelUtils.write( + response, "短信日志.xls", "数据", SmsLogRespVO.class, BeanUtils.toBean(list, SmsLogRespVO.class)); + } +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/sms/SmsTemplateController.http b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/sms/SmsTemplateController.http new file mode 100644 index 0000000..3b975c3 --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/sms/SmsTemplateController.http @@ -0,0 +1,14 @@ +### 请求 /system/sms-template/send-sms 接口 => 成功 +POST {{baseUrl}}/system/sms-template/send-sms +Authorization: Bearer {{token}} +Content-Type: application/json +tenant-id: {{adminTenantId}} + +{ + "templateCode": "test_01", + "mobile": "15601691390", + "templateParams": { + "operation": "value01", + "code": "value02" + } +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/sms/SmsTemplateController.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/sms/SmsTemplateController.java new file mode 100644 index 0000000..1f4254b --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/sms/SmsTemplateController.java @@ -0,0 +1,124 @@ +package com.tashow.cloud.system.controller.admin.sms; + +import com.tashow.cloud.common.pojo.CommonResult; +import com.tashow.cloud.common.pojo.PageParam; +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.common.util.object.BeanUtils; +import com.tashow.cloud.excel.excel.core.util.ExcelUtils; +import com.tashow.cloud.system.controller.admin.sms.vo.template.SmsTemplatePageReqVO; +import com.tashow.cloud.system.controller.admin.sms.vo.template.SmsTemplateRespVO; +import com.tashow.cloud.system.controller.admin.sms.vo.template.SmsTemplateSaveReqVO; +import com.tashow.cloud.system.controller.admin.sms.vo.template.SmsTemplateSendReqVO; +import com.tashow.cloud.system.dal.dataobject.sms.SmsTemplateDO; +import com.tashow.cloud.system.service.sms.SmsSendService; +import com.tashow.cloud.system.service.sms.SmsTemplateService; +import com.tashow.cloud.web.apilog.core.annotation.ApiAccessLog; +import jakarta.annotation.Resource; +import jakarta.servlet.http.HttpServletResponse; +import jakarta.validation.Valid; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; + +import java.io.IOException; +import java.util.List; + +import static com.tashow.cloud.common.pojo.CommonResult.success; +import static com.tashow.cloud.web.apilog.core.enums.OperateTypeEnum.EXPORT; + +/** + * 管理后台 - 短信模板 + */ +@RestController +@RequestMapping("/system/sms-template") +public class SmsTemplateController { + + @Resource + private SmsTemplateService smsTemplateService; + @Resource + private SmsSendService smsSendService; + + /** + * 创建短信模板 + */ + @PostMapping("/create") + @PreAuthorize("@ss.hasPermission('system:sms-template:create')") + public CommonResult createSmsTemplate( + @Valid @RequestBody SmsTemplateSaveReqVO createReqVO) { + return success(smsTemplateService.createSmsTemplate(createReqVO)); + } + + /** + * 更新短信模板 + */ + @PutMapping("/update") + @PreAuthorize("@ss.hasPermission('system:sms-template:update')") + public CommonResult updateSmsTemplate( + @Valid @RequestBody SmsTemplateSaveReqVO updateReqVO) { + smsTemplateService.updateSmsTemplate(updateReqVO); + return success(true); + } + + /** + * 删除短信模板 + */ + @DeleteMapping("/delete") + @PreAuthorize("@ss.hasPermission('system:sms-template:delete')") + public CommonResult deleteSmsTemplate(@RequestParam("id") Long id) { + smsTemplateService.deleteSmsTemplate(id); + return success(true); + } + + /** + * 获得短信模板 + */ + @GetMapping("/get") + @PreAuthorize("@ss.hasPermission('system:sms-template:query')") + public CommonResult getSmsTemplate(@RequestParam("id") Long id) { + SmsTemplateDO template = smsTemplateService.getSmsTemplate(id); + return success(BeanUtils.toBean(template, SmsTemplateRespVO.class)); + } + + /** + * 获得短信模板分页 + */ + @GetMapping("/page") + @PreAuthorize("@ss.hasPermission('system:sms-template:query')") + public CommonResult> getSmsTemplatePage( + @Valid SmsTemplatePageReqVO pageVO) { + PageResult pageResult = smsTemplateService.getSmsTemplatePage(pageVO); + return success(BeanUtils.toBean(pageResult, SmsTemplateRespVO.class)); + } + + /** + * 导出短信模板 Excel + */ + @GetMapping("/export-excel") + @PreAuthorize("@ss.hasPermission('system:sms-template:export')") + @ApiAccessLog(operateType = EXPORT) + public void exportSmsTemplateExcel( + @Valid SmsTemplatePageReqVO exportReqVO, HttpServletResponse response) throws IOException { + exportReqVO.setPageSize(PageParam.PAGE_SIZE_NONE); + List list = smsTemplateService.getSmsTemplatePage(exportReqVO).getList(); + // 导出 Excel + ExcelUtils.write( + response, + "短信模板.xls", + "数据", + SmsTemplateRespVO.class, + BeanUtils.toBean(list, SmsTemplateRespVO.class)); + } + + /** + * 发送短信 + */ + @PostMapping("/send-sms") + @PreAuthorize("@ss.hasPermission('system:sms-template:send-sms')") + public CommonResult sendSms(@Valid @RequestBody SmsTemplateSendReqVO sendReqVO) { + return success( + smsSendService.sendSingleSmsToAdmin( + sendReqVO.getMobile(), + null, + sendReqVO.getTemplateCode(), + sendReqVO.getTemplateParams())); + } +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/sms/vo/channel/SmsChannelPageReqVO.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/sms/vo/channel/SmsChannelPageReqVO.java new file mode 100644 index 0000000..3d351e1 --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/sms/vo/channel/SmsChannelPageReqVO.java @@ -0,0 +1,34 @@ +package com.tashow.cloud.system.controller.admin.sms.vo.channel; + +import com.tashow.cloud.common.pojo.PageParam; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; +import org.springframework.format.annotation.DateTimeFormat; + +import java.time.LocalDateTime; + +import static com.tashow.cloud.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND; + +/** + * 管理后台 - 短信渠道分页 Request VO + */ +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class SmsChannelPageReqVO extends PageParam { + + /** + * 任务状态 + */ + private Integer status; + + /** + * 短信签名,模糊匹配 + */ + private String signature; + + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) + /** 创建时间 */ + private LocalDateTime[] createTime; +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/sms/vo/channel/SmsChannelRespVO.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/sms/vo/channel/SmsChannelRespVO.java new file mode 100644 index 0000000..d9ce9c4 --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/sms/vo/channel/SmsChannelRespVO.java @@ -0,0 +1,44 @@ +package com.tashow.cloud.system.controller.admin.sms.vo.channel; + +import jakarta.validation.constraints.NotNull; +import java.time.LocalDateTime; +import lombok.Data; +import org.hibernate.validator.constraints.URL; + +/** 管理后台 - 短信渠道 Response VO */ +@Data +public class SmsChannelRespVO { + + /** 编号" */ + private Long id; + + /** 短信签名", example = "芋道源码 */ + @NotNull(message = "短信签名不能为空") + private String signature; + + /** 渠道编码,参见 SmsChannelEnum 枚举类" */ + private String code; + + /** 启用状态" */ + @NotNull(message = "启用状态不能为空") + private Integer status; + + /** 备注 */ + private String remark; + + /** 短信 API 的账号" */ + @NotNull(message = "短信 API 的账号不能为空") + private String apiKey; + + /** 短信 API 的密钥 */ + private String apiSecret; + + /** 短信发送回调 URL */ + @URL(message = "回调 URL 格式不正确") + private String callbackUrl; + + /** + * 创建时间 + */ + private LocalDateTime createTime; +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/sms/vo/channel/SmsChannelSaveReqVO.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/sms/vo/channel/SmsChannelSaveReqVO.java new file mode 100644 index 0000000..ff61329 --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/sms/vo/channel/SmsChannelSaveReqVO.java @@ -0,0 +1,57 @@ +package com.tashow.cloud.system.controller.admin.sms.vo.channel; + +import jakarta.validation.constraints.NotNull; +import lombok.Data; +import org.hibernate.validator.constraints.URL; + +/** + * 管理后台 - 短信渠道创建/修改 Request VO + */ +@Data +public class SmsChannelSaveReqVO { + + /** + * 编号 + */ + private Long id; + + /** + * 短信签名", example = "芋道源码 + */ + @NotNull(message = "短信签名不能为空") + private String signature; + + /** + * 渠道编码,参见 SmsChannelEnum 枚举类" + */ + @NotNull(message = "渠道编码不能为空") + private String code; + + /** + * 启用状态" + */ + @NotNull(message = "启用状态不能为空") + private Integer status; + + /** + * 备注 + */ + private String remark; + + /** + * 短信 API 的账号" + */ + @NotNull(message = "短信 API 的账号不能为空") + private String apiKey; + + /** + * 短信 API 的密钥 + */ + private String apiSecret; + + /** + * 短信发送回调 URL + */ + @URL(message = "回调 URL 格式不正确") + private String callbackUrl; +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/sms/vo/channel/SmsChannelSimpleRespVO.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/sms/vo/channel/SmsChannelSimpleRespVO.java new file mode 100644 index 0000000..1601ff2 --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/sms/vo/channel/SmsChannelSimpleRespVO.java @@ -0,0 +1,17 @@ +package com.tashow.cloud.system.controller.admin.sms.vo.channel; + +import lombok.Data; + +/** 管理后台 - 短信渠道精简 Response VO */ +@Data +public class SmsChannelSimpleRespVO { + + /** 编号" */ + private Long id; + + /** 短信签名", example = "芋道源码 */ + private String signature; + + /** 渠道编码,参见 SmsChannelEnum 枚举类" */ + private String code; +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/sms/vo/log/SmsLogPageReqVO.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/sms/vo/log/SmsLogPageReqVO.java new file mode 100644 index 0000000..cc71677 --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/sms/vo/log/SmsLogPageReqVO.java @@ -0,0 +1,40 @@ +package com.tashow.cloud.system.controller.admin.sms.vo.log; + +import static com.tashow.cloud.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND; + +import com.tashow.cloud.common.pojo.PageParam; +import java.time.LocalDateTime; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; +import org.springframework.format.annotation.DateTimeFormat; + +/** 管理后台 - 短信日志分页 Request VO */ +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class SmsLogPageReqVO extends PageParam { + + /** 短信渠道编号 */ + private Long channelId; + + /** 模板编号 */ + private Long templateId; + + /** 手机号 */ + private String mobile; + + /** 发送状态,参见 SmsSendStatusEnum 枚举类 */ + private Integer sendStatus; + + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) + /** 发送时间 */ + private LocalDateTime[] sendTime; + + /** 接收状态,参见 SmsReceiveStatusEnum 枚举类 */ + private Integer receiveStatus; + + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) + /** 接收时间 */ + private LocalDateTime[] receiveTime; +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/sms/vo/log/SmsLogRespVO.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/sms/vo/log/SmsLogRespVO.java new file mode 100644 index 0000000..bc34632 --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/sms/vo/log/SmsLogRespVO.java @@ -0,0 +1,115 @@ +package com.tashow.cloud.system.controller.admin.sms.vo.log; + +import com.alibaba.excel.annotation.ExcelIgnoreUnannotated; +import com.alibaba.excel.annotation.ExcelProperty; +import com.tashow.cloud.excel.excel.core.annotations.DictFormat; +import com.tashow.cloud.excel.excel.core.convert.DictConvert; +import com.tashow.cloud.excel.excel.core.convert.JsonConvert; +import com.tashow.cloud.systemapi.enums.DictTypeConstants; +import java.time.LocalDateTime; +import java.util.Map; +import lombok.Data; + +/** 管理后台 - 短信日志 Response VO */ +@Data +@ExcelIgnoreUnannotated +public class SmsLogRespVO { + + /** 编号" */ + @ExcelProperty("编号") + private Long id; + + /** 短信渠道编号" */ + @ExcelProperty("短信渠道编号") + private Long channelId; + + /** 短信渠道编码" */ + @ExcelProperty("短信渠道编码") + private String channelCode; + + /** 模板编号" */ + @ExcelProperty("模板编号") + private Long templateId; + + /** 模板编码" */ + @ExcelProperty("模板编码") + private String templateCode; + + /** 短信类型" */ + @ExcelProperty(value = "短信类型", converter = DictConvert.class) + @DictFormat(DictTypeConstants.SMS_TEMPLATE_TYPE) + private Integer templateType; + + /** 短信内容", example = "你好,你的验证码是 1024 */ + @ExcelProperty("短信内容") + private String templateContent; + + /** 短信参数" */ + @ExcelProperty(value = "短信参数", converter = JsonConvert.class) + private Map templateParams; + + /** 短信 API 的模板编号" */ + @ExcelProperty("短信 API 的模板编号") + private String apiTemplateId; + + /** 手机号" */ + @ExcelProperty("手机号") + private String mobile; + + /** 用户编号 */ + @ExcelProperty("用户编号") + private Long userId; + + /** 用户类型 */ + @ExcelProperty(value = "用户类型", converter = DictConvert.class) + @DictFormat(DictTypeConstants.USER_TYPE) + private Integer userType; + + /** 发送状态" */ + @ExcelProperty(value = "发送状态", converter = DictConvert.class) + @DictFormat(DictTypeConstants.SMS_SEND_STATUS) + private Integer sendStatus; + + /** 发送时间 */ + @ExcelProperty("发送时间") + private LocalDateTime sendTime; + + /** 短信 API 发送结果的编码 */ + @ExcelProperty("短信 API 发送结果的编码") + private String apiSendCode; + + /** 短信 API 发送失败的提示 */ + @ExcelProperty("短信 API 发送失败的提示") + private String apiSendMsg; + + /** 短信 API 发送返回的唯一请求 ID */ + @ExcelProperty("短信 API 发送返回的唯一请求 ID") + private String apiRequestId; + + /** 短信 API 发送返回的序号 */ + @ExcelProperty("短信 API 发送返回的序号") + private String apiSerialNo; + + /** 接收状态" */ + @ExcelProperty(value = "接收状态", converter = DictConvert.class) + @DictFormat(DictTypeConstants.SMS_RECEIVE_STATUS) + private Integer receiveStatus; + + /** 接收时间 */ + @ExcelProperty("接收时间") + private LocalDateTime receiveTime; + + /** API 接收结果的编码 */ + @ExcelProperty("API 接收结果的编码") + private String apiReceiveCode; + + /** API 接收结果的说明 */ + @ExcelProperty("API 接收结果的说明") + private String apiReceiveMsg; + + /** + * 创建时间 + */ + @ExcelProperty("创建时间") + private LocalDateTime createTime; +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/sms/vo/template/SmsTemplatePageReqVO.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/sms/vo/template/SmsTemplatePageReqVO.java new file mode 100644 index 0000000..bc6fe60 --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/sms/vo/template/SmsTemplatePageReqVO.java @@ -0,0 +1,54 @@ +package com.tashow.cloud.system.controller.admin.sms.vo.template; + +import com.tashow.cloud.common.pojo.PageParam; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; +import org.springframework.format.annotation.DateTimeFormat; + +import java.time.LocalDateTime; + +import static com.tashow.cloud.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND; + +/** + * 管理后台 - 短信模板分页 Request VO + */ +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class SmsTemplatePageReqVO extends PageParam { + + /** + * 短信签名 + */ + private Integer type; + + /** + * 开启状态 + */ + private Integer status; + + /** + * 模板编码,模糊匹配 + */ + private String code; + + /** + * 模板内容,模糊匹配 + */ + private String content; + + /** + * 短信 API 的模板编号,模糊匹配 + */ + private String apiTemplateId; + + /** + * 短信渠道编号 + */ + private Long channelId; + + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) + /** 创建时间 */ + private LocalDateTime[] createTime; +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/sms/vo/template/SmsTemplateRespVO.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/sms/vo/template/SmsTemplateRespVO.java new file mode 100644 index 0000000..cd69b00 --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/sms/vo/template/SmsTemplateRespVO.java @@ -0,0 +1,93 @@ +package com.tashow.cloud.system.controller.admin.sms.vo.template; + +import com.alibaba.excel.annotation.ExcelIgnoreUnannotated; +import com.alibaba.excel.annotation.ExcelProperty; +import com.tashow.cloud.excel.excel.core.annotations.DictFormat; +import com.tashow.cloud.excel.excel.core.convert.DictConvert; +import com.tashow.cloud.systemapi.enums.DictTypeConstants; +import lombok.Data; + +import java.time.LocalDateTime; +import java.util.List; + +/** + * 管理后台 - 短信模板 Response VO + */ +@Data +@ExcelIgnoreUnannotated +public class SmsTemplateRespVO { + + /** + * 编号" + */ + @ExcelProperty("编号") + private Long id; + + /** + * 短信类型,参见 SmsTemplateTypeEnum 枚举类" + */ + @ExcelProperty(value = "短信签名", converter = DictConvert.class) + @DictFormat(DictTypeConstants.SMS_TEMPLATE_TYPE) + private Integer type; + + /** + * 开启状态,参见 CommonStatusEnum 枚举类" + */ + @ExcelProperty(value = "开启状态", converter = DictConvert.class) + @DictFormat(DictTypeConstants.COMMON_STATUS) + private Integer status; + + /** + * 模板编码" + */ + @ExcelProperty("模板编码") + private String code; + + /** + * 模板名称" + */ + @ExcelProperty("模板名称") + private String name; + + /** + * 模板内容", example = "你好,{name}。你长的太{like}啦! + */ + @ExcelProperty("模板内容") + private String content; + + /** + * 参数数组 + */ + private List params; + + /** + * 备注 + */ + @ExcelProperty("备注") + private String remark; + + /** + * 短信 API 的模板编号" + */ + @ExcelProperty("短信 API 的模板编号") + private String apiTemplateId; + + /** + * 短信渠道编号" + */ + @ExcelProperty("短信渠道编号") + private Long channelId; + + /** + * 短信渠道编码" + */ + @ExcelProperty(value = "短信渠道编码", converter = DictConvert.class) + @DictFormat(DictTypeConstants.SMS_CHANNEL_CODE) + private String channelCode; + + /** + * 创建时间 + */ + @ExcelProperty("创建时间") + private LocalDateTime createTime; +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/sms/vo/template/SmsTemplateSaveReqVO.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/sms/vo/template/SmsTemplateSaveReqVO.java new file mode 100644 index 0000000..1e62a30 --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/sms/vo/template/SmsTemplateSaveReqVO.java @@ -0,0 +1,43 @@ +package com.tashow.cloud.system.controller.admin.sms.vo.template; + +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +/** 管理后台 - 短信模板创建/修改 Request VO */ +@Data +public class SmsTemplateSaveReqVO { + + /** 编号 */ + private Long id; + + /** 短信类型,参见 SmsTemplateTypeEnum 枚举类" */ + @NotNull(message = "短信类型不能为空") + private Integer type; + + /** 开启状态,参见 CommonStatusEnum 枚举类" */ + @NotNull(message = "开启状态不能为空") + private Integer status; + + /** 模板编码" */ + @NotNull(message = "模板编码不能为空") + private String code; + + /** 模板名称" */ + @NotNull(message = "模板名称不能为空") + private String name; + + /** 模板内容", example = "你好,{name}。你长的太{like}啦! */ + @NotNull(message = "模板内容不能为空") + private String content; + + /** 备注 */ + private String remark; + + /** 短信 API 的模板编号" */ + @NotNull(message = "短信 API 的模板编号不能为空") + private String apiTemplateId; + + /** 短信渠道编号" */ + @NotNull(message = "短信渠道编号不能为空") + private Long channelId; +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/sms/vo/template/SmsTemplateSendReqVO.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/sms/vo/template/SmsTemplateSendReqVO.java new file mode 100644 index 0000000..80a590f --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/sms/vo/template/SmsTemplateSendReqVO.java @@ -0,0 +1,21 @@ +package com.tashow.cloud.system.controller.admin.sms.vo.template; + +import jakarta.validation.constraints.NotNull; +import java.util.Map; +import lombok.Data; + +/** 管理后台 - 短信模板的发送 Request VO */ +@Data +public class SmsTemplateSendReqVO { + + /** 手机号" */ + @NotNull(message = "手机号不能为空") + private String mobile; + + /** 模板编码" */ + @NotNull(message = "模板编码不能为空") + private String templateCode; + + /** 模板参数 */ + private Map templateParams; +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/socail/SocialClientController.http b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/socail/SocialClientController.http new file mode 100644 index 0000000..9909b51 --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/socail/SocialClientController.http @@ -0,0 +1,20 @@ +### 请求 /system/social-client/send-subscribe-message 接口 => 发送测试订阅消息 +POST {{baseUrl}}/system/social-client/send-subscribe-message +Authorization: Bearer {{token}} +Content-Type: application/json +#Authorization: Bearer test100 +tenant-id: {{adminTenantId}} + +{ + "userId": 247, + "userType": 1, + "socialType": 34, + "templateTitle": "充值成功通知", + "page": "", + "messages": { + "character_string1":"5616122165165", + "amount2":"1000.00", + "time3":"2024-01-01 10:10:10", + "phrase4": "充值成功" + } +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/socail/SocialClientController.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/socail/SocialClientController.java new file mode 100644 index 0000000..3376e03 --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/socail/SocialClientController.java @@ -0,0 +1,95 @@ +package com.tashow.cloud.system.controller.admin.socail; + +import com.tashow.cloud.common.pojo.CommonResult; +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.common.util.object.BeanUtils; +import com.tashow.cloud.system.controller.admin.socail.vo.client.SocialClientPageReqVO; +import com.tashow.cloud.system.controller.admin.socail.vo.client.SocialClientRespVO; +import com.tashow.cloud.system.controller.admin.socail.vo.client.SocialClientSaveReqVO; +import com.tashow.cloud.system.dal.dataobject.social.SocialClientDO; +import com.tashow.cloud.system.service.social.SocialClientService; +import com.tashow.cloud.systemapi.api.social.SocialClientApi; +import com.tashow.cloud.systemapi.api.social.dto.SocialWxaSubscribeMessageSendReqDTO; +import jakarta.annotation.Resource; +import jakarta.validation.Valid; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import static com.tashow.cloud.common.pojo.CommonResult.success; + +/** + * 管理后台 - 社交客户端 + */ +@RestController +@RequestMapping("/system/social-client") +@Validated +public class SocialClientController { + + @Resource + private SocialClientService socialClientService; + @Resource + private SocialClientApi socialClientApi; + + /** + * 创建社交客户端 + */ + @PostMapping("/create") + @PreAuthorize("@ss.hasPermission('system:social-client:create')") + public CommonResult createSocialClient( + @Valid @RequestBody SocialClientSaveReqVO createReqVO) { + return success(socialClientService.createSocialClient(createReqVO)); + } + + /** + * 更新社交客户端 + */ + @PutMapping("/update") + @PreAuthorize("@ss.hasPermission('system:social-client:update')") + public CommonResult updateSocialClient( + @Valid @RequestBody SocialClientSaveReqVO updateReqVO) { + socialClientService.updateSocialClient(updateReqVO); + return success(true); + } + + /** + * 删除社交客户端 + */ + @DeleteMapping("/delete") + @PreAuthorize("@ss.hasPermission('system:social-client:delete')") + public CommonResult deleteSocialClient(@RequestParam("id") Long id) { + socialClientService.deleteSocialClient(id); + return success(true); + } + + /** + * 获得社交客户端 + */ + @GetMapping("/get") + @PreAuthorize("@ss.hasPermission('system:social-client:query')") + public CommonResult getSocialClient(@RequestParam("id") Long id) { + SocialClientDO client = socialClientService.getSocialClient(id); + return success(BeanUtils.toBean(client, SocialClientRespVO.class)); + } + + /** + * 获得社交客户端分页 + */ + @GetMapping("/page") + @PreAuthorize("@ss.hasPermission('system:social-client:query')") + public CommonResult> getSocialClientPage( + @Valid SocialClientPageReqVO pageVO) { + PageResult pageResult = socialClientService.getSocialClientPage(pageVO); + return success(BeanUtils.toBean(pageResult, SocialClientRespVO.class)); + } + + /** + * 发送订阅消息 + */ + // 用于测试 + @PostMapping("/send-subscribe-message") + @PreAuthorize("@ss.hasPermission('system:social-client:query')") + public void sendSubscribeMessage(@RequestBody SocialWxaSubscribeMessageSendReqDTO reqDTO) { + socialClientApi.sendWxaSubscribeMessage(reqDTO).checkError(); + } +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/socail/SocialUserController.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/socail/SocialUserController.java new file mode 100644 index 0000000..eb54db7 --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/socail/SocialUserController.java @@ -0,0 +1,76 @@ +package com.tashow.cloud.system.controller.admin.socail; + +import com.tashow.cloud.common.enums.UserTypeEnum; +import com.tashow.cloud.common.pojo.CommonResult; +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.common.util.object.BeanUtils; +import com.tashow.cloud.system.controller.admin.socail.vo.user.SocialUserBindReqVO; +import com.tashow.cloud.system.controller.admin.socail.vo.user.SocialUserPageReqVO; +import com.tashow.cloud.system.controller.admin.socail.vo.user.SocialUserRespVO; +import com.tashow.cloud.system.controller.admin.socail.vo.user.SocialUserUnbindReqVO; +import com.tashow.cloud.system.convert.social.SocialUserConvert; +import com.tashow.cloud.system.dal.dataobject.social.SocialUserDO; +import com.tashow.cloud.system.service.social.SocialUserService; +import jakarta.annotation.Resource; +import jakarta.validation.Valid; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import static com.tashow.cloud.common.pojo.CommonResult.success; +import static com.tashow.cloud.web.web.core.util.WebFrameworkUtils.getLoginUserId; + +/** + * 管理后台 - 社交用户 + */ +@RestController +@RequestMapping("/system/social-user") +@Validated +public class SocialUserController { + + @Resource + private SocialUserService socialUserService; + + /** + * 社交绑定,使用 code 授权码 + */ + @PostMapping("/bind") + public CommonResult socialBind(@RequestBody @Valid SocialUserBindReqVO reqVO) { + socialUserService.bindSocialUser( + SocialUserConvert.INSTANCE.convert(getLoginUserId(), UserTypeEnum.ADMIN.getValue(), reqVO)); + return CommonResult.success(true); + } + + /** + * 取消社交绑定 + */ + @DeleteMapping("/unbind") + public CommonResult socialUnbind(@RequestBody SocialUserUnbindReqVO reqVO) { + socialUserService.unbindSocialUser( + getLoginUserId(), UserTypeEnum.ADMIN.getValue(), reqVO.getType(), reqVO.getOpenid()); + return CommonResult.success(true); + } + + // ==================== 社交用户 CRUD ==================== + + /** + * 获得社交用户 + */ + @GetMapping("/get") + @PreAuthorize("@ss.hasPermission('system:social-user:query')") + public CommonResult getSocialUser(@RequestParam("id") Long id) { + SocialUserDO socialUser = socialUserService.getSocialUser(id); + return success(BeanUtils.toBean(socialUser, SocialUserRespVO.class)); + } + + /** + * 获得社交用户分页 + */ + @GetMapping("/page") + @PreAuthorize("@ss.hasPermission('system:social-user:query')") + public CommonResult> getSocialUserPage( + @Valid SocialUserPageReqVO pageVO) { + PageResult pageResult = socialUserService.getSocialUserPage(pageVO); + return success(BeanUtils.toBean(pageResult, SocialUserRespVO.class)); + } +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/socail/vo/client/SocialClientPageReqVO.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/socail/vo/client/SocialClientPageReqVO.java new file mode 100644 index 0000000..dfc79b2 --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/socail/vo/client/SocialClientPageReqVO.java @@ -0,0 +1,40 @@ +package com.tashow.cloud.system.controller.admin.socail.vo.client; + +import com.tashow.cloud.common.pojo.PageParam; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; + +/** + * 管理后台 - 社交客户端分页 Request VO + */ +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class SocialClientPageReqVO extends PageParam { + + /** + * 应用名 + */ + private String name; + + /** + * 社交平台的类型 + */ + private Integer socialType; + + /** + * 用户类型 + */ + private Integer userType; + + /** + * 客户端编号 + */ + private String clientId; + + /** + * 状态 + */ + private Integer status; +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/socail/vo/client/SocialClientRespVO.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/socail/vo/client/SocialClientRespVO.java new file mode 100644 index 0000000..86e653e --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/socail/vo/client/SocialClientRespVO.java @@ -0,0 +1,38 @@ +package com.tashow.cloud.system.controller.admin.socail.vo.client; + +import java.time.LocalDateTime; +import lombok.Data; + +/** 管理后台 - 社交客户端 Response VO */ +@Data +public class SocialClientRespVO { + + /** 编号" */ + private Long id; + + /** 应用名" */ + private String name; + + /** 社交平台的类型" */ + private Integer socialType; + + /** 用户类型" */ + private Integer userType; + + /** 客户端编号" */ + private String clientId; + + /** 客户端密钥" */ + private String clientSecret; + + /** 授权方的网页应用编号" */ + private String agentId; + + /** 状态" */ + private Integer status; + + /** + * 创建时间 + */ + private LocalDateTime createTime; +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/socail/vo/client/SocialClientSaveReqVO.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/socail/vo/client/SocialClientSaveReqVO.java new file mode 100644 index 0000000..b7b0d8a --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/socail/vo/client/SocialClientSaveReqVO.java @@ -0,0 +1,58 @@ +package com.tashow.cloud.system.controller.admin.socail.vo.client; + +import cn.hutool.core.util.StrUtil; +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.tashow.cloud.common.enums.CommonStatusEnum; +import com.tashow.cloud.common.enums.UserTypeEnum; +import com.tashow.cloud.common.validation.InEnum; +import com.tashow.cloud.systemapi.enums.social.SocialTypeEnum; +import jakarta.validation.constraints.AssertTrue; +import jakarta.validation.constraints.NotNull; +import java.util.Objects; +import lombok.Data; + +/** 管理后台 - 社交客户端创建/修改 Request VO */ +@Data +public class SocialClientSaveReqVO { + + /** 编号 */ + private Long id; + + /** 应用名" */ + @NotNull(message = "应用名不能为空") + private String name; + + /** 社交平台的类型" */ + @NotNull(message = "社交平台的类型不能为空") + @InEnum(SocialTypeEnum.class) + private Integer socialType; + + /** 用户类型" */ + @NotNull(message = "用户类型不能为空") + @InEnum(UserTypeEnum.class) + private Integer userType; + + /** 客户端编号" */ + @NotNull(message = "客户端编号不能为空") + private String clientId; + + /** 客户端密钥" */ + @NotNull(message = "客户端密钥不能为空") + private String clientSecret; + + /** 授权方的网页应用编号" */ + private String agentId; + + /** 状态" */ + @NotNull(message = "状态不能为空") + @InEnum(CommonStatusEnum.class) + private Integer status; + + @AssertTrue(message = "agentId 不能为空") + @JsonIgnore + public boolean isAgentIdValid() { + // 如果是企业微信,必须填写 agentId 属性 + return !Objects.equals(socialType, SocialTypeEnum.WECHAT_ENTERPRISE.getType()) + || !StrUtil.isEmpty(agentId); + } +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/socail/vo/user/SocialUserBindReqVO.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/socail/vo/user/SocialUserBindReqVO.java new file mode 100644 index 0000000..ae500cf --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/socail/vo/user/SocialUserBindReqVO.java @@ -0,0 +1,39 @@ +package com.tashow.cloud.system.controller.admin.socail.vo.user; + +import com.tashow.cloud.common.validation.InEnum; +import com.tashow.cloud.systemapi.enums.social.SocialTypeEnum; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 管理后台 - 社交绑定 Request VO,使用 code 授权码 + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +@Builder +public class SocialUserBindReqVO { + + /** + * 社交平台的类型,参见 UserSocialTypeEnum 枚举值" + */ + @InEnum(SocialTypeEnum.class) + @NotNull(message = "社交平台的类型不能为空") + private Integer type; + + /** + * 授权码" + */ + @NotEmpty(message = "授权码不能为空") + private String code; + + /** + * state" + */ + @NotEmpty(message = "state 不能为空") + private String state; +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/socail/vo/user/SocialUserPageReqVO.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/socail/vo/user/SocialUserPageReqVO.java new file mode 100644 index 0000000..ccc2143 --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/socail/vo/user/SocialUserPageReqVO.java @@ -0,0 +1,30 @@ +package com.tashow.cloud.system.controller.admin.socail.vo.user; + +import static com.tashow.cloud.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND; + +import com.tashow.cloud.common.pojo.PageParam; +import java.time.LocalDateTime; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; +import org.springframework.format.annotation.DateTimeFormat; + +/** 管理后台 - 社交用户分页 Request VO */ +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class SocialUserPageReqVO extends PageParam { + + /** 社交平台的类型 */ + private Integer type; + + /** 用户昵称 */ + private String nickname; + + /** 社交 openid */ + private String openid; + + /** 创建时间 */ + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) + private LocalDateTime[] createTime; +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/socail/vo/user/SocialUserRespVO.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/socail/vo/user/SocialUserRespVO.java new file mode 100644 index 0000000..190640c --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/socail/vo/user/SocialUserRespVO.java @@ -0,0 +1,49 @@ +package com.tashow.cloud.system.controller.admin.socail.vo.user; + +import java.time.LocalDateTime; +import lombok.Data; + +/** 管理后台 - 社交用户 Response VO */ +@Data +public class SocialUserRespVO { + + /** 主键(自增策略)" */ + private Long id; + + /** 社交平台的类型" */ + private Integer type; + + /** 社交 openid" */ + private String openid; + + /** 社交 token" */ + private String token; + + /** 原始 Token 数据,一般是 JSON 格式", example = "{} */ + private String rawTokenInfo; + + /** 用户昵称", example = "芋艿 */ + private String nickname; + + /** 用户头像 */ + private String avatar; + + /** 原始用户数据,一般是 JSON 格式", example = "{} */ + private String rawUserInfo; + + /** 最后一次的认证 code" */ + private String code; + + /** 最后一次的认证 state" */ + private String state; + + /** + * 创建时间 + */ + private LocalDateTime createTime; + + /** + * 更新时间 + */ + private LocalDateTime updateTime; +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/socail/vo/user/SocialUserUnbindReqVO.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/socail/vo/user/SocialUserUnbindReqVO.java new file mode 100644 index 0000000..206526b --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/socail/vo/user/SocialUserUnbindReqVO.java @@ -0,0 +1,33 @@ +package com.tashow.cloud.system.controller.admin.socail.vo.user; + +import com.tashow.cloud.common.validation.InEnum; +import com.tashow.cloud.systemapi.enums.social.SocialTypeEnum; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 管理后台 - 取消社交绑定 Request VO + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +@Builder +public class SocialUserUnbindReqVO { + + /** + * 社交平台的类型,参见 UserSocialTypeEnum 枚举值" + */ + @InEnum(SocialTypeEnum.class) + @NotNull(message = "社交平台的类型不能为空") + private Integer type; + + /** + * 社交用户的 openid" + */ + @NotEmpty(message = "社交用户的 openid 不能为空") + private String openid; +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/tenant/TenantController.http b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/tenant/TenantController.http new file mode 100644 index 0000000..38aa594 --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/tenant/TenantController.http @@ -0,0 +1,21 @@ +### 获取租户编号 /admin-api/system/get-id-by-name +GET {{baseUrl}}/system/tenant/get-id-by-name?name=芋道源码 + +### 创建租户 /admin-api/system/tenant/create +POST {{baseUrl}}/system/tenant/create +Content-Type: application/json +Authorization: Bearer {{token}} +tenant-id: {{adminTenantId}} + +{ + "name": "芋道", + "contactName": "芋艿", + "contactMobile": "15601691300", + "status": 0, + "domain": "https://www.iocoder.cn", + "packageId": 110, + "expireTime": 1699545600000, + "accountCount": 20, + "username": "admin", + "password": "123321" +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/tenant/TenantController.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/tenant/TenantController.java new file mode 100644 index 0000000..44e0657 --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/tenant/TenantController.java @@ -0,0 +1,122 @@ +package com.tashow.cloud.system.controller.admin.tenant; + +import com.tashow.cloud.common.pojo.CommonResult; +import com.tashow.cloud.common.pojo.PageParam; +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.common.util.object.BeanUtils; +import com.tashow.cloud.excel.excel.core.util.ExcelUtils; +import com.tashow.cloud.system.controller.admin.tenant.vo.tenant.TenantPageReqVO; +import com.tashow.cloud.system.controller.admin.tenant.vo.tenant.TenantRespVO; +import com.tashow.cloud.system.controller.admin.tenant.vo.tenant.TenantSaveReqVO; +import com.tashow.cloud.system.controller.admin.tenant.vo.tenant.TenantSimpleRespVO; +import com.tashow.cloud.system.dal.dataobject.tenant.TenantDO; +import com.tashow.cloud.system.service.tenant.TenantService; +import com.tashow.cloud.web.apilog.core.annotation.ApiAccessLog; +import jakarta.annotation.Resource; +import jakarta.annotation.security.PermitAll; +import jakarta.servlet.http.HttpServletResponse; +import jakarta.validation.Valid; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; + +import java.io.IOException; +import java.util.List; + +import static com.tashow.cloud.common.pojo.CommonResult.success; +import static com.tashow.cloud.web.apilog.core.enums.OperateTypeEnum.EXPORT; + +/** + * 管理后台 - 租户 + */ +@RestController +@RequestMapping("/system/tenant") +public class TenantController { + + @Resource + private TenantService tenantService; + + /** + * 使用租户名,获得租户编号", description = "登录界面,根据用户的租户名,获得租户编号 + */ + @GetMapping("/get-id-by-name") + @PermitAll + public CommonResult getTenantIdByName(@RequestParam("name") String name) { + TenantDO tenant = tenantService.getTenantByName(name); + return success(tenant != null ? tenant.getId() : null); + } + + /** + * 使用域名,获得租户信息", description = "登录界面,根据用户的域名,获得租户信息 + */ + @GetMapping("/get-by-website") + @PermitAll + public CommonResult getTenantByWebsite( + @RequestParam("website") String website) { + TenantDO tenant = tenantService.getTenantByWebsite(website); + return success(BeanUtils.toBean(tenant, TenantSimpleRespVO.class)); + } + + /** + * 创建租户 + */ + @PostMapping("/create") + @PreAuthorize("@ss.hasPermission('system:tenant:create')") + public CommonResult createTenant(@Valid @RequestBody TenantSaveReqVO createReqVO) { + return success(tenantService.createTenant(createReqVO)); + } + + /** + * 更新租户 + */ + @PutMapping("/update") + @PreAuthorize("@ss.hasPermission('system:tenant:update')") + public CommonResult updateTenant(@Valid @RequestBody TenantSaveReqVO updateReqVO) { + tenantService.updateTenant(updateReqVO); + return success(true); + } + + /** + * 删除租户 + */ + @DeleteMapping("/delete") + @PreAuthorize("@ss.hasPermission('system:tenant:delete')") + public CommonResult deleteTenant(@RequestParam("id") Long id) { + tenantService.deleteTenant(id); + return success(true); + } + + /** + * 获得租户 + */ + @GetMapping("/get") + @PreAuthorize("@ss.hasPermission('system:tenant:query')") + public CommonResult getTenant(@RequestParam("id") Long id) { + TenantDO tenant = tenantService.getTenant(id); + return success(BeanUtils.toBean(tenant, TenantRespVO.class)); + } + + /** + * 获得租户分页 + */ + @GetMapping("/page") + @PreAuthorize("@ss.hasPermission('system:tenant:query')") + public CommonResult> getTenantPage(@Valid TenantPageReqVO pageVO) { + PageResult pageResult = tenantService.getTenantPage(pageVO); + return success(BeanUtils.toBean(pageResult, TenantRespVO.class)); + } + + /** + * 导出租户 Excel + */ + @GetMapping("/export-excel") + @PreAuthorize("@ss.hasPermission('system:tenant:export')") + @ApiAccessLog(operateType = EXPORT) + public void exportTenantExcel(@Valid TenantPageReqVO exportReqVO, HttpServletResponse response) + throws IOException { + exportReqVO.setPageSize(PageParam.PAGE_SIZE_NONE); + List list = tenantService.getTenantPage(exportReqVO).getList(); + // 导出 Excel + ExcelUtils.write( + response, "租户.xls", "数据", TenantRespVO.class, BeanUtils.toBean(list, TenantRespVO.class)); + } +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/tenant/TenantPackageController.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/tenant/TenantPackageController.java new file mode 100644 index 0000000..01b32a6 --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/tenant/TenantPackageController.java @@ -0,0 +1,95 @@ +package com.tashow.cloud.system.controller.admin.tenant; + +import com.tashow.cloud.common.enums.CommonStatusEnum; +import com.tashow.cloud.common.pojo.CommonResult; +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.common.util.object.BeanUtils; +import com.tashow.cloud.system.controller.admin.tenant.vo.packages.TenantPackagePageReqVO; +import com.tashow.cloud.system.controller.admin.tenant.vo.packages.TenantPackageRespVO; +import com.tashow.cloud.system.controller.admin.tenant.vo.packages.TenantPackageSaveReqVO; +import com.tashow.cloud.system.controller.admin.tenant.vo.packages.TenantPackageSimpleRespVO; +import com.tashow.cloud.system.dal.dataobject.tenant.TenantPackageDO; +import com.tashow.cloud.system.service.tenant.TenantPackageService; +import jakarta.annotation.Resource; +import jakarta.validation.Valid; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +import static com.tashow.cloud.common.pojo.CommonResult.success; + +/** + * 管理后台 - 租户套餐 + */ +@RestController +@RequestMapping("/system/tenant-package") +@Validated +public class TenantPackageController { + + @Resource + private TenantPackageService tenantPackageService; + + /** + * 创建租户套餐 + */ + @PostMapping("/create") + @PreAuthorize("@ss.hasPermission('system:tenant-package:create')") + public CommonResult createTenantPackage( + @Valid @RequestBody TenantPackageSaveReqVO createReqVO) { + return success(tenantPackageService.createTenantPackage(createReqVO)); + } + + /** + * 更新租户套餐 + */ + @PutMapping("/update") + @PreAuthorize("@ss.hasPermission('system:tenant-package:update')") + public CommonResult updateTenantPackage( + @Valid @RequestBody TenantPackageSaveReqVO updateReqVO) { + tenantPackageService.updateTenantPackage(updateReqVO); + return success(true); + } + + /** + * 删除租户套餐 + */ + @DeleteMapping("/delete") + @PreAuthorize("@ss.hasPermission('system:tenant-package:delete')") + public CommonResult deleteTenantPackage(@RequestParam("id") Long id) { + tenantPackageService.deleteTenantPackage(id); + return success(true); + } + + /** + * 获得租户套餐 + */ + @GetMapping("/get") + @PreAuthorize("@ss.hasPermission('system:tenant-package:query')") + public CommonResult getTenantPackage(@RequestParam("id") Long id) { + TenantPackageDO tenantPackage = tenantPackageService.getTenantPackage(id); + return success(BeanUtils.toBean(tenantPackage, TenantPackageRespVO.class)); + } + + /** + * 获得租户套餐分页 + */ + @GetMapping("/page") + @PreAuthorize("@ss.hasPermission('system:tenant-package:query')") + public CommonResult> getTenantPackagePage( + @Valid TenantPackagePageReqVO pageVO) { + PageResult pageResult = tenantPackageService.getTenantPackagePage(pageVO); + return success(BeanUtils.toBean(pageResult, TenantPackageRespVO.class)); + } + + /** + * 获取租户套餐精简信息列表", description = "只包含被开启的租户套餐,主要用于前端的下拉选项 + */ + @GetMapping({"/get-simple-list", "simple-list"}) + public CommonResult> getTenantPackageList() { + List list = + tenantPackageService.getTenantPackageListByStatus(CommonStatusEnum.ENABLE.getStatus()); + return success(BeanUtils.toBean(list, TenantPackageSimpleRespVO.class)); + } +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/tenant/vo/packages/TenantPackagePageReqVO.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/tenant/vo/packages/TenantPackagePageReqVO.java new file mode 100644 index 0000000..7351f7f --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/tenant/vo/packages/TenantPackagePageReqVO.java @@ -0,0 +1,39 @@ +package com.tashow.cloud.system.controller.admin.tenant.vo.packages; + +import com.tashow.cloud.common.pojo.PageParam; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; +import org.springframework.format.annotation.DateTimeFormat; + +import java.time.LocalDateTime; + +import static com.tashow.cloud.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND; + +/** + * 管理后台 - 租户套餐分页 Request VO + */ +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class TenantPackagePageReqVO extends PageParam { + + /** + * 套餐名 + */ + private String name; + + /** + * 状态 + */ + private Integer status; + + /** + * 备注 + */ + private String remark; + + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) + /** 创建时间 */ + private LocalDateTime[] createTime; +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/tenant/vo/packages/TenantPackageRespVO.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/tenant/vo/packages/TenantPackageRespVO.java new file mode 100644 index 0000000..9712ef3 --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/tenant/vo/packages/TenantPackageRespVO.java @@ -0,0 +1,43 @@ +package com.tashow.cloud.system.controller.admin.tenant.vo.packages; + +import lombok.Data; + +import java.time.LocalDateTime; +import java.util.Set; + +/** + * 管理后台 - 租户套餐 Response VO + */ +@Data +public class TenantPackageRespVO { + + /** + * 套餐编号" + */ + private Long id; + + /** + * 套餐名" + */ + private String name; + + /** + * 状态,参见 CommonStatusEnum 枚举" + */ + private Integer status; + + /** + * 备注 + */ + private String remark; + + /** + * 关联的菜单编号 + */ + private Set menuIds; + + /** + * 创建时间 + */ + private LocalDateTime createTime; +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/tenant/vo/packages/TenantPackageSaveReqVO.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/tenant/vo/packages/TenantPackageSaveReqVO.java new file mode 100644 index 0000000..4f10db8 --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/tenant/vo/packages/TenantPackageSaveReqVO.java @@ -0,0 +1,45 @@ +package com.tashow.cloud.system.controller.admin.tenant.vo.packages; + +import com.tashow.cloud.common.enums.CommonStatusEnum; +import com.tashow.cloud.common.validation.InEnum; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +import java.util.Set; + +/** + * 管理后台 - 租户套餐创建/修改 Request VO + */ +@Data +public class TenantPackageSaveReqVO { + + /** + * 套餐编号 + */ + private Long id; + + /** + * 套餐名" + */ + @NotEmpty(message = "套餐名不能为空") + private String name; + + /** + * 状态,参见 CommonStatusEnum 枚举" + */ + @NotNull(message = "状态不能为空") + @InEnum(value = CommonStatusEnum.class, message = "状态必须是 {value}") + private Integer status; + + /** + * 备注 + */ + private String remark; + + /** + * 关联的菜单编号 + */ + @NotNull(message = "关联的菜单编号不能为空") + private Set menuIds; +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/tenant/vo/packages/TenantPackageSimpleRespVO.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/tenant/vo/packages/TenantPackageSimpleRespVO.java new file mode 100644 index 0000000..39914f2 --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/tenant/vo/packages/TenantPackageSimpleRespVO.java @@ -0,0 +1,23 @@ +package com.tashow.cloud.system.controller.admin.tenant.vo.packages; + +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +/** + * 管理后台 - 租户套餐精简 Response VO + */ +@Data +public class TenantPackageSimpleRespVO { + + /** + * 套餐编号" + */ + @NotNull(message = "套餐编号不能为空") + private Long id; + + /** + * 套餐名" + */ + @NotNull(message = "套餐名不能为空") + private String name; +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/tenant/vo/tenant/TenantPageReqVO.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/tenant/vo/tenant/TenantPageReqVO.java new file mode 100644 index 0000000..b7fe41f --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/tenant/vo/tenant/TenantPageReqVO.java @@ -0,0 +1,33 @@ +package com.tashow.cloud.system.controller.admin.tenant.vo.tenant; + +import static com.tashow.cloud.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND; + +import com.tashow.cloud.common.pojo.PageParam; +import java.time.LocalDateTime; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; +import org.springframework.format.annotation.DateTimeFormat; + +/** 管理后台 - 租户分页 Request VO */ +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class TenantPageReqVO extends PageParam { + + /** 租户名 */ + private String name; + + /** 联系人 */ + private String contactName; + + /** 联系手机 */ + private String contactMobile; + + /** 租户状态(0正常 1停用) */ + private Integer status; + + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) + /** 创建时间 */ + private LocalDateTime[] createTime; +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/tenant/vo/tenant/TenantRespVO.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/tenant/vo/tenant/TenantRespVO.java new file mode 100644 index 0000000..5682b10 --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/tenant/vo/tenant/TenantRespVO.java @@ -0,0 +1,56 @@ +package com.tashow.cloud.system.controller.admin.tenant.vo.tenant; + +import com.alibaba.excel.annotation.ExcelIgnoreUnannotated; +import com.alibaba.excel.annotation.ExcelProperty; +import com.tashow.cloud.excel.excel.core.annotations.DictFormat; +import com.tashow.cloud.excel.excel.core.convert.DictConvert; +import com.tashow.cloud.systemapi.enums.DictTypeConstants; +import java.time.LocalDateTime; +import lombok.Data; + +/** 管理后台 - 租户 Response VO */ +@Data +@ExcelIgnoreUnannotated +public class TenantRespVO { + + /** 租户编号" */ + @ExcelProperty("租户编号") + private Long id; + + /** 租户名", example = "芋道 */ + @ExcelProperty("租户名") + private String name; + + /** 联系人", example = "芋艿 */ + @ExcelProperty("联系人") + private String contactName; + + /** 联系手机 */ + @ExcelProperty("联系手机") + private String contactMobile; + + /** 租户状态" */ + @ExcelProperty(value = "状态", converter = DictConvert.class) + @DictFormat(DictTypeConstants.COMMON_STATUS) + private Integer status; + + /** 绑定域名 */ + private String website; + + /** 租户套餐编号" */ + private Long packageId; + + /** + * 过期时间 + */ + private LocalDateTime expireTime; + + /** 账号数量" */ + private Integer accountCount; + + /** + * 创建时间 + */ + @ExcelProperty("创建时间") + private LocalDateTime createTime; +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/tenant/vo/tenant/TenantSaveReqVO.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/tenant/vo/tenant/TenantSaveReqVO.java new file mode 100644 index 0000000..3f20270 --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/tenant/vo/tenant/TenantSaveReqVO.java @@ -0,0 +1,92 @@ +package com.tashow.cloud.system.controller.admin.tenant.vo.tenant; + +import cn.hutool.core.util.ObjectUtil; +import com.fasterxml.jackson.annotation.JsonIgnore; +import jakarta.validation.constraints.AssertTrue; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Pattern; +import jakarta.validation.constraints.Size; +import lombok.Data; +import org.hibernate.validator.constraints.Length; + +import java.time.LocalDateTime; + +/** + * 管理后台 - 租户创建/修改 Request VO + */ +@Data +public class TenantSaveReqVO { + + /** + * 租户编号 + */ + private Long id; + + /** + * 租户名", example = "芋道 + */ + @NotNull(message = "租户名不能为空") + private String name; + + /** + * 联系人", example = "芋艿 + */ + @NotNull(message = "联系人不能为空") + private String contactName; + + /** + * 联系手机 + */ + private String contactMobile; + + /** + * 租户状态" + */ + @NotNull(message = "租户状态") + private Integer status; + + /** + * 绑定域名 + */ + private String website; + + /** + * 租户套餐编号" + */ + @NotNull(message = "租户套餐编号不能为空") + private Long packageId; + + /** + * 过期时间 + */ + @NotNull(message = "过期时间不能为空") + private LocalDateTime expireTime; + + /** + * 账号数量" + */ + @NotNull(message = "账号数量不能为空") + private Integer accountCount; + + // ========== 仅【创建】时,需要传递的字段 ========== + + /** + * 用户账号" + */ + @Pattern(regexp = "^[a-zA-Z0-9]{4,30}$", message = "用户账号由 数字、字母 组成") + @Size(min = 4, max = 30, message = "用户账号长度为 4-30 个字符") + private String username; + + /** + * 密码" + */ + @Length(min = 4, max = 16, message = "密码长度为 4-16 位") + private String password; + + @AssertTrue(message = "用户账号、密码不能为空") + @JsonIgnore + public boolean isUsernameValid() { + return id != null // 修改时,不需要传递 + || (ObjectUtil.isAllNotEmpty(username, password)); // 新增时,必须都传递 username、password + } +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/tenant/vo/tenant/TenantSimpleRespVO.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/tenant/vo/tenant/TenantSimpleRespVO.java new file mode 100644 index 0000000..95a48a5 --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/tenant/vo/tenant/TenantSimpleRespVO.java @@ -0,0 +1,20 @@ +package com.tashow.cloud.system.controller.admin.tenant.vo.tenant; + +import lombok.Data; + +/** + * 管理后台 - 租户精简 Response VO + */ +@Data +public class TenantSimpleRespVO { + + /** + * 租户编号" + */ + private Long id; + + /** + * 租户名", example = "芋道 + */ + private String name; +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/user/UserController.http b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/user/UserController.http new file mode 100644 index 0000000..90f0c98 --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/user/UserController.http @@ -0,0 +1,5 @@ +### 请求 /system/user/page 接口 => 没有权限 +GET {{baseUrl}}/system/user/page?pageNo=1&pageSize=10 +Authorization: Bearer {{token}} +#Authorization: Bearer test100 +tenant-id: {{adminTenantId}} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/user/UserController.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/user/UserController.java new file mode 100644 index 0000000..0c2bcc4 --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/user/UserController.java @@ -0,0 +1,207 @@ +package com.tashow.cloud.system.controller.admin.user; + +import cn.hutool.core.collection.CollUtil; +import com.tashow.cloud.common.enums.CommonStatusEnum; +import com.tashow.cloud.common.pojo.CommonResult; +import com.tashow.cloud.common.pojo.PageParam; +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.excel.excel.core.util.ExcelUtils; +import com.tashow.cloud.system.controller.admin.user.vo.user.*; +import com.tashow.cloud.system.convert.user.UserConvert; +import com.tashow.cloud.system.dal.dataobject.dept.DeptDO; +import com.tashow.cloud.system.dal.dataobject.user.AdminUserDO; +import com.tashow.cloud.system.service.dept.DeptService; +import com.tashow.cloud.system.service.user.AdminUserService; +import com.tashow.cloud.systemapi.enums.common.SexEnum; +import com.tashow.cloud.web.apilog.core.annotation.ApiAccessLog; +import jakarta.annotation.Resource; +import jakarta.servlet.http.HttpServletResponse; +import jakarta.validation.Valid; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; + +import java.io.IOException; +import java.util.Arrays; +import java.util.List; +import java.util.Map; + +import static com.tashow.cloud.common.pojo.CommonResult.success; +import static com.tashow.cloud.common.util.collection.CollectionUtils.convertList; +import static com.tashow.cloud.web.apilog.core.enums.OperateTypeEnum.EXPORT; + +/** + * 管理后台 - 用户 + */ +@RestController +@RequestMapping("/system/user") +@Validated +public class UserController { + + @Resource + private AdminUserService userService; + @Resource + private DeptService deptService; + + /** + * 新增用户 + */ + @PostMapping("/create") + @PreAuthorize("@ss.hasPermission('system:user:create')") + public CommonResult createUser(@Valid @RequestBody UserSaveReqVO reqVO) { + Long id = userService.createUser(reqVO); + return success(id); + } + + /** + * 修改用户 + */ + @PutMapping("update") + @PreAuthorize("@ss.hasPermission('system:user:update')") + public CommonResult updateUser(@Valid @RequestBody UserSaveReqVO reqVO) { + userService.updateUser(reqVO); + return success(true); + } + + /** + * 删除用户 + */ + @DeleteMapping("/delete") + @PreAuthorize("@ss.hasPermission('system:user:delete')") + public CommonResult deleteUser(@RequestParam("id") Long id) { + userService.deleteUser(id); + return success(true); + } + + /** + * 重置用户密码 + */ + @PutMapping("/update-password") + @PreAuthorize("@ss.hasPermission('system:user:update-password')") + public CommonResult updateUserPassword( + @Valid @RequestBody UserUpdatePasswordReqVO reqVO) { + userService.updateUserPassword(reqVO.getId(), reqVO.getPassword()); + return success(true); + } + + /** + * 修改用户状态 + */ + @PutMapping("/update-status") + @PreAuthorize("@ss.hasPermission('system:user:update')") + public CommonResult updateUserStatus(@Valid @RequestBody UserUpdateStatusReqVO reqVO) { + userService.updateUserStatus(reqVO.getId(), reqVO.getStatus()); + return success(true); + } + + /** + * 获得用户分页列表 + */ + @GetMapping("/page") + @PreAuthorize("@ss.hasPermission('system:user:query')") + public CommonResult> getUserPage(@Valid UserPageReqVO pageReqVO) { + // 获得用户分页列表 + PageResult pageResult = userService.getUserPage(pageReqVO); + if (CollUtil.isEmpty(pageResult.getList())) { + return success(new PageResult<>(pageResult.getTotal())); + } + // 拼接数据 + Map deptMap = + deptService.getDeptMap(convertList(pageResult.getList(), AdminUserDO::getDeptId)); + return success( + new PageResult<>( + UserConvert.INSTANCE.convertList(pageResult.getList(), deptMap), + pageResult.getTotal())); + } + + /** + * 获取用户精简信息列表", description = "只包含被开启的用户,主要用于前端的下拉选项 + */ + @GetMapping({"/list-all-simple", "/simple-list"}) + public CommonResult> getSimpleUserList() { + List list = userService.getUserListByStatus(CommonStatusEnum.ENABLE.getStatus()); + // 拼接数据 + Map deptMap = deptService.getDeptMap(convertList(list, AdminUserDO::getDeptId)); + return success(UserConvert.INSTANCE.convertSimpleList(list, deptMap)); + } + + /** + * 获得用户详情 + */ + @GetMapping("/get") + @PreAuthorize("@ss.hasPermission('system:user:query')") + public CommonResult getUser(@RequestParam("id") Long id) { + AdminUserDO user = userService.getUser(id); + if (user == null) { + return success(null); + } + // 拼接数据 + DeptDO dept = deptService.getDept(user.getDeptId()); + return success(UserConvert.INSTANCE.convert(user, dept)); + } + + /** + * 导出用户 + */ + @GetMapping("/export") + @PreAuthorize("@ss.hasPermission('system:user:export')") + @ApiAccessLog(operateType = EXPORT) + public void exportUserList(@Validated UserPageReqVO exportReqVO, HttpServletResponse response) + throws IOException { + exportReqVO.setPageSize(PageParam.PAGE_SIZE_NONE); + List list = userService.getUserPage(exportReqVO).getList(); + // 输出 Excel + Map deptMap = deptService.getDeptMap(convertList(list, AdminUserDO::getDeptId)); + ExcelUtils.write( + response, + "用户数据.xls", + "数据", + UserRespVO.class, + UserConvert.INSTANCE.convertList(list, deptMap)); + } + + /** + * 获得导入用户模板 + */ + @GetMapping("/get-import-template") + public void importTemplate(HttpServletResponse response) throws IOException { + // 手动创建导出 demo + List list = + Arrays.asList( + UserImportExcelVO.builder() + .username("yunai") + .deptId(1L) + .email("yunai@iocoder.cn") + .mobile("15601691300") + .nickname("芋道") + .status(CommonStatusEnum.ENABLE.getStatus()) + .sex(SexEnum.MALE.getSex()) + .build(), + UserImportExcelVO.builder() + .username("yuanma") + .deptId(2L) + .email("yuanma@iocoder.cn") + .mobile("15601701300") + .nickname("源码") + .status(CommonStatusEnum.DISABLE.getStatus()) + .sex(SexEnum.FEMALE.getSex()) + .build()); + // 输出 + ExcelUtils.write(response, "用户导入模板.xls", "用户列表", UserImportExcelVO.class, list); + } + + /** + * 导入用户 + */ + @PostMapping("/import") + @PreAuthorize("@ss.hasPermission('system:user:import')") + public CommonResult importExcel( + @RequestParam("file") MultipartFile file, + @RequestParam(value = "updateSupport", required = false, defaultValue = "false") + Boolean updateSupport) + throws Exception { + List list = ExcelUtils.read(file, UserImportExcelVO.class); + return success(userService.importUserList(list, updateSupport)); + } +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/user/UserProfileController.http b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/user/UserProfileController.http new file mode 100644 index 0000000..c94c2ad --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/user/UserProfileController.http @@ -0,0 +1,4 @@ +### 请求 /system/user/profile/get 接口 => 没有权限 +GET {{baseUrl}}/system/user/profile/get +Authorization: Bearer {{token}} +tenant-id: {{adminTenantId}} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/user/UserProfileController.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/user/UserProfileController.java new file mode 100644 index 0000000..2594984 --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/user/UserProfileController.java @@ -0,0 +1,113 @@ +package com.tashow.cloud.system.controller.admin.user; + +import cn.hutool.core.collection.CollUtil; +import com.tashow.cloud.common.enums.UserTypeEnum; +import com.tashow.cloud.common.pojo.CommonResult; +import com.tashow.cloud.permission.core.annotation.DataPermission; +import com.tashow.cloud.system.controller.admin.user.vo.profile.UserProfileRespVO; +import com.tashow.cloud.system.controller.admin.user.vo.profile.UserProfileUpdatePasswordReqVO; +import com.tashow.cloud.system.controller.admin.user.vo.profile.UserProfileUpdateReqVO; +import com.tashow.cloud.system.convert.user.UserConvert; +import com.tashow.cloud.system.dal.dataobject.dept.DeptDO; +import com.tashow.cloud.system.dal.dataobject.dept.PostDO; +import com.tashow.cloud.system.dal.dataobject.permission.RoleDO; +import com.tashow.cloud.system.dal.dataobject.social.SocialUserDO; +import com.tashow.cloud.system.dal.dataobject.user.AdminUserDO; +import com.tashow.cloud.system.service.dept.DeptService; +import com.tashow.cloud.system.service.dept.PostService; +import com.tashow.cloud.system.service.permission.PermissionService; +import com.tashow.cloud.system.service.permission.RoleService; +import com.tashow.cloud.system.service.social.SocialUserService; +import com.tashow.cloud.system.service.user.AdminUserService; +import jakarta.annotation.Resource; +import jakarta.validation.Valid; +import lombok.extern.slf4j.Slf4j; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; + +import java.util.List; + +import static com.tashow.cloud.common.exception.util.ServiceExceptionUtil.exception; +import static com.tashow.cloud.common.pojo.CommonResult.success; +import static com.tashow.cloud.infraapi.enums.ErrorCodeConstants.FILE_IS_EMPTY; +import static com.tashow.cloud.web.web.core.util.WebFrameworkUtils.getLoginUserId; + +/** + * 管理后台 - 用户个人中心 + */ +@RestController +@RequestMapping("/system/user/profile") +@Validated +@Slf4j +public class UserProfileController { + + @Resource + private AdminUserService userService; + @Resource + private DeptService deptService; + @Resource + private PostService postService; + @Resource + private PermissionService permissionService; + @Resource + private RoleService roleService; + @Resource + private SocialUserService socialService; + + /** + * 获得登录用户信息 + */ + @GetMapping("/get") + @DataPermission(enable = false) // 关闭数据权限,避免只查看自己时,查询不到部门。 + public CommonResult getUserProfile() { + // 获得用户基本信息 + AdminUserDO user = userService.getUser(getLoginUserId()); + // 获得用户角色 + List userRoles = + roleService.getRoleListFromCache(permissionService.getUserRoleIdListByUserId(user.getId())); + // 获得部门信息 + DeptDO dept = user.getDeptId() != null ? deptService.getDept(user.getDeptId()) : null; + // 获得岗位信息 + List posts = + CollUtil.isNotEmpty(user.getPostIds()) ? postService.getPostList(user.getPostIds()) : null; + // 获得社交用户信息 + List socialUsers = + socialService.getSocialUserList(user.getId(), UserTypeEnum.ADMIN.getValue()); + return success(UserConvert.INSTANCE.convert(user, userRoles, dept, posts, socialUsers)); + } + + /** + * 修改用户个人信息 + */ + @PutMapping("/update") + public CommonResult updateUserProfile(@Valid @RequestBody UserProfileUpdateReqVO reqVO) { + userService.updateUserProfile(getLoginUserId(), reqVO); + return success(true); + } + + /** + * 修改用户个人密码 + */ + @PutMapping("/update-password") + public CommonResult updateUserProfilePassword( + @Valid @RequestBody UserProfileUpdatePasswordReqVO reqVO) { + userService.updateUserPassword(getLoginUserId(), reqVO); + return success(true); + } + + /** + * 上传用户个人头像 + */ + @RequestMapping( + value = "/update-avatar", + method = {RequestMethod.POST, RequestMethod.PUT}) // 解决 uni-app 不支持 Put 上传文件的问题 + public CommonResult updateUserAvatar(@RequestParam("avatarFile") MultipartFile file) + throws Exception { + if (file.isEmpty()) { + throw exception(FILE_IS_EMPTY); + } + String avatar = userService.updateUserAvatar(getLoginUserId(), file.getInputStream()); + return success(avatar); + } +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/user/vo/profile/UserProfileRespVO.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/user/vo/profile/UserProfileRespVO.java new file mode 100644 index 0000000..9b1b528 --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/user/vo/profile/UserProfileRespVO.java @@ -0,0 +1,101 @@ +package com.tashow.cloud.system.controller.admin.user.vo.profile; + +import com.tashow.cloud.system.controller.admin.dept.vo.dept.DeptSimpleRespVO; +import com.tashow.cloud.system.controller.admin.dept.vo.post.PostSimpleRespVO; +import com.tashow.cloud.system.controller.admin.permission.vo.role.RoleSimpleRespVO; +import lombok.Data; + +import java.time.LocalDateTime; +import java.util.List; + +@Data +/** 管理后台 - 用户个人中心信息 Response VO */ +public class UserProfileRespVO { + + /** + * 用户编号" + */ + private Long id; + + /** + * 用户账号" + */ + private String username; + + /** + * 用户昵称", example = "芋艿 + */ + private String nickname; + + /** + * 用户邮箱 + */ + private String email; + + /** + * 手机号码 + */ + private String mobile; + + /** + * 用户性别,参见 SexEnum 枚举类 + */ + private Integer sex; + + /** + * 用户头像 + */ + private String avatar; + + /** + * 最后登录 IP" + */ + private String loginIp; + + /** + * 最后登录时间", example = "时间戳格式 + */ + private LocalDateTime loginDate; + + /** + * 创建时间", example = "时间戳格式 + */ + private LocalDateTime createTime; + + /** + * 所属角色 + */ + private List roles; + + /** + * 所在部门 + */ + private DeptSimpleRespVO dept; + + /** + * 所属岗位数组 + */ + private List posts; + + /** + * 社交用户数组 + */ + private List socialUsers; + + /** + * 社交用户 + */ + @Data + public static class SocialUser { + + /** + * 社交平台的类型,参见 SocialTypeEnum 枚举类" + */ + private Integer type; + + /** + * 社交用户的 openid" + */ + private String openid; + } +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/user/vo/profile/UserProfileUpdatePasswordReqVO.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/user/vo/profile/UserProfileUpdatePasswordReqVO.java new file mode 100644 index 0000000..2e65fdc --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/user/vo/profile/UserProfileUpdatePasswordReqVO.java @@ -0,0 +1,26 @@ +package com.tashow.cloud.system.controller.admin.user.vo.profile; + +import jakarta.validation.constraints.NotEmpty; +import lombok.Data; +import org.hibernate.validator.constraints.Length; + +/** + * 管理后台 - 用户个人中心更新密码 Request VO + */ +@Data +public class UserProfileUpdatePasswordReqVO { + + /** + * 旧密码" + */ + @NotEmpty(message = "旧密码不能为空") + @Length(min = 4, max = 16, message = "密码长度为 4-16 位") + private String oldPassword; + + /** + * 新密码" + */ + @NotEmpty(message = "新密码不能为空") + @Length(min = 4, max = 16, message = "密码长度为 4-16 位") + private String newPassword; +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/user/vo/profile/UserProfileUpdateReqVO.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/user/vo/profile/UserProfileUpdateReqVO.java new file mode 100644 index 0000000..d6644b4 --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/user/vo/profile/UserProfileUpdateReqVO.java @@ -0,0 +1,37 @@ +package com.tashow.cloud.system.controller.admin.user.vo.profile; + +import jakarta.validation.constraints.Email; +import jakarta.validation.constraints.Size; +import lombok.Data; +import org.hibernate.validator.constraints.Length; + +/** + * 管理后台 - 用户个人信息更新 Request VO + */ +@Data +public class UserProfileUpdateReqVO { + + /** + * 用户昵称", example = "芋艿 + */ + @Size(max = 30, message = "用户昵称长度不能超过 30 个字符") + private String nickname; + + /** + * 用户邮箱 + */ + @Email(message = "邮箱格式不正确") + @Size(max = 50, message = "邮箱长度不能超过 50 个字符") + private String email; + + /** + * 手机号码 + */ + @Length(min = 11, max = 11, message = "手机号长度必须 11 位") + private String mobile; + + /** + * 用户性别,参见 SexEnum 枚举类 + */ + private Integer sex; +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/user/vo/user/UserImportExcelVO.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/user/vo/user/UserImportExcelVO.java new file mode 100644 index 0000000..2b51263 --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/user/vo/user/UserImportExcelVO.java @@ -0,0 +1,45 @@ +package com.tashow.cloud.system.controller.admin.user.vo.user; + +import com.alibaba.excel.annotation.ExcelProperty; +import com.tashow.cloud.excel.excel.core.annotations.DictFormat; +import com.tashow.cloud.excel.excel.core.convert.DictConvert; +import com.tashow.cloud.systemapi.enums.DictTypeConstants; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.Accessors; + +/** + * 用户 Excel 导入 VO + */ +@Data +@Builder +@AllArgsConstructor +@NoArgsConstructor +@Accessors(chain = false) // 设置 chain = false,避免用户导入有问题 +public class UserImportExcelVO { + + @ExcelProperty("登录名称") + private String username; + + @ExcelProperty("用户名称") + private String nickname; + + @ExcelProperty("部门编号") + private Long deptId; + + @ExcelProperty("用户邮箱") + private String email; + + @ExcelProperty("手机号码") + private String mobile; + + @ExcelProperty(value = "用户性别", converter = DictConvert.class) + @DictFormat(DictTypeConstants.USER_SEX) + private Integer sex; + + @ExcelProperty(value = "账号状态", converter = DictConvert.class) + @DictFormat(DictTypeConstants.COMMON_STATUS) + private Integer status; +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/user/vo/user/UserImportRespVO.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/user/vo/user/UserImportRespVO.java new file mode 100644 index 0000000..bf8aacb --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/user/vo/user/UserImportRespVO.java @@ -0,0 +1,30 @@ +package com.tashow.cloud.system.controller.admin.user.vo.user; + +import lombok.Builder; +import lombok.Data; + +import java.util.List; +import java.util.Map; + +/** + * 管理后台 - 用户导入 Response VO + */ +@Data +@Builder +public class UserImportRespVO { + + /** + * 创建成功的用户名数组 + */ + private List createUsernames; + + /** + * 更新成功的用户名数组 + */ + private List updateUsernames; + + /** + * 导入失败的用户集合,key 为用户名,value 为失败原因 + */ + private Map failureUsernames; +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/user/vo/user/UserPageReqVO.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/user/vo/user/UserPageReqVO.java new file mode 100644 index 0000000..2ff978a --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/user/vo/user/UserPageReqVO.java @@ -0,0 +1,38 @@ +package com.tashow.cloud.system.controller.admin.user.vo.user; + +import static com.tashow.cloud.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND; + +import com.tashow.cloud.common.pojo.PageParam; +import java.time.LocalDateTime; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import org.springframework.format.annotation.DateTimeFormat; + +/** 管理后台 - 用户分页 Request VO */ +@Data +@NoArgsConstructor +@AllArgsConstructor +@EqualsAndHashCode(callSuper = true) +public class UserPageReqVO extends PageParam { + + /** 用户账号,模糊匹配 */ + private String username; + + /** 手机号码,模糊匹配 */ + private String mobile; + + /** 展示状态,参见 CommonStatusEnum 枚举类 */ + private Integer status; + + /** 创建时间 */ + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) + private LocalDateTime[] createTime; + + /** 部门编号,同时筛选子部门 */ + private Long deptId; + + /** 角色编号 */ + private Long roleId; +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/user/vo/user/UserRespVO.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/user/vo/user/UserRespVO.java new file mode 100644 index 0000000..56ddde9 --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/user/vo/user/UserRespVO.java @@ -0,0 +1,106 @@ +package com.tashow.cloud.system.controller.admin.user.vo.user; + +import com.alibaba.excel.annotation.ExcelIgnoreUnannotated; +import com.alibaba.excel.annotation.ExcelProperty; +import com.tashow.cloud.excel.excel.core.annotations.DictFormat; +import com.tashow.cloud.excel.excel.core.convert.DictConvert; +import com.tashow.cloud.systemapi.enums.DictTypeConstants; +import lombok.Data; + +import java.time.LocalDateTime; +import java.util.Set; + +/** + * 管理后台 - 用户信息 Response VO + */ +@Data +@ExcelIgnoreUnannotated +public class UserRespVO { + + /** + * 用户编号" + */ + @ExcelProperty("用户编号") + private Long id; + + /** + * 用户账号" + */ + @ExcelProperty("用户名称") + private String username; + + /** + * 用户昵称", example = "芋艿 + */ + @ExcelProperty("用户昵称") + private String nickname; + + /** + * 备注 + */ + private String remark; + + /** + * 部门ID + */ + private Long deptId; + + /** + * 部门名称 + */ + @ExcelProperty("部门名称") + private String deptName; + + /** + * 岗位编号数组 + */ + private Set postIds; + + /** + * 用户邮箱 + */ + @ExcelProperty("用户邮箱") + private String email; + + /** + * 手机号码 + */ + @ExcelProperty("手机号码") + private String mobile; + + /** + * 用户性别,参见 SexEnum 枚举类 + */ + @ExcelProperty(value = "用户性别", converter = DictConvert.class) + @DictFormat(DictTypeConstants.USER_SEX) + private Integer sex; + + /** + * 用户头像 + */ + private String avatar; + + /** + * 状态,参见 CommonStatusEnum 枚举类" + */ + @ExcelProperty(value = "帐号状态", converter = DictConvert.class) + @DictFormat(DictTypeConstants.COMMON_STATUS) + private Integer status; + + /** + * 最后登录 IP" + */ + @ExcelProperty("最后登录IP") + private String loginIp; + + /** + * 最后登录时间", example = "时间戳格式 + */ + @ExcelProperty("最后登录时间") + private LocalDateTime loginDate; + + /** + * 创建时间", example = "时间戳格式 + */ + private LocalDateTime createTime; +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/user/vo/user/UserSaveReqVO.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/user/vo/user/UserSaveReqVO.java new file mode 100644 index 0000000..6e8f2af --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/user/vo/user/UserSaveReqVO.java @@ -0,0 +1,102 @@ +package com.tashow.cloud.system.controller.admin.user.vo.user; + +import cn.hutool.core.util.ObjectUtil; +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.mzt.logapi.starter.annotation.DiffLogField; +import com.tashow.cloud.common.validation.Mobile; +import com.tashow.cloud.system.framework.operatelog.core.DeptParseFunction; +import com.tashow.cloud.system.framework.operatelog.core.PostParseFunction; +import com.tashow.cloud.system.framework.operatelog.core.SexParseFunction; +import jakarta.validation.constraints.*; +import lombok.Data; +import org.hibernate.validator.constraints.Length; + +import java.util.Set; + +/** + * 管理后台 - 用户创建/修改 Request VO + */ +@Data +public class UserSaveReqVO { + + /** + * 用户编号 + */ + private Long id; + + /** + * 用户账号" + */ + @NotBlank(message = "用户账号不能为空") + @Pattern(regexp = "^[a-zA-Z0-9]+$", message = "用户账号由 数字、字母 组成") + @Size(min = 4, max = 30, message = "用户账号长度为 4-30 个字符") + @DiffLogField(name = "用户账号") + private String username; + + /** + * 用户昵称", example = "芋艿 + */ + @Size(max = 30, message = "用户昵称长度不能超过30个字符") + @DiffLogField(name = "用户昵称") + private String nickname; + + /** + * 备注 + */ + @DiffLogField(name = "备注") + private String remark; + + /** + * 部门编号 + */ + @DiffLogField(name = "部门", function = DeptParseFunction.NAME) + private Long deptId; + + /** + * 岗位编号数组 + */ + @DiffLogField(name = "岗位", function = PostParseFunction.NAME) + private Set postIds; + + /** + * 用户邮箱 + */ + @Email(message = "邮箱格式不正确") + @Size(max = 50, message = "邮箱长度不能超过 50 个字符") + @DiffLogField(name = "用户邮箱") + private String email; + + /** + * 手机号码 + */ + @Mobile + @DiffLogField(name = "手机号码") + private String mobile; + + /** + * 用户性别,参见 SexEnum 枚举类 + */ + @DiffLogField(name = "用户性别", function = SexParseFunction.NAME) + private Integer sex; + + /** + * 用户头像 + */ + @DiffLogField(name = "用户头像") + private String avatar; + + // ========== 仅【创建】时,需要传递的字段 ========== + + /** + * 密码" + */ + @Length(min = 4, max = 16, message = "密码长度为 4-16 位") + private String password; + + @AssertTrue(message = "密码不能为空") + @JsonIgnore + public boolean isPasswordValid() { + return id != null // 修改时,不需要传递 + || (ObjectUtil.isAllNotEmpty(password)); // 新增时,必须都传递 password + } +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/user/vo/user/UserSimpleRespVO.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/user/vo/user/UserSimpleRespVO.java new file mode 100644 index 0000000..74362ce --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/user/vo/user/UserSimpleRespVO.java @@ -0,0 +1,34 @@ +package com.tashow.cloud.system.controller.admin.user.vo.user; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 管理后台 - 用户精简信息 Response VO + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +public class UserSimpleRespVO { + + /** + * 用户编号" + */ + private Long id; + + /** + * 用户昵称", example = "芋道 + */ + private String nickname; + + /** + * 部门ID + */ + private Long deptId; + + /** + * 部门名称 + */ + private String deptName; +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/user/vo/user/UserUpdatePasswordReqVO.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/user/vo/user/UserUpdatePasswordReqVO.java new file mode 100644 index 0000000..7559c71 --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/user/vo/user/UserUpdatePasswordReqVO.java @@ -0,0 +1,20 @@ +package com.tashow.cloud.system.controller.admin.user.vo.user; + +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import lombok.Data; +import org.hibernate.validator.constraints.Length; + +/** 管理后台 - 用户更新密码 Request VO */ +@Data +public class UserUpdatePasswordReqVO { + + /** 用户编号" */ + @NotNull(message = "用户编号不能为空") + private Long id; + + /** 密码" */ + @NotEmpty(message = "密码不能为空") + @Length(min = 4, max = 16, message = "密码长度为 4-16 位") + private String password; +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/user/vo/user/UserUpdateStatusReqVO.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/user/vo/user/UserUpdateStatusReqVO.java new file mode 100644 index 0000000..e9741d8 --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/user/vo/user/UserUpdateStatusReqVO.java @@ -0,0 +1,26 @@ +package com.tashow.cloud.system.controller.admin.user.vo.user; + +import com.tashow.cloud.common.enums.CommonStatusEnum; +import com.tashow.cloud.common.validation.InEnum; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +/** + * 管理后台 - 用户更新状态 Request VO + */ +@Data +public class UserUpdateStatusReqVO { + + /** + * 用户编号" + */ + @NotNull(message = "角色编号不能为空") + private Long id; + + /** + * 状态,见 CommonStatusEnum 枚举" + */ + @NotNull(message = "状态不能为空") + @InEnum(value = CommonStatusEnum.class, message = "修改状态必须是 {value}") + private Integer status; +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/app/dict/AppDictDataController.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/app/dict/AppDictDataController.java new file mode 100644 index 0000000..fb3a455 --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/app/dict/AppDictDataController.java @@ -0,0 +1,37 @@ +package com.tashow.cloud.system.controller.app.dict; + +import static com.tashow.cloud.common.pojo.CommonResult.success; + +import com.tashow.cloud.common.enums.CommonStatusEnum; +import com.tashow.cloud.common.pojo.CommonResult; +import com.tashow.cloud.common.util.object.BeanUtils; +import com.tashow.cloud.system.controller.app.dict.vo.AppDictDataRespVO; +import com.tashow.cloud.system.dal.dataobject.dict.DictDataDO; +import com.tashow.cloud.system.service.dict.DictDataService; +import jakarta.annotation.Resource; +import jakarta.annotation.security.PermitAll; +import java.util.List; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +/** 用户 App - 字典数据 */ +@RestController +@RequestMapping("/system/dict-data") +@Validated +public class AppDictDataController { + + @Resource private DictDataService dictDataService; + + /** 根据字典类型查询字典数据信息 */ + @GetMapping("/type") + @PermitAll + public CommonResult> getDictDataListByType( + @RequestParam("type") String type) { + List list = + dictDataService.getDictDataList(CommonStatusEnum.ENABLE.getStatus(), type); + return success(BeanUtils.toBean(list, AppDictDataRespVO.class)); + } +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/app/dict/vo/AppDictDataRespVO.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/app/dict/vo/AppDictDataRespVO.java new file mode 100644 index 0000000..9e8725a --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/app/dict/vo/AppDictDataRespVO.java @@ -0,0 +1,24 @@ +package com.tashow.cloud.system.controller.app.dict.vo; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** 用户 App - 字典数据信息 Response VO */ +@Data +@NoArgsConstructor +@AllArgsConstructor +public class AppDictDataRespVO { + + /** 字典数据编号" */ + private Long id; + + /** 字典标签", example = "芋道 */ + private String label; + + /** 字典值" */ + private String value; + + /** 字典类型" */ + private String dictType; +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/app/ip/AppAreaController.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/app/ip/AppAreaController.java new file mode 100644 index 0000000..53a5060 --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/app/ip/AppAreaController.java @@ -0,0 +1,32 @@ +package com.tashow.cloud.system.controller.app.ip; + +import static com.tashow.cloud.common.pojo.CommonResult.success; + +import cn.hutool.core.lang.Assert; +import com.tashow.cloud.common.core.Area; +import com.tashow.cloud.common.pojo.CommonResult; +import com.tashow.cloud.common.util.ip.AreaUtils; +import com.tashow.cloud.common.util.object.BeanUtils; +import com.tashow.cloud.system.controller.app.ip.vo.AppAreaNodeRespVO; +import jakarta.annotation.security.PermitAll; +import java.util.List; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +/** 用户 App - 地区 */ +@RestController +@RequestMapping("/system/area") +@Validated +public class AppAreaController { + + /** 获得地区树 */ + @GetMapping("/tree") + @PermitAll + public CommonResult> getAreaTree() { + Area area = AreaUtils.getArea(Area.ID_CHINA); + Assert.notNull(area, "获取不到中国"); + return success(BeanUtils.toBean(area.getChildren(), AppAreaNodeRespVO.class)); + } +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/app/ip/vo/AppAreaNodeRespVO.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/app/ip/vo/AppAreaNodeRespVO.java new file mode 100644 index 0000000..2a185ed --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/app/ip/vo/AppAreaNodeRespVO.java @@ -0,0 +1,18 @@ +package com.tashow.cloud.system.controller.app.ip.vo; + +import java.util.List; +import lombok.Data; + +/** 用户 App - 地区节点 Response VO */ +@Data +public class AppAreaNodeRespVO { + + /** 编号" */ + private Integer id; + + /** 名字", example = "北京 */ + private String name; + + /** 子节点 */ + private List children; +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/app/package-info.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/app/package-info.java new file mode 100644 index 0000000..af5af4f --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/app/package-info.java @@ -0,0 +1,4 @@ +/** + * 占位,避免 package 无法提交到 Git 仓库 + */ +package com.tashow.cloud.system.controller.app; diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/package-info.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/package-info.java new file mode 100644 index 0000000..8886d60 --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/package-info.java @@ -0,0 +1,6 @@ +/** + * 提供 RESTful API 给前端: + * 1. admin 包:提供给管理后台 yudao-ui-admin 前端项目 + * 2. app 包:提供给用户 APP yudao-ui-app 前端项目,它的 Controller 和 VO 都要添加 App 前缀,用于和管理后台进行区分 + */ +package com.tashow.cloud.system.controller; diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/convert/auth/AuthConvert.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/convert/auth/AuthConvert.java new file mode 100644 index 0000000..db5b428 --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/convert/auth/AuthConvert.java @@ -0,0 +1,88 @@ +package com.tashow.cloud.system.convert.auth; + +import cn.hutool.core.collection.CollUtil; +import com.tashow.cloud.common.util.object.BeanUtils; +import com.tashow.cloud.systemapi.api.sms.dto.code.SmsCodeSendReqDTO; +import com.tashow.cloud.systemapi.api.sms.dto.code.SmsCodeUseReqDTO; +import com.tashow.cloud.systemapi.api.social.dto.SocialUserBindReqDTO; +import com.tashow.cloud.system.dal.dataobject.oauth2.OAuth2AccessTokenDO; +import com.tashow.cloud.system.dal.dataobject.permission.MenuDO; +import com.tashow.cloud.system.dal.dataobject.permission.RoleDO; +import com.tashow.cloud.system.dal.dataobject.user.AdminUserDO; +import com.tashow.cloud.systemapi.enums.permission.MenuTypeEnum; +import com.tashow.cloud.system.controller.admin.auth.vo.*; +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; +import org.slf4j.LoggerFactory; + +import java.util.*; + +import static com.tashow.cloud.common.util.collection.CollectionUtils.convertSet; +import static com.tashow.cloud.common.util.collection.CollectionUtils.filterList; +import static com.tashow.cloud.system.dal.dataobject.permission.MenuDO.ID_ROOT; + +@Mapper +public interface AuthConvert { + + AuthConvert INSTANCE = Mappers.getMapper(AuthConvert.class); + + AuthLoginRespVO convert(OAuth2AccessTokenDO bean); + + default AuthPermissionInfoRespVO convert(AdminUserDO user, List roleList, List menuList) { + return AuthPermissionInfoRespVO.builder() + .user(BeanUtils.toBean(user, AuthPermissionInfoRespVO.UserVO.class)) + .roles(convertSet(roleList, RoleDO::getCode)) + // 权限标识信息 + .permissions(convertSet(menuList, MenuDO::getPermission)) + // 菜单树 + .menus(buildMenuTree(menuList)) + .build(); + } + + AuthPermissionInfoRespVO.MenuVO convertTreeNode(MenuDO menu); + + /** + * 将菜单列表,构建成菜单树 + * + * @param menuList 菜单列表 + * @return 菜单树 + */ + default List buildMenuTree(List menuList) { + if (CollUtil.isEmpty(menuList)) { + return Collections.emptyList(); + } + // 移除按钮 + menuList.removeIf(menu -> menu.getType().equals(MenuTypeEnum.BUTTON.getType())); + // 排序,保证菜单的有序性 + menuList.sort(Comparator.comparing(MenuDO::getSort)); + + // 构建菜单树 + // 使用 LinkedHashMap 的原因,是为了排序 。实际也可以用 Stream API ,就是太丑了。 + Map treeNodeMap = new LinkedHashMap<>(); + menuList.forEach(menu -> treeNodeMap.put(menu.getId(), AuthConvert.INSTANCE.convertTreeNode(menu))); + // 处理父子关系 + treeNodeMap.values().stream().filter(node -> !node.getParentId().equals(ID_ROOT)).forEach(childNode -> { + // 获得父节点 + AuthPermissionInfoRespVO.MenuVO parentNode = treeNodeMap.get(childNode.getParentId()); + if (parentNode == null) { + LoggerFactory.getLogger(getClass()).error("[buildRouterTree][resource({}) 找不到父资源({})]", + childNode.getId(), childNode.getParentId()); + return; + } + // 将自己添加到父节点中 + if (parentNode.getChildren() == null) { + parentNode.setChildren(new ArrayList<>()); + } + parentNode.getChildren().add(childNode); + }); + // 获得到所有的根节点 + return filterList(treeNodeMap.values(), node -> ID_ROOT.equals(node.getParentId())); + } + + SocialUserBindReqDTO convert(Long userId, Integer userType, AuthSocialLoginReqVO reqVO); + + SmsCodeSendReqDTO convert(AuthSmsSendReqVO reqVO); + + SmsCodeUseReqDTO convert(AuthSmsLoginReqVO reqVO, Integer scene, String usedIp); + +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/convert/oauth2/OAuth2OpenConvert.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/convert/oauth2/OAuth2OpenConvert.java new file mode 100644 index 0000000..59d7b9a --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/convert/oauth2/OAuth2OpenConvert.java @@ -0,0 +1,56 @@ +package com.tashow.cloud.system.convert.oauth2; + +import cn.hutool.core.date.LocalDateTimeUtil; +import com.tashow.cloud.common.core.KeyValue; +import com.tashow.cloud.common.enums.UserTypeEnum; +import com.tashow.cloud.common.util.collection.CollectionUtils; +import com.tashow.cloud.common.util.object.BeanUtils; +import com.tashow.cloud.security.security.core.util.SecurityFrameworkUtils; +import com.tashow.cloud.system.controller.admin.oauth2.vo.open.OAuth2OpenAccessTokenRespVO; +import com.tashow.cloud.system.controller.admin.oauth2.vo.open.OAuth2OpenAuthorizeInfoRespVO; +import com.tashow.cloud.system.controller.admin.oauth2.vo.open.OAuth2OpenCheckTokenRespVO; +import com.tashow.cloud.system.dal.dataobject.oauth2.OAuth2AccessTokenDO; +import com.tashow.cloud.system.dal.dataobject.oauth2.OAuth2ApproveDO; +import com.tashow.cloud.system.dal.dataobject.oauth2.OAuth2ClientDO; +import com.tashow.cloud.system.util.oauth2.OAuth2Utils; +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +@Mapper +public interface OAuth2OpenConvert { + + OAuth2OpenConvert INSTANCE = Mappers.getMapper(OAuth2OpenConvert.class); + + default OAuth2OpenAccessTokenRespVO convert(OAuth2AccessTokenDO bean) { + OAuth2OpenAccessTokenRespVO respVO = BeanUtils.toBean(bean, OAuth2OpenAccessTokenRespVO.class); + respVO.setTokenType(SecurityFrameworkUtils.AUTHORIZATION_BEARER.toLowerCase()); + respVO.setExpiresIn(OAuth2Utils.getExpiresIn(bean.getExpiresTime())); + respVO.setScope(OAuth2Utils.buildScopeStr(bean.getScopes())); + return respVO; + } + + default OAuth2OpenCheckTokenRespVO convert2(OAuth2AccessTokenDO bean) { + OAuth2OpenCheckTokenRespVO respVO = BeanUtils.toBean(bean, OAuth2OpenCheckTokenRespVO.class); + respVO.setExp(LocalDateTimeUtil.toEpochMilli(bean.getExpiresTime()) / 1000L); + respVO.setUserType(UserTypeEnum.ADMIN.getValue()); + return respVO; + } + + default OAuth2OpenAuthorizeInfoRespVO convert(OAuth2ClientDO client, List approves) { + // 构建 scopes + List> scopes = new ArrayList<>(client.getScopes().size()); + Map approveMap = CollectionUtils.convertMap(approves, OAuth2ApproveDO::getScope); + client.getScopes().forEach(scope -> { + OAuth2ApproveDO approve = approveMap.get(scope); + scopes.add(new KeyValue<>(scope, approve != null ? approve.getApproved() : false)); + }); + // 拼接返回 + return new OAuth2OpenAuthorizeInfoRespVO( + new OAuth2OpenAuthorizeInfoRespVO.Client(client.getName(), client.getLogo()), scopes); + } + +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/convert/package-info.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/convert/package-info.java new file mode 100644 index 0000000..af26e66 --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/convert/package-info.java @@ -0,0 +1,6 @@ +/** + * 提供 POJO 类的实体转换 + * + * 目前使用 MapStruct 框架 + */ +package com.tashow.cloud.system.convert; diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/convert/social/SocialUserConvert.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/convert/social/SocialUserConvert.java new file mode 100644 index 0000000..dd27030 --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/convert/social/SocialUserConvert.java @@ -0,0 +1,19 @@ +package com.tashow.cloud.system.convert.social; + +import com.tashow.cloud.systemapi.api.social.dto.SocialUserBindReqDTO; +import com.tashow.cloud.system.controller.admin.socail.vo.user.SocialUserBindReqVO; +import com.tashow.cloud.systemapi.api.social.dto.SocialUserBindReqDTO; +import com.tashow.cloud.system.controller.admin.socail.vo.user.SocialUserBindReqVO; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.factory.Mappers; + +@Mapper +public interface SocialUserConvert { + + SocialUserConvert INSTANCE = Mappers.getMapper(SocialUserConvert.class); + + @Mapping(source = "reqVO.type", target = "socialType") + SocialUserBindReqDTO convert(Long userId, Integer userType, SocialUserBindReqVO reqVO); + +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/convert/tenant/TenantConvert.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/convert/tenant/TenantConvert.java new file mode 100644 index 0000000..5a95cb1 --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/convert/tenant/TenantConvert.java @@ -0,0 +1,28 @@ +package com.tashow.cloud.system.convert.tenant; + +import com.tashow.cloud.system.controller.admin.tenant.vo.tenant.TenantSaveReqVO; +import com.tashow.cloud.system.controller.admin.user.vo.user.UserSaveReqVO; +import com.tashow.cloud.system.controller.admin.tenant.vo.tenant.TenantSaveReqVO; +import com.tashow.cloud.system.controller.admin.user.vo.user.UserSaveReqVO; +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +/** + * 租户 Convert + * + * @author 芋道源码 + */ +@Mapper +public interface TenantConvert { + + TenantConvert INSTANCE = Mappers.getMapper(TenantConvert.class); + + default UserSaveReqVO convert02(TenantSaveReqVO bean) { + UserSaveReqVO reqVO = new UserSaveReqVO(); + reqVO.setUsername(bean.getUsername()); + reqVO.setPassword(bean.getPassword()); + reqVO.setNickname(bean.getContactName()).setMobile(bean.getContactMobile()); + return reqVO; + } + +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/convert/user/UserConvert.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/convert/user/UserConvert.java new file mode 100644 index 0000000..af36309 --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/convert/user/UserConvert.java @@ -0,0 +1,58 @@ +package com.tashow.cloud.system.convert.user; + +import com.tashow.cloud.common.util.collection.CollectionUtils; +import com.tashow.cloud.common.util.collection.MapUtils; +import com.tashow.cloud.common.util.object.BeanUtils; +import com.tashow.cloud.system.controller.admin.dept.vo.dept.DeptSimpleRespVO; +import com.tashow.cloud.system.controller.admin.dept.vo.post.PostSimpleRespVO; +import com.tashow.cloud.system.controller.admin.permission.vo.role.RoleSimpleRespVO; +import com.tashow.cloud.system.controller.admin.user.vo.profile.UserProfileRespVO; +import com.tashow.cloud.system.controller.admin.user.vo.user.UserRespVO; +import com.tashow.cloud.system.controller.admin.user.vo.user.UserSimpleRespVO; +import com.tashow.cloud.system.dal.dataobject.dept.DeptDO; +import com.tashow.cloud.system.dal.dataobject.dept.PostDO; +import com.tashow.cloud.system.dal.dataobject.permission.RoleDO; +import com.tashow.cloud.system.dal.dataobject.social.SocialUserDO; +import com.tashow.cloud.system.dal.dataobject.user.AdminUserDO; +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +import java.util.List; +import java.util.Map; + +@Mapper +public interface UserConvert { + + UserConvert INSTANCE = Mappers.getMapper(UserConvert.class); + + default List convertList(List list, Map deptMap) { + return CollectionUtils.convertList(list, user -> convert(user, deptMap.get(user.getDeptId()))); + } + + default UserRespVO convert(AdminUserDO user, DeptDO dept) { + UserRespVO userVO = BeanUtils.toBean(user, UserRespVO.class); + if (dept != null) { + userVO.setDeptName(dept.getName()); + } + return userVO; + } + + default List convertSimpleList(List list, Map deptMap) { + return CollectionUtils.convertList(list, user -> { + UserSimpleRespVO userVO = BeanUtils.toBean(user, UserSimpleRespVO.class); + MapUtils.findAndThen(deptMap, user.getDeptId(), dept -> userVO.setDeptName(dept.getName())); + return userVO; + }); + } + + default UserProfileRespVO convert(AdminUserDO user, List userRoles, + DeptDO dept, List posts, List socialUsers) { + UserProfileRespVO userVO = BeanUtils.toBean(user, UserProfileRespVO.class); + userVO.setRoles(BeanUtils.toBean(userRoles, RoleSimpleRespVO.class)); + userVO.setDept(BeanUtils.toBean(dept, DeptSimpleRespVO.class)); + userVO.setPosts(BeanUtils.toBean(posts, PostSimpleRespVO.class)); + userVO.setSocialUsers(BeanUtils.toBean(socialUsers, UserProfileRespVO.SocialUser.class)); + return userVO; + } + +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/convert/《芋道 Spring Boot 对象转换 MapStruct 入门》.md b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/convert/《芋道 Spring Boot 对象转换 MapStruct 入门》.md new file mode 100644 index 0000000..8153487 --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/convert/《芋道 Spring Boot 对象转换 MapStruct 入门》.md @@ -0,0 +1 @@ + diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/dal/dataobject/dept/DeptDO.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/dal/dataobject/dept/DeptDO.java new file mode 100644 index 0000000..6726704 --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/dal/dataobject/dept/DeptDO.java @@ -0,0 +1,66 @@ +package com.tashow.cloud.system.dal.dataobject.dept; + +import com.tashow.cloud.common.enums.CommonStatusEnum; +import com.tashow.cloud.tenant.core.db.TenantBaseDO; +import com.tashow.cloud.system.dal.dataobject.user.AdminUserDO; +import com.baomidou.mybatisplus.annotation.KeySequence; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 部门表 + * + * @author ruoyi + * @author 芋道源码 + */ +@TableName("system_dept") +@KeySequence("system_dept_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 +@Data +@EqualsAndHashCode(callSuper = true) +public class DeptDO extends TenantBaseDO { + + public static final Long PARENT_ID_ROOT = 0L; + + /** + * 部门ID + */ + @TableId + private Long id; + /** + * 部门名称 + */ + private String name; + /** + * 父部门ID + * + * 关联 {@link #id} + */ + private Long parentId; + /** + * 显示顺序 + */ + private Integer sort; + /** + * 负责人 + * + * 关联 {@link AdminUserDO#getId()} + */ + private Long leaderUserId; + /** + * 联系电话 + */ + private String phone; + /** + * 邮箱 + */ + private String email; + /** + * 部门状态 + * + * 枚举 {@link CommonStatusEnum} + */ + private Integer status; + +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/dal/dataobject/dept/PostDO.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/dal/dataobject/dept/PostDO.java new file mode 100644 index 0000000..2b62445 --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/dal/dataobject/dept/PostDO.java @@ -0,0 +1,50 @@ +package com.tashow.cloud.system.dal.dataobject.dept; + +import com.tashow.cloud.common.enums.CommonStatusEnum; +import com.tashow.cloud.mybatis.mybatis.core.dataobject.BaseDO; +import com.baomidou.mybatisplus.annotation.KeySequence; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 岗位表 + * + * @author ruoyi + */ +@TableName("system_post") +@KeySequence("system_post_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 +@Data +@EqualsAndHashCode(callSuper = true) +public class PostDO extends BaseDO { + + /** + * 岗位序号 + */ + @TableId + private Long id; + /** + * 岗位名称 + */ + private String name; + /** + * 岗位编码 + */ + private String code; + /** + * 岗位排序 + */ + private Integer sort; + /** + * 状态 + * + * 枚举 {@link CommonStatusEnum} + */ + private Integer status; + /** + * 备注 + */ + private String remark; + +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/dal/dataobject/dept/UserPostDO.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/dal/dataobject/dept/UserPostDO.java new file mode 100644 index 0000000..1327596 --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/dal/dataobject/dept/UserPostDO.java @@ -0,0 +1,41 @@ +package com.tashow.cloud.system.dal.dataobject.dept; + +import com.tashow.cloud.mybatis.mybatis.core.dataobject.BaseDO; +import com.tashow.cloud.system.dal.dataobject.user.AdminUserDO; +import com.baomidou.mybatisplus.annotation.KeySequence; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import com.tashow.cloud.system.dal.dataobject.user.AdminUserDO; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 用户和岗位关联 + * + * @author ruoyi + */ +@TableName("system_user_post") +@KeySequence("system_user_post_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 +@Data +@EqualsAndHashCode(callSuper = true) +public class UserPostDO extends BaseDO { + + /** + * 自增主键 + */ + @TableId + private Long id; + /** + * 用户 ID + * + * 关联 {@link AdminUserDO#getId()} + */ + private Long userId; + /** + * 角色 ID + * + * 关联 {@link PostDO#getId()} + */ + private Long postId; + +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/dal/dataobject/dict/DictDataDO.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/dal/dataobject/dict/DictDataDO.java new file mode 100644 index 0000000..0c36135 --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/dal/dataobject/dict/DictDataDO.java @@ -0,0 +1,65 @@ +package com.tashow.cloud.system.dal.dataobject.dict; + +import com.tashow.cloud.common.enums.CommonStatusEnum; +import com.tashow.cloud.mybatis.mybatis.core.dataobject.BaseDO; +import com.baomidou.mybatisplus.annotation.*; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 字典数据表 + * + * @author ruoyi + */ +@TableName("system_dict_data") +@KeySequence("system_dict_data_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 +@Data +@EqualsAndHashCode(callSuper = true) +public class DictDataDO extends BaseDO { + + /** + * 字典数据编号 + */ + @TableId + private Long id; + /** + * 字典排序 + */ + private Integer sort; + /** + * 字典标签 + */ + private String label; + /** + * 字典值 + */ + private String value; + /** + * 字典类型 + * + * 冗余 {@link DictDataDO#getDictType()} + */ + private String dictType; + /** + * 状态 + * + * 枚举 {@link CommonStatusEnum} + */ + private Integer status; + /** + * 颜色类型 + * + * 对应到 element-ui 为 default、primary、success、info、warning、danger + */ + private String colorType; + /** + * css 样式 + */ + @TableField(updateStrategy = FieldStrategy.ALWAYS) + private String cssClass; + /** + * 备注 + */ + private String remark; + +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/dal/dataobject/dict/DictTypeDO.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/dal/dataobject/dict/DictTypeDO.java new file mode 100644 index 0000000..c137067 --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/dal/dataobject/dict/DictTypeDO.java @@ -0,0 +1,56 @@ +package com.tashow.cloud.system.dal.dataobject.dict; + +import com.tashow.cloud.common.enums.CommonStatusEnum; +import com.tashow.cloud.mybatis.mybatis.core.dataobject.BaseDO; +import com.baomidou.mybatisplus.annotation.KeySequence; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.*; + +import java.time.LocalDateTime; + +/** + * 字典类型表 + * + * @author ruoyi + */ +@TableName("system_dict_type") +@KeySequence("system_dict_type_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class DictTypeDO extends BaseDO { + + /** + * 字典主键 + */ + @TableId + private Long id; + /** + * 字典名称 + */ + private String name; + /** + * 字典类型 + */ + private String type; + /** + * 状态 + * + * 枚举 {@link CommonStatusEnum} + */ + private Integer status; + /** + * 备注 + */ + private String remark; + + /** + * 删除时间 + */ + private LocalDateTime deletedTime; + +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/dal/dataobject/logger/LoginLogDO.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/dal/dataobject/logger/LoginLogDO.java new file mode 100644 index 0000000..1a7078f --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/dal/dataobject/logger/LoginLogDO.java @@ -0,0 +1,74 @@ +package com.tashow.cloud.system.dal.dataobject.logger; + +import com.tashow.cloud.common.enums.UserTypeEnum; +import com.tashow.cloud.mybatis.mybatis.core.dataobject.BaseDO; +import com.tashow.cloud.systemapi.enums.logger.LoginLogTypeEnum; +import com.tashow.cloud.systemapi.enums.logger.LoginResultEnum; +import com.baomidou.mybatisplus.annotation.KeySequence; +import com.baomidou.mybatisplus.annotation.TableName; +import com.tashow.cloud.systemapi.enums.logger.LoginLogTypeEnum; +import com.tashow.cloud.systemapi.enums.logger.LoginResultEnum; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; + +/** + * 登录日志表 + * + * 注意,包括登录和登出两种行为 + * + * @author 芋道源码 + */ +@TableName("system_login_log") +@KeySequence("system_login_log_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class LoginLogDO extends BaseDO { + + /** + * 日志主键 + */ + private Long id; + /** + * 日志类型 + * + * 枚举 {@link LoginLogTypeEnum} + */ + private Integer logType; + /** + * 链路追踪编号 + */ + private String traceId; + /** + * 用户编号 + */ + private Long userId; + /** + * 用户类型 + * + * 枚举 {@link UserTypeEnum} + */ + private Integer userType; + /** + * 用户账号 + * + * 冗余,因为账号可以变更 + */ + private String username; + /** + * 登录结果 + * + * 枚举 {@link LoginResultEnum} + */ + private Integer result; + /** + * 用户 IP + */ + private String userIp; + /** + * 浏览器 UA + */ + private String userAgent; + +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/dal/dataobject/logger/OperateLogDO.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/dal/dataobject/logger/OperateLogDO.java new file mode 100644 index 0000000..e40f680 --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/dal/dataobject/logger/OperateLogDO.java @@ -0,0 +1,85 @@ +package com.tashow.cloud.system.dal.dataobject.logger; + +import com.tashow.cloud.common.enums.UserTypeEnum; +import com.tashow.cloud.mybatis.mybatis.core.dataobject.BaseDO; +import com.baomidou.mybatisplus.annotation.KeySequence; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; + +/** + * 操作日志表 + * + * @author 芋道源码 + */ +@TableName(value = "system_operate_log", autoResultMap = true) +@KeySequence("system_operate_log_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 +@Data +public class OperateLogDO extends BaseDO { + + /** + * 日志主键 + */ + @TableId + private Long id; + /** + * 链路追踪编号 + * + * 一般来说,通过链路追踪编号,可以将访问日志,错误日志,链路追踪日志,logger 打印日志等,结合在一起,从而进行排错。 + */ + private String traceId; + /** + * 用户编号 + * + * 关联 MemberUserDO 的 id 属性,或者 AdminUserDO 的 id 属性 + */ + private Long userId; + /** + * 用户类型 + * + * 关联 {@link UserTypeEnum} + */ + private Integer userType; + /** + * 操作模块类型 + */ + private String type; + /** + * 操作名 + */ + private String subType; + /** + * 操作模块业务编号 + */ + private Long bizId; + /** + * 日志内容,记录整个操作的明细 + * + * 例如说,修改编号为 1 的用户信息,将性别从男改成女,将姓名从芋道改成源码。 + */ + private String action; + /** + * 拓展字段,有些复杂的业务,需要记录一些字段 ( JSON 格式 ) + * + * 例如说,记录订单编号,{ orderId: "1"} + */ + private String extra; + + /** + * 请求方法名 + */ + private String requestMethod; + /** + * 请求地址 + */ + private String requestUrl; + /** + * 用户 IP + */ + private String userIp; + /** + * 浏览器 UA + */ + private String userAgent; + +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/dal/dataobject/mail/MailAccountDO.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/dal/dataobject/mail/MailAccountDO.java new file mode 100644 index 0000000..c332efb --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/dal/dataobject/mail/MailAccountDO.java @@ -0,0 +1,59 @@ +package com.tashow.cloud.system.dal.dataobject.mail; + +import com.tashow.cloud.mybatis.mybatis.core.dataobject.BaseDO; +import com.baomidou.mybatisplus.annotation.KeySequence; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 邮箱账号 DO + * + * 用途:配置发送邮箱的账号 + * + * @author wangjingyi + * @since 2022-03-21 + */ +@TableName(value = "system_mail_account", autoResultMap = true) +@KeySequence("system_mail_account_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 +@Data +@EqualsAndHashCode(callSuper = true) +public class MailAccountDO extends BaseDO { + + /** + * 主键 + */ + @TableId + private Long id; + /** + * 邮箱 + */ + private String mail; + + /** + * 用户名 + */ + private String username; + /** + * 密码 + */ + private String password; + /** + * SMTP 服务器域名 + */ + private String host; + /** + * SMTP 服务器端口 + */ + private Integer port; + /** + * 是否开启 SSL + */ + private Boolean sslEnable; + /** + * 是否开启 STARTTLS + */ + private Boolean starttlsEnable; + +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/dal/dataobject/mail/MailLogDO.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/dal/dataobject/mail/MailLogDO.java new file mode 100644 index 0000000..a9f439f --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/dal/dataobject/mail/MailLogDO.java @@ -0,0 +1,125 @@ +package com.tashow.cloud.system.dal.dataobject.mail; + +import com.tashow.cloud.common.enums.UserTypeEnum; +import com.tashow.cloud.mybatis.mybatis.core.dataobject.BaseDO; +import com.tashow.cloud.systemapi.enums.mail.MailSendStatusEnum; +import com.baomidou.mybatisplus.annotation.KeySequence; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler; +import com.tashow.cloud.systemapi.enums.mail.MailSendStatusEnum; +import lombok.*; + +import java.io.Serializable; +import java.time.LocalDateTime; +import java.util.Date; +import java.util.Map; + +/** + * 邮箱日志 DO + * 记录每一次邮件的发送 + * + * @author wangjingyi + * @since 2022-03-21 + */ +@TableName(value = "system_mail_log", autoResultMap = true) +@KeySequence("system_mail_log_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +@Builder +@AllArgsConstructor +@NoArgsConstructor +public class MailLogDO extends BaseDO implements Serializable { + + /** + * 日志编号,自增 + */ + private Long id; + + /** + * 用户编码 + */ + private Long userId; + /** + * 用户类型 + * + * 枚举 {@link UserTypeEnum} + */ + private Integer userType; + /** + * 接收邮箱地址 + */ + private String toMail; + + /** + * 邮箱账号编号 + * + * 关联 {@link MailAccountDO#getId()} + */ + private Long accountId; + /** + * 发送邮箱地址 + * + * 冗余 {@link MailAccountDO#getMail()} + */ + private String fromMail; + + // ========= 模板相关字段 ========= + /** + * 模版编号 + * + * 关联 {@link MailTemplateDO#getId()} + */ + private Long templateId; + /** + * 模版编码 + * + * 冗余 {@link MailTemplateDO#getCode()} + */ + private String templateCode; + /** + * 模版发送人名称 + * + * 冗余 {@link MailTemplateDO#getNickname()} + */ + private String templateNickname; + /** + * 模版标题 + */ + private String templateTitle; + /** + * 模版内容 + * + * 基于 {@link MailTemplateDO#getContent()} 格式化后的内容 + */ + private String templateContent; + /** + * 模版参数 + * + * 基于 {@link MailTemplateDO#getParams()} 输入后的参数 + */ + @TableField(typeHandler = JacksonTypeHandler.class) + private Map templateParams; + + // ========= 发送相关字段 ========= + /** + * 发送状态 + * + * 枚举 {@link MailSendStatusEnum} + */ + private Integer sendStatus; + /** + * 发送时间 + */ + private LocalDateTime sendTime; + /** + * 发送返回的消息 ID + */ + private String sendMessageId; + /** + * 发送异常 + */ + private String sendException; + +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/dal/dataobject/mail/MailTemplateDO.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/dal/dataobject/mail/MailTemplateDO.java new file mode 100644 index 0000000..ed32f1f --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/dal/dataobject/mail/MailTemplateDO.java @@ -0,0 +1,73 @@ +package com.tashow.cloud.system.dal.dataobject.mail; + +import com.tashow.cloud.common.enums.CommonStatusEnum; +import com.tashow.cloud.mybatis.mybatis.core.dataobject.BaseDO; +import com.baomidou.mybatisplus.annotation.KeySequence; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.util.List; + +/** + * 邮件模版 DO + * + * @author wangjingyi + * @since 2022-03-21 + */ +@TableName(value = "system_mail_template", autoResultMap = true) +@KeySequence("system_mail_template_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 +@Data +@EqualsAndHashCode(callSuper = true) +public class MailTemplateDO extends BaseDO { + + /** + * 主键 + */ + private Long id; + /** + * 模版名称 + */ + private String name; + /** + * 模版编号 + */ + private String code; + /** + * 发送的邮箱账号编号 + * + * 关联 {@link MailAccountDO#getId()} + */ + private Long accountId; + + /** + * 发送人名称 + */ + private String nickname; + /** + * 标题 + */ + private String title; + /** + * 内容 + */ + private String content; + /** + * 参数数组(自动根据内容生成) + */ + @TableField(typeHandler = JacksonTypeHandler.class) + private List params; + /** + * 状态 + * + * 枚举 {@link CommonStatusEnum} + */ + private Integer status; + /** + * 备注 + */ + private String remark; + +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/dal/dataobject/notice/NoticeDO.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/dal/dataobject/notice/NoticeDO.java new file mode 100644 index 0000000..7da5750 --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/dal/dataobject/notice/NoticeDO.java @@ -0,0 +1,48 @@ +package com.tashow.cloud.system.dal.dataobject.notice; + +import com.tashow.cloud.common.enums.CommonStatusEnum; +import com.tashow.cloud.mybatis.mybatis.core.dataobject.BaseDO; +import com.tashow.cloud.systemapi.enums.notice.NoticeTypeEnum; +import com.baomidou.mybatisplus.annotation.KeySequence; +import com.baomidou.mybatisplus.annotation.TableName; +import com.tashow.cloud.systemapi.enums.notice.NoticeTypeEnum; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 通知公告表 + * + * @author ruoyi + */ +@TableName("system_notice") +@KeySequence("system_notice_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 +@Data +@EqualsAndHashCode(callSuper = true) +public class NoticeDO extends BaseDO { + + /** + * 公告ID + */ + private Long id; + /** + * 公告标题 + */ + private String title; + /** + * 公告类型 + * + * 枚举 {@link NoticeTypeEnum} + */ + private Integer type; + /** + * 公告内容 + */ + private String content; + /** + * 公告状态 + * + * 枚举 {@link CommonStatusEnum} + */ + private Integer status; + +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/dal/dataobject/notify/NotifyMessageDO.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/dal/dataobject/notify/NotifyMessageDO.java new file mode 100644 index 0000000..d3d58e1 --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/dal/dataobject/notify/NotifyMessageDO.java @@ -0,0 +1,99 @@ +package com.tashow.cloud.system.dal.dataobject.notify; + +import com.tashow.cloud.common.enums.UserTypeEnum; +import com.tashow.cloud.mybatis.mybatis.core.dataobject.BaseDO; +import com.baomidou.mybatisplus.annotation.KeySequence; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler; +import lombok.*; + +import java.time.LocalDateTime; +import java.util.Map; + +/** + * 站内信 DO + * + * @author xrcoder + */ +@TableName(value = "system_notify_message", autoResultMap = true) +@KeySequence("system_notify_message_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class NotifyMessageDO extends BaseDO { + + /** + * 站内信编号,自增 + */ + @TableId + private Long id; + /** + * 用户编号 + * + * 关联 MemberUserDO 的 id 字段、或者 AdminUserDO 的 id 字段 + */ + private Long userId; + /** + * 用户类型 + * + * 枚举 {@link UserTypeEnum} + */ + private Integer userType; + + // ========= 模板相关字段 ========= + + /** + * 模版编号 + * + * 关联 {@link NotifyTemplateDO#getId()} + */ + private Long templateId; + /** + * 模版编码 + * + * 关联 {@link NotifyTemplateDO#getCode()} + */ + private String templateCode; + /** + * 模版类型 + * + * 冗余 {@link NotifyTemplateDO#getType()} + */ + private Integer templateType; + /** + * 模版发送人名称 + * + * 冗余 {@link NotifyTemplateDO#getNickname()} + */ + private String templateNickname; + /** + * 模版内容 + * + * 基于 {@link NotifyTemplateDO#getContent()} 格式化后的内容 + */ + private String templateContent; + /** + * 模版参数 + * + * 基于 {@link NotifyTemplateDO#getParams()} 输入后的参数 + */ + @TableField(typeHandler = JacksonTypeHandler.class) + private Map templateParams; + + // ========= 读取相关字段 ========= + + /** + * 是否已读 + */ + private Boolean readStatus; + /** + * 阅读时间 + */ + private LocalDateTime readTime; + +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/dal/dataobject/notify/NotifyTemplateDO.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/dal/dataobject/notify/NotifyTemplateDO.java new file mode 100644 index 0000000..687964b --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/dal/dataobject/notify/NotifyTemplateDO.java @@ -0,0 +1,72 @@ +package com.tashow.cloud.system.dal.dataobject.notify; + +import com.tashow.cloud.common.enums.CommonStatusEnum; +import com.tashow.cloud.mybatis.mybatis.core.dataobject.BaseDO; +import com.baomidou.mybatisplus.annotation.KeySequence; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler; +import lombok.*; + +import java.util.List; + +/** + * 站内信模版 DO + * + * @author xrcoder + */ +@TableName(value = "system_notify_template", autoResultMap = true) +@KeySequence("system_notify_template_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class NotifyTemplateDO extends BaseDO { + + /** + * ID + */ + @TableId + private Long id; + /** + * 模版名称 + */ + private String name; + /** + * 模版编码 + */ + private String code; + /** + * 模版类型 + * + * 对应 system_notify_template_type 字典 + */ + private Integer type; + /** + * 发送人名称 + */ + private String nickname; + /** + * 模版内容 + */ + private String content; + /** + * 参数数组 + */ + @TableField(typeHandler = JacksonTypeHandler.class) + private List params; + /** + * 状态 + * + * 枚举 {@link CommonStatusEnum} + */ + private Integer status; + /** + * 备注 + */ + private String remark; + +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/dal/dataobject/oauth2/OAuth2AccessTokenDO.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/dal/dataobject/oauth2/OAuth2AccessTokenDO.java new file mode 100644 index 0000000..d07e668 --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/dal/dataobject/oauth2/OAuth2AccessTokenDO.java @@ -0,0 +1,75 @@ +package com.tashow.cloud.system.dal.dataobject.oauth2; + +import com.tashow.cloud.common.enums.UserTypeEnum; +import com.tashow.cloud.tenant.core.db.TenantBaseDO; +import com.baomidou.mybatisplus.annotation.KeySequence; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.time.LocalDateTime; +import java.util.List; +import java.util.Map; + +/** + * OAuth2 访问令牌 DO + * + * 如下字段,暂时未使用,暂时不支持: + * user_name、authentication(用户信息) + * + * @author 芋道源码 + */ +@TableName(value = "system_oauth2_access_token", autoResultMap = true) +@KeySequence("system_oauth2_access_token_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 +@Data +@EqualsAndHashCode(callSuper = true) +public class OAuth2AccessTokenDO extends TenantBaseDO { + + /** + * 编号,数据库递增 + */ + @TableId + private Long id; + /** + * 访问令牌 + */ + private String accessToken; + /** + * 刷新令牌 + */ + private String refreshToken; + /** + * 用户编号 + */ + private Long userId; + /** + * 用户类型 + * + * 枚举 {@link UserTypeEnum} + */ + private Integer userType; + /** + * 用户信息 + */ + @TableField(typeHandler = JacksonTypeHandler.class) + private Map userInfo; + /** + * 客户端编号 + * + * 关联 {@link OAuth2ClientDO#getId()} + */ + private String clientId; + /** + * 授权范围 + */ + @TableField(typeHandler = JacksonTypeHandler.class) + private List scopes; + /** + * 过期时间 + */ + private LocalDateTime expiresTime; + +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/dal/dataobject/oauth2/OAuth2ApproveDO.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/dal/dataobject/oauth2/OAuth2ApproveDO.java new file mode 100644 index 0000000..747325d --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/dal/dataobject/oauth2/OAuth2ApproveDO.java @@ -0,0 +1,63 @@ +package com.tashow.cloud.system.dal.dataobject.oauth2; + +import com.tashow.cloud.common.enums.UserTypeEnum; +import com.tashow.cloud.mybatis.mybatis.core.dataobject.BaseDO; +import com.baomidou.mybatisplus.annotation.KeySequence; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.time.LocalDateTime; + +/** + * OAuth2 批准 DO + * + * 用户在 sso.vue 界面时,记录接受的 scope 列表 + * + * @author 芋道源码 + */ +@TableName(value = "system_oauth2_approve", autoResultMap = true) +@KeySequence("system_oauth2_approve_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 +@Data +@EqualsAndHashCode(callSuper = true) +public class OAuth2ApproveDO extends BaseDO { + + /** + * 编号,数据库自增 + */ + @TableId + private Long id; + /** + * 用户编号 + */ + private Long userId; + /** + * 用户类型 + * + * 枚举 {@link UserTypeEnum} + */ + private Integer userType; + /** + * 客户端编号 + * + * 关联 {@link OAuth2ClientDO#getId()} + */ + private String clientId; + /** + * 授权范围 + */ + private String scope; + /** + * 是否接受 + * + * true - 接受 + * false - 拒绝 + */ + private Boolean approved; + /** + * 过期时间 + */ + private LocalDateTime expiresTime; + +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/dal/dataobject/oauth2/OAuth2ClientDO.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/dal/dataobject/oauth2/OAuth2ClientDO.java new file mode 100644 index 0000000..1f00e22 --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/dal/dataobject/oauth2/OAuth2ClientDO.java @@ -0,0 +1,108 @@ +package com.tashow.cloud.system.dal.dataobject.oauth2; + +import com.tashow.cloud.common.enums.CommonStatusEnum; +import com.tashow.cloud.mybatis.mybatis.core.dataobject.BaseDO; +import com.tashow.cloud.systemapi.enums.oauth2.OAuth2GrantTypeEnum; +import com.baomidou.mybatisplus.annotation.KeySequence; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler; +import com.tashow.cloud.systemapi.enums.oauth2.OAuth2GrantTypeEnum; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.util.List; + +/** + * OAuth2 客户端 DO + * + * @author 芋道源码 + */ +@TableName(value = "system_oauth2_client", autoResultMap = true) +@KeySequence("system_oauth2_client_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 +@Data +@EqualsAndHashCode(callSuper = true) +public class OAuth2ClientDO extends BaseDO { + + /** + * 编号,数据库自增 + * + * 由于 SQL Server 在存储 String 主键有点问题,所以暂时使用 Long 类型 + */ + @TableId + private Long id; + /** + * 客户端编号 + */ + private String clientId; + /** + * 客户端密钥 + */ + private String secret; + /** + * 应用名 + */ + private String name; + /** + * 应用图标 + */ + private String logo; + /** + * 应用描述 + */ + private String description; + /** + * 状态 + * + * 枚举 {@link CommonStatusEnum} + */ + private Integer status; + /** + * 访问令牌的有效期 + */ + private Integer accessTokenValiditySeconds; + /** + * 刷新令牌的有效期 + */ + private Integer refreshTokenValiditySeconds; + /** + * 可重定向的 URI 地址 + */ + @TableField(typeHandler = JacksonTypeHandler.class) + private List redirectUris; + /** + * 授权类型(模式) + * + * 枚举 {@link OAuth2GrantTypeEnum} + */ + @TableField(typeHandler = JacksonTypeHandler.class) + private List authorizedGrantTypes; + /** + * 授权范围 + */ + @TableField(typeHandler = JacksonTypeHandler.class) + private List scopes; + /** + * 自动授权的 Scope + * + * code 授权时,如果 scope 在这个范围内,则自动通过 + */ + @TableField(typeHandler = JacksonTypeHandler.class) + private List autoApproveScopes; + /** + * 权限 + */ + @TableField(typeHandler = JacksonTypeHandler.class) + private List authorities; + /** + * 资源 + */ + @TableField(typeHandler = JacksonTypeHandler.class) + private List resourceIds; + /** + * 附加信息,JSON 格式 + */ + private String additionalInformation; + +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/dal/dataobject/oauth2/OAuth2CodeDO.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/dal/dataobject/oauth2/OAuth2CodeDO.java new file mode 100644 index 0000000..50d193e --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/dal/dataobject/oauth2/OAuth2CodeDO.java @@ -0,0 +1,68 @@ +package com.tashow.cloud.system.dal.dataobject.oauth2; + +import com.tashow.cloud.common.enums.UserTypeEnum; +import com.tashow.cloud.mybatis.mybatis.core.dataobject.BaseDO; +import com.baomidou.mybatisplus.annotation.KeySequence; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.time.LocalDateTime; +import java.util.List; + +/** + * OAuth2 授权码 DO + * + * @author 芋道源码 + */ +@TableName(value = "system_oauth2_code", autoResultMap = true) +@KeySequence("system_oauth2_code_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 +@Data +@EqualsAndHashCode(callSuper = true) +public class OAuth2CodeDO extends BaseDO { + + /** + * 编号,数据库递增 + */ + private Long id; + /** + * 授权码 + */ + private String code; + /** + * 用户编号 + */ + private Long userId; + /** + * 用户类型 + * + * 枚举 {@link UserTypeEnum} + */ + private Integer userType; + /** + * 客户端编号 + * + * 关联 {@link OAuth2ClientDO#getClientId()} + */ + private String clientId; + /** + * 授权范围 + */ + @TableField(typeHandler = JacksonTypeHandler.class) + private List scopes; + /** + * 重定向地址 + */ + private String redirectUri; + /** + * 状态 + */ + private String state; + /** + * 过期时间 + */ + private LocalDateTime expiresTime; + +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/dal/dataobject/oauth2/OAuth2RefreshTokenDO.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/dal/dataobject/oauth2/OAuth2RefreshTokenDO.java new file mode 100644 index 0000000..40c87a2 --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/dal/dataobject/oauth2/OAuth2RefreshTokenDO.java @@ -0,0 +1,62 @@ +package com.tashow.cloud.system.dal.dataobject.oauth2; + +import com.tashow.cloud.common.enums.UserTypeEnum; +import com.tashow.cloud.tenant.core.db.TenantBaseDO; +import com.baomidou.mybatisplus.annotation.KeySequence; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.experimental.Accessors; + +import java.time.LocalDateTime; +import java.util.List; + +/** + * OAuth2 刷新令牌 + * + * @author 芋道源码 + */ +@TableName(value = "system_oauth2_refresh_token", autoResultMap = true) +// 由于 Oracle 的 SEQ 的名字长度有限制,所以就先用 system_oauth2_access_token_seq 吧,反正也没啥问题 +@Data +@EqualsAndHashCode(callSuper = true) +@Accessors(chain = true) +public class OAuth2RefreshTokenDO extends TenantBaseDO { + + /** + * 编号,数据库字典 + */ + private Long id; + /** + * 刷新令牌 + */ + private String refreshToken; + /** + * 用户编号 + */ + private Long userId; + /** + * 用户类型 + * + * 枚举 {@link UserTypeEnum} + */ + private Integer userType; + /** + * 客户端编号 + * + * 关联 {@link OAuth2ClientDO#getId()} + */ + private String clientId; + /** + * 授权范围 + */ + @TableField(typeHandler = JacksonTypeHandler.class) + private List scopes; + /** + * 过期时间 + */ + private LocalDateTime expiresTime; + +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/dal/dataobject/permission/MenuDO.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/dal/dataobject/permission/MenuDO.java new file mode 100644 index 0000000..f31febe --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/dal/dataobject/permission/MenuDO.java @@ -0,0 +1,108 @@ +package com.tashow.cloud.system.dal.dataobject.permission; + +import com.tashow.cloud.common.enums.CommonStatusEnum; +import com.tashow.cloud.mybatis.mybatis.core.dataobject.BaseDO; +import com.tashow.cloud.systemapi.enums.permission.MenuTypeEnum; +import com.baomidou.mybatisplus.annotation.KeySequence; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import com.tashow.cloud.systemapi.enums.permission.MenuTypeEnum; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 菜单 DO + * + * @author ruoyi + */ +@TableName("system_menu") +@KeySequence("system_menu_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 +@Data +@EqualsAndHashCode(callSuper = true) +public class MenuDO extends BaseDO { + + /** + * 菜单编号 - 根节点 + */ + public static final Long ID_ROOT = 0L; + + /** + * 菜单编号 + */ + @TableId + private Long id; + /** + * 菜单名称 + */ + private String name; + /** + * 权限标识 + * + * 一般格式为:${系统}:${模块}:${操作} + * 例如说:system:admin:add,即 system 服务的添加管理员。 + * + * 当我们把该 MenuDO 赋予给角色后,意味着该角色有该资源: + * - 对于后端,配合 @PreAuthorize 注解,配置 API 接口需要该权限,从而对 API 接口进行权限控制。 + * - 对于前端,配合前端标签,配置按钮是否展示,避免用户没有该权限时,结果可以看到该操作。 + */ + private String permission; + /** + * 菜单类型 + * + * 枚举 {@link MenuTypeEnum} + */ + private Integer type; + /** + * 显示顺序 + */ + private Integer sort; + /** + * 父菜单ID + */ + private Long parentId; + /** + * 路由地址 + * + * 如果 path 为 http(s) 时,则它是外链 + */ + private String path; + /** + * 菜单图标 + */ + private String icon; + /** + * 组件路径 + */ + private String component; + /** + * 组件名 + */ + private String componentName; + /** + * 状态 + * + * 枚举 {@link CommonStatusEnum} + */ + private Integer status; + /** + * 是否可见 + * + * 只有菜单、目录使用 + * 当设置为 true 时,该菜单不会展示在侧边栏,但是路由还是存在。例如说,一些独立的编辑页面 /edit/1024 等等 + */ + private Boolean visible; + /** + * 是否缓存 + * + * 只有菜单、目录使用,否使用 Vue 路由的 keep-alive 特性 + * 注意:如果开启缓存,则必须填写 {@link #componentName} 属性,否则无法缓存 + */ + private Boolean keepAlive; + /** + * 是否总是显示 + * + * 如果为 false 时,当该菜单只有一个子菜单时,不展示自己,直接展示子菜单 + */ + private Boolean alwaysShow; + +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/dal/dataobject/permission/RoleDO.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/dal/dataobject/permission/RoleDO.java new file mode 100644 index 0000000..3025dd5 --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/dal/dataobject/permission/RoleDO.java @@ -0,0 +1,80 @@ +package com.tashow.cloud.system.dal.dataobject.permission; + +import com.tashow.cloud.common.enums.CommonStatusEnum; +import com.tashow.cloud.tenant.core.db.TenantBaseDO; +import com.tashow.cloud.systemapi.enums.permission.DataScopeEnum; +import com.tashow.cloud.systemapi.enums.permission.RoleTypeEnum; +import com.baomidou.mybatisplus.annotation.KeySequence; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler; +import com.tashow.cloud.systemapi.enums.permission.DataScopeEnum; +import com.tashow.cloud.systemapi.enums.permission.RoleTypeEnum; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.util.Set; + +/** + * 角色 DO + * + * @author ruoyi + */ +@TableName(value = "system_role", autoResultMap = true) +@KeySequence("system_role_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 +@Data +@EqualsAndHashCode(callSuper = true) +public class RoleDO extends TenantBaseDO { + + /** + * 角色ID + */ + @TableId + private Long id; + /** + * 角色名称 + */ + private String name; + /** + * 角色标识 + * + * 枚举 + */ + private String code; + /** + * 角色排序 + */ + private Integer sort; + /** + * 角色状态 + * + * 枚举 {@link CommonStatusEnum} + */ + private Integer status; + /** + * 角色类型 + * + * 枚举 {@link RoleTypeEnum} + */ + private Integer type; + /** + * 备注 + */ + private String remark; + + /** + * 数据范围 + * + * 枚举 {@link DataScopeEnum} + */ + private Integer dataScope; + /** + * 数据范围(指定部门数组) + * + * 适用于 {@link #dataScope} 的值为 {@link DataScopeEnum#DEPT_CUSTOM} 时 + */ + @TableField(typeHandler = JacksonTypeHandler.class) + private Set dataScopeDeptIds; + +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/dal/dataobject/permission/RoleMenuDO.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/dal/dataobject/permission/RoleMenuDO.java new file mode 100644 index 0000000..d586bf9 --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/dal/dataobject/permission/RoleMenuDO.java @@ -0,0 +1,35 @@ +package com.tashow.cloud.system.dal.dataobject.permission; + +import com.tashow.cloud.tenant.core.db.TenantBaseDO; +import com.baomidou.mybatisplus.annotation.KeySequence; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 角色和菜单关联 + * + * @author ruoyi + */ +@TableName("system_role_menu") +@KeySequence("system_role_menu_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 +@Data +@EqualsAndHashCode(callSuper = true) +public class RoleMenuDO extends TenantBaseDO { + + /** + * 自增主键 + */ + @TableId + private Long id; + /** + * 角色ID + */ + private Long roleId; + /** + * 菜单ID + */ + private Long menuId; + +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/dal/dataobject/permission/UserRoleDO.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/dal/dataobject/permission/UserRoleDO.java new file mode 100644 index 0000000..a7b2fd4 --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/dal/dataobject/permission/UserRoleDO.java @@ -0,0 +1,35 @@ +package com.tashow.cloud.system.dal.dataobject.permission; + +import com.tashow.cloud.mybatis.mybatis.core.dataobject.BaseDO; +import com.baomidou.mybatisplus.annotation.KeySequence; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 用户和角色关联 + * + * @author ruoyi + */ +@TableName("system_user_role") +@KeySequence("system_user_role_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 +@Data +@EqualsAndHashCode(callSuper = true) +public class UserRoleDO extends BaseDO { + + /** + * 自增主键 + */ + @TableId + private Long id; + /** + * 用户 ID + */ + private Long userId; + /** + * 角色 ID + */ + private Long roleId; + +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/dal/dataobject/sms/SmsChannelDO.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/dal/dataobject/sms/SmsChannelDO.java new file mode 100644 index 0000000..10c4148 --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/dal/dataobject/sms/SmsChannelDO.java @@ -0,0 +1,63 @@ +package com.tashow.cloud.system.dal.dataobject.sms; + +import com.tashow.cloud.common.enums.CommonStatusEnum; +import com.tashow.cloud.mybatis.mybatis.core.dataobject.BaseDO; +import com.tashow.cloud.system.framework.sms.core.enums.SmsChannelEnum; +import com.baomidou.mybatisplus.annotation.KeySequence; +import com.baomidou.mybatisplus.annotation.TableName; +import com.tashow.cloud.system.framework.sms.core.enums.SmsChannelEnum; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; + +/** + * 短信渠道 DO + * + * @author zzf + * @since 2021-01-25 + */ +@TableName(value = "system_sms_channel", autoResultMap = true) +@KeySequence("system_sms_channel_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class SmsChannelDO extends BaseDO { + + /** + * 渠道编号 + */ + private Long id; + /** + * 短信签名 + */ + private String signature; + /** + * 渠道编码 + * + * 枚举 {@link SmsChannelEnum} + */ + private String code; + /** + * 启用状态 + * + * 枚举 {@link CommonStatusEnum} + */ + private Integer status; + /** + * 备注 + */ + private String remark; + /** + * 短信 API 的账号 + */ + private String apiKey; + /** + * 短信 API 的密钥 + */ + private String apiSecret; + /** + * 短信发送回调 URL + */ + private String callbackUrl; + +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/dal/dataobject/sms/SmsCodeDO.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/dal/dataobject/sms/SmsCodeDO.java new file mode 100644 index 0000000..449d28f --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/dal/dataobject/sms/SmsCodeDO.java @@ -0,0 +1,65 @@ +package com.tashow.cloud.system.dal.dataobject.sms; + +import com.tashow.cloud.mybatis.mybatis.core.dataobject.BaseDO; +import com.baomidou.mybatisplus.annotation.KeySequence; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.*; + +import java.time.LocalDateTime; + +/** + * 手机验证码 DO + * + * idx_mobile 索引:基于 {@link #mobile} 字段 + * + * @author 芋道源码 + */ +@TableName("system_sms_code") +@KeySequence("system_sms_code_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 +@Data +@EqualsAndHashCode(callSuper = true) +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class SmsCodeDO extends BaseDO { + + /** + * 编号 + */ + private Long id; + /** + * 手机号 + */ + private String mobile; + /** + * 验证码 + */ + private String code; + /** + * 发送场景 + * + * 枚举 {@link SmsCodeDO} + */ + private Integer scene; + /** + * 创建 IP + */ + private String createIp; + /** + * 今日发送的第几条 + */ + private Integer todayIndex; + /** + * 是否使用 + */ + private Boolean used; + /** + * 使用时间 + */ + private LocalDateTime usedTime; + /** + * 使用 IP + */ + private String usedIp; + +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/dal/dataobject/sms/SmsLogDO.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/dal/dataobject/sms/SmsLogDO.java new file mode 100644 index 0000000..c0ccfd2 --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/dal/dataobject/sms/SmsLogDO.java @@ -0,0 +1,163 @@ +package com.tashow.cloud.system.dal.dataobject.sms; + +import com.tashow.cloud.common.enums.UserTypeEnum; +import com.tashow.cloud.mybatis.mybatis.core.dataobject.BaseDO; +import com.tashow.cloud.systemapi.enums.sms.SmsReceiveStatusEnum; +import com.tashow.cloud.systemapi.enums.sms.SmsSendStatusEnum; +import com.baomidou.mybatisplus.annotation.KeySequence; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler; +import com.tashow.cloud.systemapi.enums.sms.SmsReceiveStatusEnum; +import com.tashow.cloud.systemapi.enums.sms.SmsSendStatusEnum; +import lombok.*; + +import java.time.LocalDateTime; +import java.util.Map; + +/** + * 短信日志 DO + * + * @author zzf + * @since 2021-01-25 + */ +@TableName(value = "system_sms_log", autoResultMap = true) +@KeySequence("system_sms_log_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +@Builder +@AllArgsConstructor +@NoArgsConstructor +public class SmsLogDO extends BaseDO { + + /** + * 自增编号 + */ + private Long id; + + // ========= 渠道相关字段 ========= + + /** + * 短信渠道编号 + * + * 关联 {@link SmsChannelDO#getId()} + */ + private Long channelId; + /** + * 短信渠道编码 + * + * 冗余 {@link SmsChannelDO#getCode()} + */ + private String channelCode; + + // ========= 模板相关字段 ========= + + /** + * 模板编号 + * + * 关联 {@link SmsTemplateDO#getId()} + */ + private Long templateId; + /** + * 模板编码 + * + * 冗余 {@link SmsTemplateDO#getCode()} + */ + private String templateCode; + /** + * 短信类型 + * + * 冗余 {@link SmsTemplateDO#getType()} + */ + private Integer templateType; + /** + * 基于 {@link SmsTemplateDO#getContent()} 格式化后的内容 + */ + private String templateContent; + /** + * 基于 {@link SmsTemplateDO#getParams()} 输入后的参数 + */ + @TableField(typeHandler = JacksonTypeHandler.class) + private Map templateParams; + /** + * 短信 API 的模板编号 + * + * 冗余 {@link SmsTemplateDO#getApiTemplateId()} + */ + private String apiTemplateId; + + // ========= 手机相关字段 ========= + + /** + * 手机号 + */ + private String mobile; + /** + * 用户编号 + */ + private Long userId; + /** + * 用户类型 + * + * 枚举 {@link UserTypeEnum} + */ + private Integer userType; + + // ========= 发送相关字段 ========= + + /** + * 发送状态 + * + * 枚举 {@link SmsSendStatusEnum} + */ + private Integer sendStatus; + /** + * 发送时间 + */ + private LocalDateTime sendTime; + /** + * 短信 API 发送结果的编码 + * + * 由于第三方的错误码可能是字符串,所以使用 String 类型 + */ + private String apiSendCode; + /** + * 短信 API 发送失败的提示 + */ + private String apiSendMsg; + /** + * 短信 API 发送返回的唯一请求 ID + * + * 用于和短信 API 进行定位于排错 + */ + private String apiRequestId; + /** + * 短信 API 发送返回的序号 + * + * 用于和短信 API 平台的发送记录关联 + */ + private String apiSerialNo; + + // ========= 接收相关字段 ========= + + /** + * 接收状态 + * + * 枚举 {@link SmsReceiveStatusEnum} + */ + private Integer receiveStatus; + /** + * 接收时间 + */ + private LocalDateTime receiveTime; + /** + * 短信 API 接收结果的编码 + */ + private String apiReceiveCode; + /** + * 短信 API 接收结果的提示 + */ + private String apiReceiveMsg; + +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/dal/dataobject/sms/SmsTemplateDO.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/dal/dataobject/sms/SmsTemplateDO.java new file mode 100644 index 0000000..ba20c9f --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/dal/dataobject/sms/SmsTemplateDO.java @@ -0,0 +1,92 @@ +package com.tashow.cloud.system.dal.dataobject.sms; + +import com.tashow.cloud.systemapi.enums.sms.SmsTemplateTypeEnum; +import com.tashow.cloud.common.enums.CommonStatusEnum; +import com.tashow.cloud.mybatis.mybatis.core.dataobject.BaseDO; +import com.baomidou.mybatisplus.annotation.KeySequence; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler; +import com.tashow.cloud.systemapi.enums.sms.SmsTemplateTypeEnum; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; + +import java.util.List; + +/** + * 短信模板 DO + * + * @author zzf + * @since 2021-01-25 + */ +@TableName(value = "system_sms_template", autoResultMap = true) +@KeySequence("system_sms_template_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class SmsTemplateDO extends BaseDO { + + /** + * 自增编号 + */ + private Long id; + + // ========= 模板相关字段 ========= + + /** + * 短信类型 + * + * 枚举 {@link SmsTemplateTypeEnum} + */ + private Integer type; + /** + * 启用状态 + * + * 枚举 {@link CommonStatusEnum} + */ + private Integer status; + /** + * 模板编码,保证唯一 + */ + private String code; + /** + * 模板名称 + */ + private String name; + /** + * 模板内容 + * + * 内容的参数,使用 {} 包括,例如说 {name} + */ + private String content; + /** + * 参数数组(自动根据内容生成) + */ + @TableField(typeHandler = JacksonTypeHandler.class) + private List params; + /** + * 备注 + */ + private String remark; + /** + * 短信 API 的模板编号 + */ + private String apiTemplateId; + + // ========= 渠道相关字段 ========= + + /** + * 短信渠道编号 + * + * 关联 {@link SmsChannelDO#getId()} + */ + private Long channelId; + /** + * 短信渠道编码 + * + * 冗余 {@link SmsChannelDO#getCode()} + */ + private String channelCode; + +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/dal/dataobject/social/SocialClientDO.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/dal/dataobject/social/SocialClientDO.java new file mode 100644 index 0000000..34ec2a4 --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/dal/dataobject/social/SocialClientDO.java @@ -0,0 +1,77 @@ +package com.tashow.cloud.system.dal.dataobject.social; + +import com.tashow.cloud.common.enums.CommonStatusEnum; +import com.tashow.cloud.common.enums.UserTypeEnum; +import com.tashow.cloud.tenant.core.db.TenantBaseDO; +import com.tashow.cloud.systemapi.enums.social.SocialTypeEnum; +import com.baomidou.mybatisplus.annotation.KeySequence; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import com.tashow.cloud.systemapi.enums.social.SocialTypeEnum; +import com.xingyuv.jushauth.config.AuthConfig; +import lombok.*; + +/** + * 社交客户端 DO + * + * 对应 {@link AuthConfig} 配置,满足不同租户,有自己的客户端配置,实现社交(三方)登录 + * + * @author 芋道源码 + */ +@TableName(value = "system_social_client", autoResultMap = true) +@KeySequence("system_social_client_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 +@Data +@EqualsAndHashCode(callSuper = true) +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class SocialClientDO extends TenantBaseDO { + + /** + * 编号,自增 + */ + @TableId + private Long id; + /** + * 应用名 + */ + private String name; + /** + * 社交类型 + * + * 枚举 {@link SocialTypeEnum} + */ + private Integer socialType; + /** + * 用户类型 + * + * 目的:不同用户类型,对应不同的小程序,需要自己的配置 + * + * 枚举 {@link UserTypeEnum} + */ + private Integer userType; + /** + * 状态 + * + * 枚举 {@link CommonStatusEnum} + */ + private Integer status; + + /** + * 客户端 id + */ + private String clientId; + /** + * 客户端 Secret + */ + private String clientSecret; + + /** + * 代理编号 + * + * 目前只有部分“社交类型”在使用: + * 1. 企业微信:对应授权方的网页应用 ID + */ + private String agentId; + +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/dal/dataobject/social/SocialUserBindDO.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/dal/dataobject/social/SocialUserBindDO.java new file mode 100644 index 0000000..2791de9 --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/dal/dataobject/social/SocialUserBindDO.java @@ -0,0 +1,56 @@ +package com.tashow.cloud.system.dal.dataobject.social; + +import com.tashow.cloud.common.enums.UserTypeEnum; +import com.tashow.cloud.mybatis.mybatis.core.dataobject.BaseDO; +import com.baomidou.mybatisplus.annotation.KeySequence; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.*; + +/** + * 社交用户的绑定 + * 即 {@link SocialUserDO} 与 UserDO 的关联表 + * + * @author 芋道源码 + */ +@TableName(value = "system_social_user_bind", autoResultMap = true) +@KeySequence("system_social_user_bind_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 +@Data +@EqualsAndHashCode(callSuper = true) +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class SocialUserBindDO extends BaseDO { + + /** + * 编号 + */ + @TableId + private Long id; + /** + * 关联的用户编号 + * + * 关联 UserDO 的编号 + */ + private Long userId; + /** + * 用户类型 + * + * 枚举 {@link UserTypeEnum} + */ + private Integer userType; + + /** + * 社交平台的用户编号 + * + * 关联 {@link SocialUserDO#getId()} + */ + private Long socialUserId; + /** + * 社交平台的类型 + * + * 冗余 {@link SocialUserDO#getType()} + */ + private Integer socialType; + +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/dal/dataobject/social/SocialUserDO.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/dal/dataobject/social/SocialUserDO.java new file mode 100644 index 0000000..8c196cd --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/dal/dataobject/social/SocialUserDO.java @@ -0,0 +1,74 @@ +package com.tashow.cloud.system.dal.dataobject.social; + +import com.tashow.cloud.mybatis.mybatis.core.dataobject.BaseDO; +import com.tashow.cloud.systemapi.enums.social.SocialTypeEnum; +import com.baomidou.mybatisplus.annotation.KeySequence; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import com.tashow.cloud.systemapi.enums.social.SocialTypeEnum; +import lombok.*; + +/** + * 社交(三方)用户 + * + * @author weir + */ +@TableName(value = "system_social_user", autoResultMap = true) +@KeySequence("system_social_user_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 +@Data +@EqualsAndHashCode(callSuper = true) +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class SocialUserDO extends BaseDO { + + /** + * 自增主键 + */ + @TableId + private Long id; + /** + * 社交平台的类型 + * + * 枚举 {@link SocialTypeEnum} + */ + private Integer type; + + /** + * 社交 openid + */ + private String openid; + /** + * 社交 token + */ + private String token; + /** + * 原始 Token 数据,一般是 JSON 格式 + */ + private String rawTokenInfo; + + /** + * 用户昵称 + */ + private String nickname; + /** + * 用户头像 + */ + private String avatar; + /** + * 原始用户数据,一般是 JSON 格式 + */ + private String rawUserInfo; + + /** + * 最后一次的认证 code + */ + private String code; + /** + * 最后一次的认证 state + */ + private String state; + +} + + diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/dal/dataobject/tenant/TenantDO.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/dal/dataobject/tenant/TenantDO.java new file mode 100644 index 0000000..7e24dc7 --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/dal/dataobject/tenant/TenantDO.java @@ -0,0 +1,81 @@ +package com.tashow.cloud.system.dal.dataobject.tenant; + +import com.tashow.cloud.common.enums.CommonStatusEnum; +import com.tashow.cloud.mybatis.mybatis.core.dataobject.BaseDO; +import com.tashow.cloud.system.dal.dataobject.user.AdminUserDO; +import com.baomidou.mybatisplus.annotation.KeySequence; +import com.baomidou.mybatisplus.annotation.TableName; +import com.tashow.cloud.system.dal.dataobject.user.AdminUserDO; +import lombok.*; + +import java.time.LocalDateTime; + +/** + * 租户 DO + * + * @author 芋道源码 + */ +@TableName(value = "system_tenant", autoResultMap = true) +@KeySequence("system_tenant_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +@Builder +@AllArgsConstructor +@NoArgsConstructor +public class TenantDO extends BaseDO { + + /** + * 套餐编号 - 系统 + */ + public static final Long PACKAGE_ID_SYSTEM = 0L; + + /** + * 租户编号,自增 + */ + private Long id; + /** + * 租户名,唯一 + */ + private String name; + /** + * 联系人的用户编号 + * + * 关联 {@link AdminUserDO#getId()} + */ + private Long contactUserId; + /** + * 联系人 + */ + private String contactName; + /** + * 联系手机 + */ + private String contactMobile; + /** + * 租户状态 + * + * 枚举 {@link CommonStatusEnum} + */ + private Integer status; + /** + * 绑定域名 + */ + private String website; + /** + * 租户套餐编号 + * + * 关联 {@link TenantPackageDO#getId()} + * 特殊逻辑:系统内置租户,不使用套餐,暂时使用 {@link #PACKAGE_ID_SYSTEM} 标识 + */ + private Long packageId; + /** + * 过期时间 + */ + private LocalDateTime expireTime; + /** + * 账号数量 + */ + private Integer accountCount; + +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/dal/dataobject/tenant/TenantPackageDO.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/dal/dataobject/tenant/TenantPackageDO.java new file mode 100644 index 0000000..b4bd30c --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/dal/dataobject/tenant/TenantPackageDO.java @@ -0,0 +1,52 @@ +package com.tashow.cloud.system.dal.dataobject.tenant; + +import com.tashow.cloud.common.enums.CommonStatusEnum; +import com.tashow.cloud.mybatis.mybatis.core.dataobject.BaseDO; +import com.baomidou.mybatisplus.annotation.KeySequence; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler; +import lombok.*; + +import java.util.Set; + +/** + * 租户套餐 DO + * + * @author 芋道源码 + */ +@TableName(value = "system_tenant_package", autoResultMap = true) +@KeySequence("system_tenant_package_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +@Builder +@AllArgsConstructor +@NoArgsConstructor +public class TenantPackageDO extends BaseDO { + + /** + * 套餐编号,自增 + */ + private Long id; + /** + * 套餐名,唯一 + */ + private String name; + /** + * 租户套餐状态 + * + * 枚举 {@link CommonStatusEnum} + */ + private Integer status; + /** + * 备注 + */ + private String remark; + /** + * 关联的菜单编号 + */ + @TableField(typeHandler = JacksonTypeHandler.class) + private Set menuIds; + +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/dal/dataobject/user/AdminUserDO.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/dal/dataobject/user/AdminUserDO.java new file mode 100644 index 0000000..5c3d41a --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/dal/dataobject/user/AdminUserDO.java @@ -0,0 +1,97 @@ +package com.tashow.cloud.system.dal.dataobject.user; + +import com.tashow.cloud.common.enums.CommonStatusEnum; +import com.tashow.cloud.tenant.core.db.TenantBaseDO; +import com.tashow.cloud.systemapi.enums.common.SexEnum; +import com.baomidou.mybatisplus.annotation.KeySequence; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler; +import com.tashow.cloud.systemapi.enums.common.SexEnum; +import lombok.*; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; + +import java.time.LocalDateTime; +import java.util.Set; + +/** + * 管理后台的用户 DO + * + * @author 芋道源码 + */ +@TableName(value = "system_users", autoResultMap = true) // 由于 SQL Server 的 system_user 是关键字,所以使用 system_users +@KeySequence("system_users_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 +@Data +@EqualsAndHashCode(callSuper = true) +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class AdminUserDO extends TenantBaseDO { + + /** + * 用户ID + */ + @TableId + private Long id; + /** + * 用户账号 + */ + private String username; + /** + * 加密后的密码 + * + * 因为目前使用 {@link BCryptPasswordEncoder} 加密器,所以无需自己处理 salt 盐 + */ + private String password; + /** + * 用户昵称 + */ + private String nickname; + /** + * 备注 + */ + private String remark; + /** + * 部门 ID + */ + private Long deptId; + /** + * 岗位编号数组 + */ + @TableField(typeHandler = JacksonTypeHandler.class) + private Set postIds; + /** + * 用户邮箱 + */ + private String email; + /** + * 手机号码 + */ + private String mobile; + /** + * 用户性别 + * + * 枚举类 {@link SexEnum} + */ + private Integer sex; + /** + * 用户头像 + */ + private String avatar; + /** + * 帐号状态 + * + * 枚举 {@link CommonStatusEnum} + */ + private Integer status; + /** + * 最后登录IP + */ + private String loginIp; + /** + * 最后登录时间 + */ + private LocalDateTime loginDate; + +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/dal/mysql/dept/DeptMapper.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/dal/mysql/dept/DeptMapper.java new file mode 100644 index 0000000..c3b2142 --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/dal/mysql/dept/DeptMapper.java @@ -0,0 +1,37 @@ +package com.tashow.cloud.system.dal.mysql.dept; + +import com.tashow.cloud.mybatis.mybatis.core.mapper.BaseMapperX; +import com.tashow.cloud.mybatis.mybatis.core.query.LambdaQueryWrapperX; +import com.tashow.cloud.system.controller.admin.dept.vo.dept.DeptListReqVO; +import com.tashow.cloud.system.dal.dataobject.dept.DeptDO; +import org.apache.ibatis.annotations.Mapper; + +import java.util.Collection; +import java.util.List; + +@Mapper +public interface DeptMapper extends BaseMapperX { + + default List selectList(DeptListReqVO reqVO) { + return selectList(new LambdaQueryWrapperX() + .likeIfPresent(DeptDO::getName, reqVO.getName()) + .eqIfPresent(DeptDO::getStatus, reqVO.getStatus())); + } + + default DeptDO selectByParentIdAndName(Long parentId, String name) { + return selectOne(DeptDO::getParentId, parentId, DeptDO::getName, name); + } + + default Long selectCountByParentId(Long parentId) { + return selectCount(DeptDO::getParentId, parentId); + } + + default List selectListByParentId(Collection parentIds) { + return selectList(DeptDO::getParentId, parentIds); + } + + default List selectListByLeaderUserId(Long id) { + return selectList(DeptDO::getLeaderUserId, id); + } + +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/dal/mysql/dept/PostMapper.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/dal/mysql/dept/PostMapper.java new file mode 100644 index 0000000..88e350f --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/dal/mysql/dept/PostMapper.java @@ -0,0 +1,38 @@ +package com.tashow.cloud.system.dal.mysql.dept; + +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.mybatis.mybatis.core.mapper.BaseMapperX; +import com.tashow.cloud.mybatis.mybatis.core.query.LambdaQueryWrapperX; +import com.tashow.cloud.system.controller.admin.dept.vo.post.PostPageReqVO; +import com.tashow.cloud.system.dal.dataobject.dept.PostDO; +import org.apache.ibatis.annotations.Mapper; + +import java.util.Collection; +import java.util.List; + +@Mapper +public interface PostMapper extends BaseMapperX { + + default List selectList(Collection ids, Collection statuses) { + return selectList(new LambdaQueryWrapperX() + .inIfPresent(PostDO::getId, ids) + .inIfPresent(PostDO::getStatus, statuses)); + } + + default PageResult selectPage(PostPageReqVO reqVO) { + return selectPage(reqVO, new LambdaQueryWrapperX() + .likeIfPresent(PostDO::getCode, reqVO.getCode()) + .likeIfPresent(PostDO::getName, reqVO.getName()) + .eqIfPresent(PostDO::getStatus, reqVO.getStatus()) + .orderByDesc(PostDO::getId)); + } + + default PostDO selectByName(String name) { + return selectOne(PostDO::getName, name); + } + + default PostDO selectByCode(String code) { + return selectOne(PostDO::getCode, code); + } + +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/dal/mysql/dept/UserPostMapper.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/dal/mysql/dept/UserPostMapper.java new file mode 100644 index 0000000..b77b834 --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/dal/mysql/dept/UserPostMapper.java @@ -0,0 +1,32 @@ +package com.tashow.cloud.system.dal.mysql.dept; + +import com.tashow.cloud.mybatis.mybatis.core.mapper.BaseMapperX; +import com.tashow.cloud.mybatis.mybatis.core.query.LambdaQueryWrapperX; +import com.tashow.cloud.system.dal.dataobject.dept.UserPostDO; +import com.baomidou.mybatisplus.core.toolkit.Wrappers; +import org.apache.ibatis.annotations.Mapper; + +import java.util.Collection; +import java.util.List; + +@Mapper +public interface UserPostMapper extends BaseMapperX { + + default List selectListByUserId(Long userId) { + return selectList(UserPostDO::getUserId, userId); + } + + default void deleteByUserIdAndPostId(Long userId, Collection postIds) { + delete(new LambdaQueryWrapperX() + .eq(UserPostDO::getUserId, userId) + .in(UserPostDO::getPostId, postIds)); + } + + default List selectListByPostIds(Collection postIds) { + return selectList(UserPostDO::getPostId, postIds); + } + + default void deleteByUserId(Long userId) { + delete(Wrappers.lambdaUpdate(UserPostDO.class).eq(UserPostDO::getUserId, userId)); + } +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/dal/mysql/dict/DictDataMapper.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/dal/mysql/dict/DictDataMapper.java new file mode 100644 index 0000000..3e7b818 --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/dal/mysql/dict/DictDataMapper.java @@ -0,0 +1,51 @@ +package com.tashow.cloud.system.dal.mysql.dict; + +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.mybatis.mybatis.core.mapper.BaseMapperX; +import com.tashow.cloud.mybatis.mybatis.core.query.LambdaQueryWrapperX; +import com.tashow.cloud.system.controller.admin.dict.vo.data.DictDataPageReqVO; +import com.tashow.cloud.system.dal.dataobject.dict.DictDataDO; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.tashow.cloud.system.controller.admin.dict.vo.data.DictDataPageReqVO; +import com.tashow.cloud.system.dal.dataobject.dict.DictDataDO; +import org.apache.ibatis.annotations.Mapper; + +import java.util.Arrays; +import java.util.Collection; +import java.util.List; + +@Mapper +public interface DictDataMapper extends BaseMapperX { + + default DictDataDO selectByDictTypeAndValue(String dictType, String value) { + return selectOne(DictDataDO::getDictType, dictType, DictDataDO::getValue, value); + } + + default DictDataDO selectByDictTypeAndLabel(String dictType, String label) { + return selectOne(DictDataDO::getDictType, dictType, DictDataDO::getLabel, label); + } + + default List selectByDictTypeAndValues(String dictType, Collection values) { + return selectList(new LambdaQueryWrapper().eq(DictDataDO::getDictType, dictType) + .in(DictDataDO::getValue, values)); + } + + default long selectCountByDictType(String dictType) { + return selectCount(DictDataDO::getDictType, dictType); + } + + default PageResult selectPage(DictDataPageReqVO reqVO) { + return selectPage(reqVO, new LambdaQueryWrapperX() + .likeIfPresent(DictDataDO::getLabel, reqVO.getLabel()) + .eqIfPresent(DictDataDO::getDictType, reqVO.getDictType()) + .eqIfPresent(DictDataDO::getStatus, reqVO.getStatus()) + .orderByDesc(Arrays.asList(DictDataDO::getDictType, DictDataDO::getSort))); + } + + default List selectListByStatusAndDictType(Integer status, String dictType) { + return selectList(new LambdaQueryWrapperX() + .eqIfPresent(DictDataDO::getStatus, status) + .eqIfPresent(DictDataDO::getDictType, dictType)); + } + +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/dal/mysql/dict/DictTypeMapper.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/dal/mysql/dict/DictTypeMapper.java new file mode 100644 index 0000000..88344bd --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/dal/mysql/dict/DictTypeMapper.java @@ -0,0 +1,37 @@ +package com.tashow.cloud.system.dal.mysql.dict; + +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.mybatis.mybatis.core.mapper.BaseMapperX; +import com.tashow.cloud.mybatis.mybatis.core.query.LambdaQueryWrapperX; +import com.tashow.cloud.system.controller.admin.dict.vo.type.DictTypePageReqVO; +import com.tashow.cloud.system.dal.dataobject.dict.DictTypeDO; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; +import org.apache.ibatis.annotations.Update; + +import java.time.LocalDateTime; + +@Mapper +public interface DictTypeMapper extends BaseMapperX { + + default PageResult selectPage(DictTypePageReqVO reqVO) { + return selectPage(reqVO, new LambdaQueryWrapperX() + .likeIfPresent(DictTypeDO::getName, reqVO.getName()) + .likeIfPresent(DictTypeDO::getType, reqVO.getType()) + .eqIfPresent(DictTypeDO::getStatus, reqVO.getStatus()) + .betweenIfPresent(DictTypeDO::getCreateTime, reqVO.getCreateTime()) + .orderByDesc(DictTypeDO::getId)); + } + + default DictTypeDO selectByType(String type) { + return selectOne(DictTypeDO::getType, type); + } + + default DictTypeDO selectByName(String name) { + return selectOne(DictTypeDO::getName, name); + } + + @Update("UPDATE system_dict_type SET deleted = 1, deleted_time = #{deletedTime} WHERE id = #{id}") + void updateToDelete(@Param("id") Long id, @Param("deletedTime") LocalDateTime deletedTime); + +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/dal/mysql/logger/LoginLogMapper.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/dal/mysql/logger/LoginLogMapper.java new file mode 100644 index 0000000..1e1502c --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/dal/mysql/logger/LoginLogMapper.java @@ -0,0 +1,31 @@ +package com.tashow.cloud.system.dal.mysql.logger; + +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.mybatis.mybatis.core.mapper.BaseMapperX; +import com.tashow.cloud.mybatis.mybatis.core.query.LambdaQueryWrapperX; +import com.tashow.cloud.system.controller.admin.logger.vo.loginlog.LoginLogPageReqVO; +import com.tashow.cloud.system.dal.dataobject.logger.LoginLogDO; +import com.tashow.cloud.systemapi.enums.logger.LoginResultEnum; +import com.tashow.cloud.system.controller.admin.logger.vo.loginlog.LoginLogPageReqVO; +import com.tashow.cloud.system.dal.dataobject.logger.LoginLogDO; +import com.tashow.cloud.systemapi.enums.logger.LoginResultEnum; +import org.apache.ibatis.annotations.Mapper; + +@Mapper +public interface LoginLogMapper extends BaseMapperX { + + default PageResult selectPage(LoginLogPageReqVO reqVO) { + LambdaQueryWrapperX query = new LambdaQueryWrapperX() + .likeIfPresent(LoginLogDO::getUserIp, reqVO.getUserIp()) + .likeIfPresent(LoginLogDO::getUsername, reqVO.getUsername()) + .betweenIfPresent(LoginLogDO::getCreateTime, reqVO.getCreateTime()); + if (Boolean.TRUE.equals(reqVO.getStatus())) { + query.eq(LoginLogDO::getResult, LoginResultEnum.SUCCESS.getResult()); + } else if (Boolean.FALSE.equals(reqVO.getStatus())) { + query.gt(LoginLogDO::getResult, LoginResultEnum.SUCCESS.getResult()); + } + query.orderByDesc(LoginLogDO::getId); // 降序 + return selectPage(reqVO, query); + } + +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/dal/mysql/logger/OperateLogMapper.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/dal/mysql/logger/OperateLogMapper.java new file mode 100644 index 0000000..efa2599 --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/dal/mysql/logger/OperateLogMapper.java @@ -0,0 +1,36 @@ +package com.tashow.cloud.system.dal.mysql.logger; + +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.mybatis.mybatis.core.mapper.BaseMapperX; +import com.tashow.cloud.mybatis.mybatis.core.query.LambdaQueryWrapperX; +import com.tashow.cloud.systemapi.api.logger.dto.OperateLogPageReqDTO; +import com.tashow.cloud.system.controller.admin.logger.vo.operatelog.OperateLogPageReqVO; +import com.tashow.cloud.system.dal.dataobject.logger.OperateLogDO; +import com.tashow.cloud.systemapi.api.logger.dto.OperateLogPageReqDTO; +import com.tashow.cloud.system.controller.admin.logger.vo.operatelog.OperateLogPageReqVO; +import com.tashow.cloud.system.dal.dataobject.logger.OperateLogDO; +import org.apache.ibatis.annotations.Mapper; + +@Mapper +public interface OperateLogMapper extends BaseMapperX { + + default PageResult selectPage(OperateLogPageReqVO pageReqDTO) { + return selectPage(pageReqDTO, new LambdaQueryWrapperX() + .eqIfPresent(OperateLogDO::getUserId, pageReqDTO.getUserId()) + .eqIfPresent(OperateLogDO::getBizId, pageReqDTO.getBizId()) + .likeIfPresent(OperateLogDO::getType, pageReqDTO.getType()) + .likeIfPresent(OperateLogDO::getSubType, pageReqDTO.getSubType()) + .likeIfPresent(OperateLogDO::getAction, pageReqDTO.getAction()) + .betweenIfPresent(OperateLogDO::getCreateTime, pageReqDTO.getCreateTime()) + .orderByDesc(OperateLogDO::getId)); + } + + default PageResult selectPage(OperateLogPageReqDTO pageReqDTO) { + return selectPage(pageReqDTO, new LambdaQueryWrapperX() + .eqIfPresent(OperateLogDO::getType, pageReqDTO.getType()) + .eqIfPresent(OperateLogDO::getBizId, pageReqDTO.getBizId()) + .eqIfPresent(OperateLogDO::getUserId, pageReqDTO.getUserId()) + .orderByDesc(OperateLogDO::getId)); + } + +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/dal/mysql/mail/MailAccountMapper.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/dal/mysql/mail/MailAccountMapper.java new file mode 100644 index 0000000..3fcaf06 --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/dal/mysql/mail/MailAccountMapper.java @@ -0,0 +1,21 @@ +package com.tashow.cloud.system.dal.mysql.mail; + +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.mybatis.mybatis.core.mapper.BaseMapperX; +import com.tashow.cloud.mybatis.mybatis.core.query.LambdaQueryWrapperX; +import com.tashow.cloud.system.controller.admin.mail.vo.account.MailAccountPageReqVO; +import com.tashow.cloud.system.dal.dataobject.mail.MailAccountDO; +import com.tashow.cloud.system.controller.admin.mail.vo.account.MailAccountPageReqVO; +import com.tashow.cloud.system.dal.dataobject.mail.MailAccountDO; +import org.apache.ibatis.annotations.Mapper; + +@Mapper +public interface MailAccountMapper extends BaseMapperX { + + default PageResult selectPage(MailAccountPageReqVO pageReqVO) { + return selectPage(pageReqVO, new LambdaQueryWrapperX() + .likeIfPresent(MailAccountDO::getMail, pageReqVO.getMail()) + .likeIfPresent(MailAccountDO::getUsername , pageReqVO.getUsername())); + } + +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/dal/mysql/mail/MailLogMapper.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/dal/mysql/mail/MailLogMapper.java new file mode 100644 index 0000000..9d883e5 --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/dal/mysql/mail/MailLogMapper.java @@ -0,0 +1,27 @@ +package com.tashow.cloud.system.dal.mysql.mail; + +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.mybatis.mybatis.core.mapper.BaseMapperX; +import com.tashow.cloud.mybatis.mybatis.core.query.LambdaQueryWrapperX; +import com.tashow.cloud.system.controller.admin.mail.vo.log.MailLogPageReqVO; +import com.tashow.cloud.system.dal.dataobject.mail.MailLogDO; +import com.tashow.cloud.system.controller.admin.mail.vo.log.MailLogPageReqVO; +import com.tashow.cloud.system.dal.dataobject.mail.MailLogDO; +import org.apache.ibatis.annotations.Mapper; + +@Mapper +public interface MailLogMapper extends BaseMapperX { + + default PageResult selectPage(MailLogPageReqVO reqVO) { + return selectPage(reqVO, new LambdaQueryWrapperX() + .eqIfPresent(MailLogDO::getUserId, reqVO.getUserId()) + .eqIfPresent(MailLogDO::getUserType, reqVO.getUserType()) + .likeIfPresent(MailLogDO::getToMail, reqVO.getToMail()) + .eqIfPresent(MailLogDO::getAccountId, reqVO.getAccountId()) + .eqIfPresent(MailLogDO::getTemplateId, reqVO.getTemplateId()) + .eqIfPresent(MailLogDO::getSendStatus, reqVO.getSendStatus()) + .betweenIfPresent(MailLogDO::getSendTime, reqVO.getSendTime()) + .orderByDesc(MailLogDO::getId)); + } + +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/dal/mysql/mail/MailTemplateMapper.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/dal/mysql/mail/MailTemplateMapper.java new file mode 100644 index 0000000..6cca078 --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/dal/mysql/mail/MailTemplateMapper.java @@ -0,0 +1,32 @@ +package com.tashow.cloud.system.dal.mysql.mail; + +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.mybatis.mybatis.core.mapper.BaseMapperX; +import com.tashow.cloud.mybatis.mybatis.core.query.LambdaQueryWrapperX; +import com.tashow.cloud.system.controller.admin.mail.vo.template.MailTemplatePageReqVO; +import com.tashow.cloud.system.dal.dataobject.mail.MailTemplateDO; +import com.tashow.cloud.system.controller.admin.mail.vo.template.MailTemplatePageReqVO; +import com.tashow.cloud.system.dal.dataobject.mail.MailTemplateDO; +import org.apache.ibatis.annotations.Mapper; + +@Mapper +public interface MailTemplateMapper extends BaseMapperX { + + default PageResult selectPage(MailTemplatePageReqVO pageReqVO){ + return selectPage(pageReqVO , new LambdaQueryWrapperX() + .eqIfPresent(MailTemplateDO::getStatus, pageReqVO.getStatus()) + .likeIfPresent(MailTemplateDO::getCode, pageReqVO.getCode()) + .likeIfPresent(MailTemplateDO::getName, pageReqVO.getName()) + .eqIfPresent(MailTemplateDO::getAccountId, pageReqVO.getAccountId()) + .betweenIfPresent(MailTemplateDO::getCreateTime, pageReqVO.getCreateTime())); + } + + default Long selectCountByAccountId(Long accountId) { + return selectCount(MailTemplateDO::getAccountId, accountId); + } + + default MailTemplateDO selectByCode(String code) { + return selectOne(MailTemplateDO::getCode, code); + } + +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/dal/mysql/notice/NoticeMapper.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/dal/mysql/notice/NoticeMapper.java new file mode 100644 index 0000000..4e34e92 --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/dal/mysql/notice/NoticeMapper.java @@ -0,0 +1,22 @@ +package com.tashow.cloud.system.dal.mysql.notice; + +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.mybatis.mybatis.core.mapper.BaseMapperX; +import com.tashow.cloud.mybatis.mybatis.core.query.LambdaQueryWrapperX; +import com.tashow.cloud.system.controller.admin.notice.vo.NoticePageReqVO; +import com.tashow.cloud.system.dal.dataobject.notice.NoticeDO; +import com.tashow.cloud.system.controller.admin.notice.vo.NoticePageReqVO; +import com.tashow.cloud.system.dal.dataobject.notice.NoticeDO; +import org.apache.ibatis.annotations.Mapper; + +@Mapper +public interface NoticeMapper extends BaseMapperX { + + default PageResult selectPage(NoticePageReqVO reqVO) { + return selectPage(reqVO, new LambdaQueryWrapperX() + .likeIfPresent(NoticeDO::getTitle, reqVO.getTitle()) + .eqIfPresent(NoticeDO::getStatus, reqVO.getStatus()) + .orderByDesc(NoticeDO::getId)); + } + +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/dal/mysql/notify/NotifyMessageMapper.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/dal/mysql/notify/NotifyMessageMapper.java new file mode 100644 index 0000000..610b5ac --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/dal/mysql/notify/NotifyMessageMapper.java @@ -0,0 +1,70 @@ +package com.tashow.cloud.system.dal.mysql.notify; + +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.mybatis.mybatis.core.mapper.BaseMapperX; +import com.tashow.cloud.mybatis.mybatis.core.query.LambdaQueryWrapperX; +import com.tashow.cloud.mybatis.mybatis.core.query.QueryWrapperX; +import com.tashow.cloud.system.controller.admin.notify.vo.message.NotifyMessageMyPageReqVO; +import com.tashow.cloud.system.controller.admin.notify.vo.message.NotifyMessagePageReqVO; +import com.tashow.cloud.system.dal.dataobject.notify.NotifyMessageDO; +import org.apache.ibatis.annotations.Mapper; + +import java.time.LocalDateTime; +import java.util.Collection; +import java.util.List; + +@Mapper +public interface NotifyMessageMapper extends BaseMapperX { + + default PageResult selectPage(NotifyMessagePageReqVO reqVO) { + return selectPage(reqVO, new LambdaQueryWrapperX() + .eqIfPresent(NotifyMessageDO::getUserId, reqVO.getUserId()) + .eqIfPresent(NotifyMessageDO::getUserType, reqVO.getUserType()) + .likeIfPresent(NotifyMessageDO::getTemplateCode, reqVO.getTemplateCode()) + .eqIfPresent(NotifyMessageDO::getTemplateType, reqVO.getTemplateType()) + .betweenIfPresent(NotifyMessageDO::getCreateTime, reqVO.getCreateTime()) + .orderByDesc(NotifyMessageDO::getId)); + } + + default PageResult selectPage(NotifyMessageMyPageReqVO reqVO, Long userId, Integer userType) { + return selectPage(reqVO, new LambdaQueryWrapperX() + .eqIfPresent(NotifyMessageDO::getReadStatus, reqVO.getReadStatus()) + .betweenIfPresent(NotifyMessageDO::getCreateTime, reqVO.getCreateTime()) + .eq(NotifyMessageDO::getUserId, userId) + .eq(NotifyMessageDO::getUserType, userType) + .orderByDesc(NotifyMessageDO::getId)); + } + + default int updateListRead(Collection ids, Long userId, Integer userType) { + return update(new NotifyMessageDO().setReadStatus(true).setReadTime(LocalDateTime.now()), + new LambdaQueryWrapperX() + .in(NotifyMessageDO::getId, ids) + .eq(NotifyMessageDO::getUserId, userId) + .eq(NotifyMessageDO::getUserType, userType) + .eq(NotifyMessageDO::getReadStatus, false)); + } + + default int updateListRead(Long userId, Integer userType) { + return update(new NotifyMessageDO().setReadStatus(true).setReadTime(LocalDateTime.now()), + new LambdaQueryWrapperX() + .eq(NotifyMessageDO::getUserId, userId) + .eq(NotifyMessageDO::getUserType, userType) + .eq(NotifyMessageDO::getReadStatus, false)); + } + + default List selectUnreadListByUserIdAndUserType(Long userId, Integer userType, Integer size) { + return selectList(new QueryWrapperX() // 由于要使用 limitN 语句,所以只能用 QueryWrapperX + .eq("user_id", userId) + .eq("user_type", userType) + .eq("read_status", false) + .orderByDesc("id").limitN(size)); + } + + default Long selectUnreadCountByUserIdAndUserType(Long userId, Integer userType) { + return selectCount(new LambdaQueryWrapperX() + .eq(NotifyMessageDO::getReadStatus, false) + .eq(NotifyMessageDO::getUserId, userId) + .eq(NotifyMessageDO::getUserType, userType)); + } + +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/dal/mysql/notify/NotifyTemplateMapper.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/dal/mysql/notify/NotifyTemplateMapper.java new file mode 100644 index 0000000..05f102c --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/dal/mysql/notify/NotifyTemplateMapper.java @@ -0,0 +1,28 @@ +package com.tashow.cloud.system.dal.mysql.notify; + +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.mybatis.mybatis.core.mapper.BaseMapperX; +import com.tashow.cloud.mybatis.mybatis.core.query.LambdaQueryWrapperX; +import com.tashow.cloud.system.controller.admin.notify.vo.template.NotifyTemplatePageReqVO; +import com.tashow.cloud.system.dal.dataobject.notify.NotifyTemplateDO; +import com.tashow.cloud.system.controller.admin.notify.vo.template.NotifyTemplatePageReqVO; +import com.tashow.cloud.system.dal.dataobject.notify.NotifyTemplateDO; +import org.apache.ibatis.annotations.Mapper; + +@Mapper +public interface NotifyTemplateMapper extends BaseMapperX { + + default NotifyTemplateDO selectByCode(String code) { + return selectOne(NotifyTemplateDO::getCode, code); + } + + default PageResult selectPage(NotifyTemplatePageReqVO reqVO) { + return selectPage(reqVO, new LambdaQueryWrapperX() + .likeIfPresent(NotifyTemplateDO::getCode, reqVO.getCode()) + .likeIfPresent(NotifyTemplateDO::getName, reqVO.getName()) + .eqIfPresent(NotifyTemplateDO::getStatus, reqVO.getStatus()) + .betweenIfPresent(NotifyTemplateDO::getCreateTime, reqVO.getCreateTime()) + .orderByDesc(NotifyTemplateDO::getId)); + } + +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/dal/mysql/oauth2/OAuth2AccessTokenMapper.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/dal/mysql/oauth2/OAuth2AccessTokenMapper.java new file mode 100644 index 0000000..f25f315 --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/dal/mysql/oauth2/OAuth2AccessTokenMapper.java @@ -0,0 +1,35 @@ +package com.tashow.cloud.system.dal.mysql.oauth2; + +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.mybatis.mybatis.core.mapper.BaseMapperX; +import com.tashow.cloud.mybatis.mybatis.core.query.LambdaQueryWrapperX; +import com.tashow.cloud.system.controller.admin.oauth2.vo.token.OAuth2AccessTokenPageReqVO; +import com.tashow.cloud.system.dal.dataobject.oauth2.OAuth2AccessTokenDO; +import com.tashow.cloud.tenant.core.aop.TenantIgnore; +import org.apache.ibatis.annotations.Mapper; + +import java.time.LocalDateTime; +import java.util.List; + +@Mapper +public interface OAuth2AccessTokenMapper extends BaseMapperX { + + @TenantIgnore // 获取 token 的时候,需要忽略租户编号。原因是:一些场景下,可能不会传递 tenant-id 请求头,例如说文件上传、积木报表等等 + default OAuth2AccessTokenDO selectByAccessToken(String accessToken) { + return selectOne(OAuth2AccessTokenDO::getAccessToken, accessToken); + } + + default List selectListByRefreshToken(String refreshToken) { + return selectList(OAuth2AccessTokenDO::getRefreshToken, refreshToken); + } + + default PageResult selectPage(OAuth2AccessTokenPageReqVO reqVO) { + return selectPage(reqVO, new LambdaQueryWrapperX() + .eqIfPresent(OAuth2AccessTokenDO::getUserId, reqVO.getUserId()) + .eqIfPresent(OAuth2AccessTokenDO::getUserType, reqVO.getUserType()) + .likeIfPresent(OAuth2AccessTokenDO::getClientId, reqVO.getClientId()) + .gt(OAuth2AccessTokenDO::getExpiresTime, LocalDateTime.now()) + .orderByDesc(OAuth2AccessTokenDO::getId)); + } + +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/dal/mysql/oauth2/OAuth2ApproveMapper.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/dal/mysql/oauth2/OAuth2ApproveMapper.java new file mode 100644 index 0000000..ff9c075 --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/dal/mysql/oauth2/OAuth2ApproveMapper.java @@ -0,0 +1,29 @@ +package com.tashow.cloud.system.dal.mysql.oauth2; + +import com.tashow.cloud.mybatis.mybatis.core.mapper.BaseMapperX; +import com.tashow.cloud.mybatis.mybatis.core.query.LambdaQueryWrapperX; +import com.tashow.cloud.system.dal.dataobject.oauth2.OAuth2ApproveDO; +import com.tashow.cloud.system.dal.dataobject.oauth2.OAuth2ApproveDO; +import org.apache.ibatis.annotations.Mapper; + +import java.util.List; + +@Mapper +public interface OAuth2ApproveMapper extends BaseMapperX { + + default int update(OAuth2ApproveDO updateObj) { + return update(updateObj, new LambdaQueryWrapperX() + .eq(OAuth2ApproveDO::getUserId, updateObj.getUserId()) + .eq(OAuth2ApproveDO::getUserType, updateObj.getUserType()) + .eq(OAuth2ApproveDO::getClientId, updateObj.getClientId()) + .eq(OAuth2ApproveDO::getScope, updateObj.getScope())); + } + + default List selectListByUserIdAndUserTypeAndClientId(Long userId, Integer userType, String clientId) { + return selectList(new LambdaQueryWrapperX() + .eq(OAuth2ApproveDO::getUserId, userId) + .eq(OAuth2ApproveDO::getUserType, userType) + .eq(OAuth2ApproveDO::getClientId, clientId)); + } + +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/dal/mysql/oauth2/OAuth2ClientMapper.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/dal/mysql/oauth2/OAuth2ClientMapper.java new file mode 100644 index 0000000..888be56 --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/dal/mysql/oauth2/OAuth2ClientMapper.java @@ -0,0 +1,32 @@ +package com.tashow.cloud.system.dal.mysql.oauth2; + +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.mybatis.mybatis.core.mapper.BaseMapperX; +import com.tashow.cloud.mybatis.mybatis.core.query.LambdaQueryWrapperX; +import com.tashow.cloud.system.controller.admin.oauth2.vo.client.OAuth2ClientPageReqVO; +import com.tashow.cloud.system.dal.dataobject.oauth2.OAuth2ClientDO; +import com.tashow.cloud.system.controller.admin.oauth2.vo.client.OAuth2ClientPageReqVO; +import com.tashow.cloud.system.dal.dataobject.oauth2.OAuth2ClientDO; +import org.apache.ibatis.annotations.Mapper; + + +/** + * OAuth2 客户端 Mapper + * + * @author 芋道源码 + */ +@Mapper +public interface OAuth2ClientMapper extends BaseMapperX { + + default PageResult selectPage(OAuth2ClientPageReqVO reqVO) { + return selectPage(reqVO, new LambdaQueryWrapperX() + .likeIfPresent(OAuth2ClientDO::getName, reqVO.getName()) + .eqIfPresent(OAuth2ClientDO::getStatus, reqVO.getStatus()) + .orderByDesc(OAuth2ClientDO::getId)); + } + + default OAuth2ClientDO selectByClientId(String clientId) { + return selectOne(OAuth2ClientDO::getClientId, clientId); + } + +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/dal/mysql/oauth2/OAuth2CodeMapper.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/dal/mysql/oauth2/OAuth2CodeMapper.java new file mode 100644 index 0000000..3a72f70 --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/dal/mysql/oauth2/OAuth2CodeMapper.java @@ -0,0 +1,15 @@ +package com.tashow.cloud.system.dal.mysql.oauth2; + +import com.tashow.cloud.mybatis.mybatis.core.mapper.BaseMapperX; +import com.tashow.cloud.system.dal.dataobject.oauth2.OAuth2CodeDO; +import com.tashow.cloud.system.dal.dataobject.oauth2.OAuth2CodeDO; +import org.apache.ibatis.annotations.Mapper; + +@Mapper +public interface OAuth2CodeMapper extends BaseMapperX { + + default OAuth2CodeDO selectByCode(String code) { + return selectOne(OAuth2CodeDO::getCode, code); + } + +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/dal/mysql/oauth2/OAuth2RefreshTokenMapper.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/dal/mysql/oauth2/OAuth2RefreshTokenMapper.java new file mode 100644 index 0000000..037853b --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/dal/mysql/oauth2/OAuth2RefreshTokenMapper.java @@ -0,0 +1,22 @@ +package com.tashow.cloud.system.dal.mysql.oauth2; + +import com.tashow.cloud.mybatis.mybatis.core.mapper.BaseMapperX; +import com.tashow.cloud.mybatis.mybatis.core.query.LambdaQueryWrapperX; +import com.tashow.cloud.system.dal.dataobject.oauth2.OAuth2RefreshTokenDO; +import com.tashow.cloud.tenant.core.aop.TenantIgnore; +import org.apache.ibatis.annotations.Mapper; + +@Mapper +public interface OAuth2RefreshTokenMapper extends BaseMapperX { + + default int deleteByRefreshToken(String refreshToken) { + return delete(new LambdaQueryWrapperX() + .eq(OAuth2RefreshTokenDO::getRefreshToken, refreshToken)); + } + + @TenantIgnore // 获取 token 的时候,需要忽略租户编号。原因是:一些场景下,可能不会传递 tenant-id 请求头,例如说文件上传、积木报表等等 + default OAuth2RefreshTokenDO selectByRefreshToken(String refreshToken) { + return selectOne(OAuth2RefreshTokenDO::getRefreshToken, refreshToken); + } + +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/dal/mysql/package-info.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/dal/mysql/package-info.java new file mode 100644 index 0000000..5a3dc21 --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/dal/mysql/package-info.java @@ -0,0 +1,9 @@ +/** + * DAL = Data Access Layer 数据访问层 + * 1. data object:数据对象 + * 2. redis:Redis 的 CRUD 操作 + * 3. mysql:MySQL 的 CRUD 操作 + * + * 其中,MySQL 的表以 system_ 作为前缀 + */ +package com.tashow.cloud.system.dal.mysql; diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/dal/mysql/permission/MenuMapper.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/dal/mysql/permission/MenuMapper.java new file mode 100644 index 0000000..a4a55a6 --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/dal/mysql/permission/MenuMapper.java @@ -0,0 +1,33 @@ +package com.tashow.cloud.system.dal.mysql.permission; + +import com.tashow.cloud.mybatis.mybatis.core.mapper.BaseMapperX; +import com.tashow.cloud.mybatis.mybatis.core.query.LambdaQueryWrapperX; +import com.tashow.cloud.system.controller.admin.permission.vo.menu.MenuListReqVO; +import com.tashow.cloud.system.dal.dataobject.permission.MenuDO; +import com.tashow.cloud.system.controller.admin.permission.vo.menu.MenuListReqVO; +import com.tashow.cloud.system.dal.dataobject.permission.MenuDO; +import org.apache.ibatis.annotations.Mapper; + +import java.util.List; + +@Mapper +public interface MenuMapper extends BaseMapperX { + + default MenuDO selectByParentIdAndName(Long parentId, String name) { + return selectOne(MenuDO::getParentId, parentId, MenuDO::getName, name); + } + + default Long selectCountByParentId(Long parentId) { + return selectCount(MenuDO::getParentId, parentId); + } + + default List selectList(MenuListReqVO reqVO) { + return selectList(new LambdaQueryWrapperX() + .likeIfPresent(MenuDO::getName, reqVO.getName()) + .eqIfPresent(MenuDO::getStatus, reqVO.getStatus())); + } + + default List selectListByPermission(String permission) { + return selectList(MenuDO::getPermission, permission); + } +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/dal/mysql/permission/RoleMapper.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/dal/mysql/permission/RoleMapper.java new file mode 100644 index 0000000..9cef51b --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/dal/mysql/permission/RoleMapper.java @@ -0,0 +1,41 @@ +package com.tashow.cloud.system.dal.mysql.permission; + +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.mybatis.mybatis.core.dataobject.BaseDO; +import com.tashow.cloud.mybatis.mybatis.core.mapper.BaseMapperX; +import com.tashow.cloud.mybatis.mybatis.core.query.LambdaQueryWrapperX; +import com.tashow.cloud.system.controller.admin.permission.vo.role.RolePageReqVO; +import com.tashow.cloud.system.dal.dataobject.permission.RoleDO; +import com.tashow.cloud.system.controller.admin.permission.vo.role.RolePageReqVO; +import com.tashow.cloud.system.dal.dataobject.permission.RoleDO; +import org.apache.ibatis.annotations.Mapper; +import org.springframework.lang.Nullable; + +import java.util.Collection; +import java.util.List; + +@Mapper +public interface RoleMapper extends BaseMapperX { + + default PageResult selectPage(RolePageReqVO reqVO) { + return selectPage(reqVO, new LambdaQueryWrapperX() + .likeIfPresent(RoleDO::getName, reqVO.getName()) + .likeIfPresent(RoleDO::getCode, reqVO.getCode()) + .eqIfPresent(RoleDO::getStatus, reqVO.getStatus()) + .betweenIfPresent(BaseDO::getCreateTime, reqVO.getCreateTime()) + .orderByAsc(RoleDO::getSort)); + } + + default RoleDO selectByName(String name) { + return selectOne(RoleDO::getName, name); + } + + default RoleDO selectByCode(String code) { + return selectOne(RoleDO::getCode, code); + } + + default List selectListByStatus(@Nullable Collection statuses) { + return selectList(RoleDO::getStatus, statuses); + } + +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/dal/mysql/permission/RoleMenuMapper.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/dal/mysql/permission/RoleMenuMapper.java new file mode 100644 index 0000000..518e8a3 --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/dal/mysql/permission/RoleMenuMapper.java @@ -0,0 +1,41 @@ +package com.tashow.cloud.system.dal.mysql.permission; + +import com.tashow.cloud.mybatis.mybatis.core.mapper.BaseMapperX; +import com.tashow.cloud.system.dal.dataobject.permission.RoleMenuDO; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.tashow.cloud.system.dal.dataobject.permission.RoleMenuDO; +import org.apache.ibatis.annotations.Mapper; + +import java.util.Collection; +import java.util.List; + +@Mapper +public interface RoleMenuMapper extends BaseMapperX { + + default List selectListByRoleId(Long roleId) { + return selectList(RoleMenuDO::getRoleId, roleId); + } + + default List selectListByRoleId(Collection roleIds) { + return selectList(RoleMenuDO::getRoleId, roleIds); + } + + default List selectListByMenuId(Long menuId) { + return selectList(RoleMenuDO::getMenuId, menuId); + } + + default void deleteListByRoleIdAndMenuIds(Long roleId, Collection menuIds) { + delete(new LambdaQueryWrapper() + .eq(RoleMenuDO::getRoleId, roleId) + .in(RoleMenuDO::getMenuId, menuIds)); + } + + default void deleteListByMenuId(Long menuId) { + delete(new LambdaQueryWrapper().eq(RoleMenuDO::getMenuId, menuId)); + } + + default void deleteListByRoleId(Long roleId) { + delete(new LambdaQueryWrapper().eq(RoleMenuDO::getRoleId, roleId)); + } + +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/dal/mysql/permission/UserRoleMapper.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/dal/mysql/permission/UserRoleMapper.java new file mode 100644 index 0000000..fa01168 --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/dal/mysql/permission/UserRoleMapper.java @@ -0,0 +1,37 @@ +package com.tashow.cloud.system.dal.mysql.permission; + +import com.tashow.cloud.mybatis.mybatis.core.mapper.BaseMapperX; +import com.tashow.cloud.system.dal.dataobject.permission.UserRoleDO; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.tashow.cloud.system.dal.dataobject.permission.UserRoleDO; +import org.apache.ibatis.annotations.Mapper; + +import java.util.Collection; +import java.util.List; + +@Mapper +public interface UserRoleMapper extends BaseMapperX { + + default List selectListByUserId(Long userId) { + return selectList(UserRoleDO::getUserId, userId); + } + + default void deleteListByUserIdAndRoleIdIds(Long userId, Collection roleIds) { + delete(new LambdaQueryWrapper() + .eq(UserRoleDO::getUserId, userId) + .in(UserRoleDO::getRoleId, roleIds)); + } + + default void deleteListByUserId(Long userId) { + delete(new LambdaQueryWrapper().eq(UserRoleDO::getUserId, userId)); + } + + default void deleteListByRoleId(Long roleId) { + delete(new LambdaQueryWrapper().eq(UserRoleDO::getRoleId, roleId)); + } + + default List selectListByRoleIds(Collection roleIds) { + return selectList(UserRoleDO::getRoleId, roleIds); + } + +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/dal/mysql/sms/SmsChannelMapper.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/dal/mysql/sms/SmsChannelMapper.java new file mode 100644 index 0000000..6975c8d --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/dal/mysql/sms/SmsChannelMapper.java @@ -0,0 +1,27 @@ +package com.tashow.cloud.system.dal.mysql.sms; + +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.mybatis.mybatis.core.mapper.BaseMapperX; +import com.tashow.cloud.mybatis.mybatis.core.query.LambdaQueryWrapperX; +import com.tashow.cloud.system.controller.admin.sms.vo.channel.SmsChannelPageReqVO; +import com.tashow.cloud.system.dal.dataobject.sms.SmsChannelDO; +import com.tashow.cloud.system.controller.admin.sms.vo.channel.SmsChannelPageReqVO; +import com.tashow.cloud.system.dal.dataobject.sms.SmsChannelDO; +import org.apache.ibatis.annotations.Mapper; + +@Mapper +public interface SmsChannelMapper extends BaseMapperX { + + default PageResult selectPage(SmsChannelPageReqVO reqVO) { + return selectPage(reqVO, new LambdaQueryWrapperX() + .likeIfPresent(SmsChannelDO::getSignature, reqVO.getSignature()) + .eqIfPresent(SmsChannelDO::getStatus, reqVO.getStatus()) + .betweenIfPresent(SmsChannelDO::getCreateTime, reqVO.getCreateTime()) + .orderByDesc(SmsChannelDO::getId)); + } + + default SmsChannelDO selectByCode(String code) { + return selectOne(SmsChannelDO::getCode, code); + } + +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/dal/mysql/sms/SmsCodeMapper.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/dal/mysql/sms/SmsCodeMapper.java new file mode 100644 index 0000000..7b5fcfd --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/dal/mysql/sms/SmsCodeMapper.java @@ -0,0 +1,29 @@ +package com.tashow.cloud.system.dal.mysql.sms; + +import com.tashow.cloud.mybatis.mybatis.core.mapper.BaseMapperX; +import com.tashow.cloud.mybatis.mybatis.core.query.QueryWrapperX; +import com.tashow.cloud.system.dal.dataobject.sms.SmsCodeDO; +import com.tashow.cloud.system.dal.dataobject.sms.SmsCodeDO; +import org.apache.ibatis.annotations.Mapper; + +@Mapper +public interface SmsCodeMapper extends BaseMapperX { + + /** + * 获得手机号的最后一个手机验证码 + * + * @param mobile 手机号 + * @param scene 发送场景,选填 + * @param code 验证码 选填 + * @return 手机验证码 + */ + default SmsCodeDO selectLastByMobile(String mobile, String code, Integer scene) { + return selectOne(new QueryWrapperX() + .eq("mobile", mobile) + .eqIfPresent("scene", scene) + .eqIfPresent("code", code) + .orderByDesc("id") + .limitN(1)); + } + +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/dal/mysql/sms/SmsLogMapper.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/dal/mysql/sms/SmsLogMapper.java new file mode 100644 index 0000000..4eaafca --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/dal/mysql/sms/SmsLogMapper.java @@ -0,0 +1,27 @@ +package com.tashow.cloud.system.dal.mysql.sms; + +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.mybatis.mybatis.core.mapper.BaseMapperX; +import com.tashow.cloud.mybatis.mybatis.core.query.LambdaQueryWrapperX; +import com.tashow.cloud.system.controller.admin.sms.vo.log.SmsLogPageReqVO; +import com.tashow.cloud.system.dal.dataobject.sms.SmsLogDO; +import com.tashow.cloud.system.controller.admin.sms.vo.log.SmsLogPageReqVO; +import com.tashow.cloud.system.dal.dataobject.sms.SmsLogDO; +import org.apache.ibatis.annotations.Mapper; + +@Mapper +public interface SmsLogMapper extends BaseMapperX { + + default PageResult selectPage(SmsLogPageReqVO reqVO) { + return selectPage(reqVO, new LambdaQueryWrapperX() + .eqIfPresent(SmsLogDO::getChannelId, reqVO.getChannelId()) + .eqIfPresent(SmsLogDO::getTemplateId, reqVO.getTemplateId()) + .likeIfPresent(SmsLogDO::getMobile, reqVO.getMobile()) + .eqIfPresent(SmsLogDO::getSendStatus, reqVO.getSendStatus()) + .betweenIfPresent(SmsLogDO::getSendTime, reqVO.getSendTime()) + .eqIfPresent(SmsLogDO::getReceiveStatus, reqVO.getReceiveStatus()) + .betweenIfPresent(SmsLogDO::getReceiveTime, reqVO.getReceiveTime()) + .orderByDesc(SmsLogDO::getId)); + } + +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/dal/mysql/sms/SmsTemplateMapper.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/dal/mysql/sms/SmsTemplateMapper.java new file mode 100644 index 0000000..176afc0 --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/dal/mysql/sms/SmsTemplateMapper.java @@ -0,0 +1,35 @@ +package com.tashow.cloud.system.dal.mysql.sms; + +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.mybatis.mybatis.core.mapper.BaseMapperX; +import com.tashow.cloud.mybatis.mybatis.core.query.LambdaQueryWrapperX; +import com.tashow.cloud.system.controller.admin.sms.vo.template.SmsTemplatePageReqVO; +import com.tashow.cloud.system.dal.dataobject.sms.SmsTemplateDO; +import com.tashow.cloud.system.controller.admin.sms.vo.template.SmsTemplatePageReqVO; +import com.tashow.cloud.system.dal.dataobject.sms.SmsTemplateDO; +import org.apache.ibatis.annotations.Mapper; + +@Mapper +public interface SmsTemplateMapper extends BaseMapperX { + + default SmsTemplateDO selectByCode(String code) { + return selectOne(SmsTemplateDO::getCode, code); + } + + default PageResult selectPage(SmsTemplatePageReqVO reqVO) { + return selectPage(reqVO, new LambdaQueryWrapperX() + .eqIfPresent(SmsTemplateDO::getType, reqVO.getType()) + .eqIfPresent(SmsTemplateDO::getStatus, reqVO.getStatus()) + .likeIfPresent(SmsTemplateDO::getCode, reqVO.getCode()) + .likeIfPresent(SmsTemplateDO::getContent, reqVO.getContent()) + .likeIfPresent(SmsTemplateDO::getApiTemplateId, reqVO.getApiTemplateId()) + .eqIfPresent(SmsTemplateDO::getChannelId, reqVO.getChannelId()) + .betweenIfPresent(SmsTemplateDO::getCreateTime, reqVO.getCreateTime()) + .orderByDesc(SmsTemplateDO::getId)); + } + + default Long selectCountByChannelId(Long channelId) { + return selectCount(SmsTemplateDO::getChannelId, channelId); + } + +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/dal/mysql/social/SocialClientMapper.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/dal/mysql/social/SocialClientMapper.java new file mode 100644 index 0000000..1817a8a --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/dal/mysql/social/SocialClientMapper.java @@ -0,0 +1,30 @@ +package com.tashow.cloud.system.dal.mysql.social; + +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.mybatis.mybatis.core.mapper.BaseMapperX; +import com.tashow.cloud.mybatis.mybatis.core.query.LambdaQueryWrapperX; +import com.tashow.cloud.system.controller.admin.socail.vo.client.SocialClientPageReqVO; +import com.tashow.cloud.system.dal.dataobject.social.SocialClientDO; +import com.tashow.cloud.system.controller.admin.socail.vo.client.SocialClientPageReqVO; +import com.tashow.cloud.system.dal.dataobject.social.SocialClientDO; +import org.apache.ibatis.annotations.Mapper; + +@Mapper +public interface SocialClientMapper extends BaseMapperX { + + default SocialClientDO selectBySocialTypeAndUserType(Integer socialType, Integer userType) { + return selectOne(SocialClientDO::getSocialType, socialType, + SocialClientDO::getUserType, userType); + } + + default PageResult selectPage(SocialClientPageReqVO reqVO) { + return selectPage(reqVO, new LambdaQueryWrapperX() + .likeIfPresent(SocialClientDO::getName, reqVO.getName()) + .eqIfPresent(SocialClientDO::getSocialType, reqVO.getSocialType()) + .eqIfPresent(SocialClientDO::getUserType, reqVO.getUserType()) + .likeIfPresent(SocialClientDO::getClientId, reqVO.getClientId()) + .eqIfPresent(SocialClientDO::getStatus, reqVO.getStatus()) + .orderByDesc(SocialClientDO::getId)); + } + +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/dal/mysql/social/SocialUserBindMapper.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/dal/mysql/social/SocialUserBindMapper.java new file mode 100644 index 0000000..ebaabcb --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/dal/mysql/social/SocialUserBindMapper.java @@ -0,0 +1,45 @@ +package com.tashow.cloud.system.dal.mysql.social; + +import com.tashow.cloud.mybatis.mybatis.core.mapper.BaseMapperX; +import com.tashow.cloud.mybatis.mybatis.core.query.LambdaQueryWrapperX; +import com.tashow.cloud.system.dal.dataobject.social.SocialUserBindDO; +import com.tashow.cloud.system.dal.dataobject.social.SocialUserBindDO; +import org.apache.ibatis.annotations.Mapper; + +import java.util.List; + +@Mapper +public interface SocialUserBindMapper extends BaseMapperX { + + default void deleteByUserTypeAndUserIdAndSocialType(Integer userType, Long userId, Integer socialType) { + delete(new LambdaQueryWrapperX() + .eq(SocialUserBindDO::getUserType, userType) + .eq(SocialUserBindDO::getUserId, userId) + .eq(SocialUserBindDO::getSocialType, socialType)); + } + + default void deleteByUserTypeAndSocialUserId(Integer userType, Long socialUserId) { + delete(new LambdaQueryWrapperX() + .eq(SocialUserBindDO::getUserType, userType) + .eq(SocialUserBindDO::getSocialUserId, socialUserId)); + } + + default SocialUserBindDO selectByUserTypeAndSocialUserId(Integer userType, Long socialUserId) { + return selectOne(SocialUserBindDO::getUserType, userType, + SocialUserBindDO::getSocialUserId, socialUserId); + } + + default List selectListByUserIdAndUserType(Long userId, Integer userType) { + return selectList(new LambdaQueryWrapperX() + .eq(SocialUserBindDO::getUserId, userId) + .eq(SocialUserBindDO::getUserType, userType)); + } + + default SocialUserBindDO selectByUserIdAndUserTypeAndSocialType(Long userId, Integer userType, Integer socialType) { + return selectOne(new LambdaQueryWrapperX() + .eq(SocialUserBindDO::getUserId, userId) + .eq(SocialUserBindDO::getUserType, userType) + .eq(SocialUserBindDO::getSocialType, socialType)); + } + +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/dal/mysql/social/SocialUserMapper.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/dal/mysql/social/SocialUserMapper.java new file mode 100644 index 0000000..c2e7e49 --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/dal/mysql/social/SocialUserMapper.java @@ -0,0 +1,38 @@ +package com.tashow.cloud.system.dal.mysql.social; + +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.mybatis.mybatis.core.mapper.BaseMapperX; +import com.tashow.cloud.mybatis.mybatis.core.query.LambdaQueryWrapperX; +import com.tashow.cloud.system.controller.admin.socail.vo.user.SocialUserPageReqVO; +import com.tashow.cloud.system.dal.dataobject.social.SocialUserDO; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.tashow.cloud.system.controller.admin.socail.vo.user.SocialUserPageReqVO; +import com.tashow.cloud.system.dal.dataobject.social.SocialUserDO; +import org.apache.ibatis.annotations.Mapper; + +@Mapper +public interface SocialUserMapper extends BaseMapperX { + + default SocialUserDO selectByTypeAndCodeAnState(Integer type, String code, String state) { + return selectOne(new LambdaQueryWrapper() + .eq(SocialUserDO::getType, type) + .eq(SocialUserDO::getCode, code) + .eq(SocialUserDO::getState, state)); + } + + default SocialUserDO selectByTypeAndOpenid(Integer type, String openid) { + return selectOne(new LambdaQueryWrapper() + .eq(SocialUserDO::getType, type) + .eq(SocialUserDO::getOpenid, openid)); + } + + default PageResult selectPage(SocialUserPageReqVO reqVO) { + return selectPage(reqVO, new LambdaQueryWrapperX() + .eqIfPresent(SocialUserDO::getType, reqVO.getType()) + .likeIfPresent(SocialUserDO::getNickname, reqVO.getNickname()) + .likeIfPresent(SocialUserDO::getOpenid, reqVO.getOpenid()) + .betweenIfPresent(SocialUserDO::getCreateTime, reqVO.getCreateTime()) + .orderByDesc(SocialUserDO::getId)); + } + +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/dal/mysql/tenant/TenantMapper.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/dal/mysql/tenant/TenantMapper.java new file mode 100644 index 0000000..9c049ae --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/dal/mysql/tenant/TenantMapper.java @@ -0,0 +1,48 @@ +package com.tashow.cloud.system.dal.mysql.tenant; + +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.mybatis.mybatis.core.mapper.BaseMapperX; +import com.tashow.cloud.mybatis.mybatis.core.query.LambdaQueryWrapperX; +import com.tashow.cloud.system.controller.admin.tenant.vo.tenant.TenantPageReqVO; +import com.tashow.cloud.system.dal.dataobject.tenant.TenantDO; +import com.tashow.cloud.system.controller.admin.tenant.vo.tenant.TenantPageReqVO; +import com.tashow.cloud.system.dal.dataobject.tenant.TenantDO; +import org.apache.ibatis.annotations.Mapper; + +import java.util.List; + +/** + * 租户 Mapper + * + * @author 芋道源码 + */ +@Mapper +public interface TenantMapper extends BaseMapperX { + + default PageResult selectPage(TenantPageReqVO reqVO) { + return selectPage(reqVO, new LambdaQueryWrapperX() + .likeIfPresent(TenantDO::getName, reqVO.getName()) + .likeIfPresent(TenantDO::getContactName, reqVO.getContactName()) + .likeIfPresent(TenantDO::getContactMobile, reqVO.getContactMobile()) + .eqIfPresent(TenantDO::getStatus, reqVO.getStatus()) + .betweenIfPresent(TenantDO::getCreateTime, reqVO.getCreateTime()) + .orderByDesc(TenantDO::getId)); + } + + default TenantDO selectByName(String name) { + return selectOne(TenantDO::getName, name); + } + + default TenantDO selectByWebsite(String website) { + return selectOne(TenantDO::getWebsite, website); + } + + default Long selectCountByPackageId(Long packageId) { + return selectCount(TenantDO::getPackageId, packageId); + } + + default List selectListByPackageId(Long packageId) { + return selectList(TenantDO::getPackageId, packageId); + } + +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/dal/mysql/tenant/TenantPackageMapper.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/dal/mysql/tenant/TenantPackageMapper.java new file mode 100644 index 0000000..0a18e69 --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/dal/mysql/tenant/TenantPackageMapper.java @@ -0,0 +1,38 @@ +package com.tashow.cloud.system.dal.mysql.tenant; + +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.mybatis.mybatis.core.mapper.BaseMapperX; +import com.tashow.cloud.mybatis.mybatis.core.query.LambdaQueryWrapperX; +import com.tashow.cloud.system.controller.admin.tenant.vo.packages.TenantPackagePageReqVO; +import com.tashow.cloud.system.dal.dataobject.tenant.TenantPackageDO; +import com.tashow.cloud.system.controller.admin.tenant.vo.packages.TenantPackagePageReqVO; +import com.tashow.cloud.system.dal.dataobject.tenant.TenantPackageDO; +import org.apache.ibatis.annotations.Mapper; + +import java.util.List; + +/** + * 租户套餐 Mapper + * + * @author 芋道源码 + */ +@Mapper +public interface TenantPackageMapper extends BaseMapperX { + + default PageResult selectPage(TenantPackagePageReqVO reqVO) { + return selectPage(reqVO, new LambdaQueryWrapperX() + .likeIfPresent(TenantPackageDO::getName, reqVO.getName()) + .eqIfPresent(TenantPackageDO::getStatus, reqVO.getStatus()) + .likeIfPresent(TenantPackageDO::getRemark, reqVO.getRemark()) + .betweenIfPresent(TenantPackageDO::getCreateTime, reqVO.getCreateTime()) + .orderByDesc(TenantPackageDO::getId)); + } + + default List selectListByStatus(Integer status) { + return selectList(TenantPackageDO::getStatus, status); + } + + default TenantPackageDO selectByName(String name) { + return selectOne(TenantPackageDO::getName, name); + } +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/dal/mysql/user/AdminUserMapper.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/dal/mysql/user/AdminUserMapper.java new file mode 100644 index 0000000..dd43e70 --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/dal/mysql/user/AdminUserMapper.java @@ -0,0 +1,53 @@ +package com.tashow.cloud.system.dal.mysql.user; + +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.mybatis.mybatis.core.mapper.BaseMapperX; +import com.tashow.cloud.mybatis.mybatis.core.query.LambdaQueryWrapperX; +import com.tashow.cloud.system.controller.admin.user.vo.user.UserPageReqVO; +import com.tashow.cloud.system.dal.dataobject.user.AdminUserDO; +import com.tashow.cloud.system.controller.admin.user.vo.user.UserPageReqVO; +import com.tashow.cloud.system.dal.dataobject.user.AdminUserDO; +import org.apache.ibatis.annotations.Mapper; + +import java.util.Collection; +import java.util.List; + +@Mapper +public interface AdminUserMapper extends BaseMapperX { + + default AdminUserDO selectByUsername(String username) { + return selectOne(AdminUserDO::getUsername, username); + } + + default AdminUserDO selectByEmail(String email) { + return selectOne(AdminUserDO::getEmail, email); + } + + default AdminUserDO selectByMobile(String mobile) { + return selectOne(AdminUserDO::getMobile, mobile); + } + + default PageResult selectPage(UserPageReqVO reqVO, Collection deptIds, Collection userIds) { + return selectPage(reqVO, new LambdaQueryWrapperX() + .likeIfPresent(AdminUserDO::getUsername, reqVO.getUsername()) + .likeIfPresent(AdminUserDO::getMobile, reqVO.getMobile()) + .eqIfPresent(AdminUserDO::getStatus, reqVO.getStatus()) + .betweenIfPresent(AdminUserDO::getCreateTime, reqVO.getCreateTime()) + .inIfPresent(AdminUserDO::getDeptId, deptIds) + .inIfPresent(AdminUserDO::getId, userIds) + .orderByDesc(AdminUserDO::getId)); + } + + default List selectListByNickname(String nickname) { + return selectList(new LambdaQueryWrapperX().like(AdminUserDO::getNickname, nickname)); + } + + default List selectListByStatus(Integer status) { + return selectList(AdminUserDO::getStatus, status); + } + + default List selectListByDeptIds(Collection deptIds) { + return selectList(AdminUserDO::getDeptId, deptIds); + } + +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/dal/redis/RedisKeyConstants.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/dal/redis/RedisKeyConstants.java new file mode 100644 index 0000000..b713489 --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/dal/redis/RedisKeyConstants.java @@ -0,0 +1,110 @@ +package com.tashow.cloud.system.dal.redis; + +import com.tashow.cloud.system.dal.dataobject.oauth2.OAuth2AccessTokenDO; + +/** + * System Redis Key 枚举类 + * + * @author 芋道源码 + */ +public interface RedisKeyConstants { + + /** + * 指定部门的所有子部门编号数组的缓存 + *

+ * KEY 格式:dept_children_ids:{id} + * VALUE 数据类型:String 子部门编号集合 + */ + String DEPT_CHILDREN_ID_LIST = "dept_children_ids"; + + /** + * 角色的缓存 + *

+ * KEY 格式:role:{id} + * VALUE 数据类型:String 角色信息 + */ + String ROLE = "role"; + + /** + * 用户拥有的角色编号的缓存 + *

+ * KEY 格式:user_role_ids:{userId} + * VALUE 数据类型:String 角色编号集合 + */ + String USER_ROLE_ID_LIST = "user_role_ids"; + + /** + * 拥有指定菜单的角色编号的缓存 + *

+ * KEY 格式:user_role_ids:{menuId} + * VALUE 数据类型:String 角色编号集合 + */ + String MENU_ROLE_ID_LIST = "menu_role_ids"; + + /** + * 拥有权限对应的菜单编号数组的缓存 + *

+ * KEY 格式:permission_menu_ids:{permission} + * VALUE 数据类型:String 菜单编号数组 + */ + String PERMISSION_MENU_ID_LIST = "permission_menu_ids"; + + /** + * OAuth2 客户端的缓存 + *

+ * KEY 格式:oauth_client:{id} + * VALUE 数据类型:String 客户端信息 + */ + String OAUTH_CLIENT = "oauth_client"; + + /** + * 访问令牌的缓存 + *

+ * KEY 格式:oauth2_access_token:{token} + * VALUE 数据类型:String 访问令牌信息 {@link OAuth2AccessTokenDO} + *

+ * 由于动态过期时间,使用 RedisTemplate 操作 + */ + String OAUTH2_ACCESS_TOKEN = "oauth2_access_token:%s"; + + /** + * 站内信模版的缓存 + *

+ * KEY 格式:notify_template:{code} + * VALUE 数据格式:String 模版信息 + */ + String NOTIFY_TEMPLATE = "notify_template"; + + /** + * 邮件账号的缓存 + *

+ * KEY 格式:mail_account:{id} + * VALUE 数据格式:String 账号信息 + */ + String MAIL_ACCOUNT = "mail_account"; + + /** + * 邮件模版的缓存 + *

+ * KEY 格式:mail_template:{code} + * VALUE 数据格式:String 模版信息 + */ + String MAIL_TEMPLATE = "mail_template"; + + /** + * 短信模版的缓存 + *

+ * KEY 格式:sms_template:{id} + * VALUE 数据格式:String 模版信息 + */ + String SMS_TEMPLATE = "sms_template"; + + /** + * 小程序订阅模版的缓存 + * + * KEY 格式:wxa_subscribe_template:{userType} + * VALUE 数据格式 String, 模版信息 + */ + String WXA_SUBSCRIBE_TEMPLATE = "wxa_subscribe_template"; + +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/dal/redis/oauth2/OAuth2AccessTokenRedisDAO.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/dal/redis/oauth2/OAuth2AccessTokenRedisDAO.java new file mode 100644 index 0000000..10c53a6 --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/dal/redis/oauth2/OAuth2AccessTokenRedisDAO.java @@ -0,0 +1,59 @@ +package com.tashow.cloud.system.dal.redis.oauth2; + +import cn.hutool.core.date.LocalDateTimeUtil; +import com.tashow.cloud.common.util.collection.CollectionUtils; +import com.tashow.cloud.common.util.json.JsonUtils; +import com.tashow.cloud.system.dal.dataobject.oauth2.OAuth2AccessTokenDO; +import com.tashow.cloud.system.dal.redis.RedisKeyConstants; +import org.springframework.data.redis.core.StringRedisTemplate; +import org.springframework.stereotype.Repository; + +import jakarta.annotation.Resource; +import java.time.LocalDateTime; +import java.time.temporal.ChronoUnit; +import java.util.Collection; +import java.util.List; +import java.util.concurrent.TimeUnit; + + +/** + * {@link OAuth2AccessTokenDO} 的 RedisDAO + * + * @author 芋道源码 + */ +@Repository +public class OAuth2AccessTokenRedisDAO { + + @Resource + private StringRedisTemplate stringRedisTemplate; + + public OAuth2AccessTokenDO get(String accessToken) { + String redisKey = formatKey(accessToken); + return JsonUtils.parseObject(stringRedisTemplate.opsForValue().get(redisKey), OAuth2AccessTokenDO.class); + } + + public void set(OAuth2AccessTokenDO accessTokenDO) { + String redisKey = formatKey(accessTokenDO.getAccessToken()); + // 清理多余字段,避免缓存 + accessTokenDO.setUpdater(null).setUpdateTime(null).setCreateTime(null).setCreator(null).setDeleted(null); + long time = LocalDateTimeUtil.between(LocalDateTime.now(), accessTokenDO.getExpiresTime(), ChronoUnit.SECONDS); + if (time > 0) { + stringRedisTemplate.opsForValue().set(redisKey, JsonUtils.toJsonString(accessTokenDO), time, TimeUnit.SECONDS); + } + } + + public void delete(String accessToken) { + String redisKey = formatKey(accessToken); + stringRedisTemplate.delete(redisKey); + } + + public void deleteList(Collection accessTokens) { + List redisKeys = CollectionUtils.convertList(accessTokens, OAuth2AccessTokenRedisDAO::formatKey); + stringRedisTemplate.delete(redisKeys); + } + + private static String formatKey(String accessToken) { + return String.format(RedisKeyConstants.OAUTH2_ACCESS_TOKEN, accessToken); + } + +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/framework/captcha/config/YudaoCaptchaConfiguration.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/framework/captcha/config/YudaoCaptchaConfiguration.java new file mode 100644 index 0000000..a4fe2e2 --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/framework/captcha/config/YudaoCaptchaConfiguration.java @@ -0,0 +1,30 @@ +package com.tashow.cloud.system.framework.captcha.config; + +import com.tashow.cloud.system.framework.captcha.core.RedisCaptchaServiceImpl; +import com.tashow.cloud.system.framework.captcha.core.RedisCaptchaServiceImpl; +import com.xingyuv.captcha.properties.AjCaptchaProperties; +import com.xingyuv.captcha.service.CaptchaCacheService; +import com.xingyuv.captcha.service.impl.CaptchaServiceFactory; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.redis.core.StringRedisTemplate; + +/** + * 验证码的配置类 + * + * @author 芋道源码 + */ +@Configuration(proxyBeanMethods = false) +public class YudaoCaptchaConfiguration { + + @Bean + public CaptchaCacheService captchaCacheService(AjCaptchaProperties config, + StringRedisTemplate stringRedisTemplate) { + CaptchaCacheService captchaCacheService = CaptchaServiceFactory.getCache(config.getCacheType().name()); + if (captchaCacheService instanceof RedisCaptchaServiceImpl) { + ((RedisCaptchaServiceImpl) captchaCacheService).setStringRedisTemplate(stringRedisTemplate); + } + return captchaCacheService; + } + +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/framework/captcha/core/RedisCaptchaServiceImpl.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/framework/captcha/core/RedisCaptchaServiceImpl.java new file mode 100644 index 0000000..8443abd --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/framework/captcha/core/RedisCaptchaServiceImpl.java @@ -0,0 +1,49 @@ +package com.tashow.cloud.system.framework.captcha.core; + +import com.xingyuv.captcha.service.CaptchaCacheService; +import lombok.Setter; +import org.springframework.data.redis.core.StringRedisTemplate; + +import java.util.concurrent.TimeUnit; + +/** + * 基于 Redis 实现验证码的存储 + * + * @author 星语 + */ +@Setter +public class RedisCaptchaServiceImpl implements CaptchaCacheService { + + private StringRedisTemplate stringRedisTemplate; + + @Override + public String type() { + return "redis"; + } + + @Override + public void set(String key, String value, long expiresInSeconds) { + stringRedisTemplate.opsForValue().set(key, value, expiresInSeconds, TimeUnit.SECONDS); + } + + @Override + public boolean exists(String key) { + return Boolean.TRUE.equals(stringRedisTemplate.hasKey(key)); + } + + @Override + public void delete(String key) { + stringRedisTemplate.delete(key); + } + + @Override + public String get(String key) { + return stringRedisTemplate.opsForValue().get(key); + } + + @Override + public Long increment(String key, long val) { + return stringRedisTemplate.opsForValue().increment(key,val); + } + +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/framework/captcha/package-info.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/framework/captcha/package-info.java new file mode 100644 index 0000000..ad6c49f --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/framework/captcha/package-info.java @@ -0,0 +1,8 @@ +/** + * 验证码拓展 + * + * 基于 aj-captcha 实现滑块验证码,文档:https://ajcaptcha.beliefteam.cn/captcha-doc/ + * + * @author 星语 + */ +package com.tashow.cloud.system.framework.captcha; diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/framework/datapermission/config/DataPermissionConfiguration.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/framework/datapermission/config/DataPermissionConfiguration.java new file mode 100644 index 0000000..9d9a1ee --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/framework/datapermission/config/DataPermissionConfiguration.java @@ -0,0 +1,28 @@ +package com.tashow.cloud.system.framework.datapermission.config; + +import com.tashow.cloud.permission.core.rule.dept.DeptDataPermissionRuleCustomizer; +import com.tashow.cloud.system.dal.dataobject.dept.DeptDO; +import com.tashow.cloud.system.dal.dataobject.user.AdminUserDO; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * system 模块的数据权限 Configuration + * + * @author 芋道源码 + */ +@Configuration(proxyBeanMethods = false) +public class DataPermissionConfiguration { + + @Bean + public DeptDataPermissionRuleCustomizer sysDeptDataPermissionRuleCustomizer() { + return rule -> { + // dept + rule.addDeptColumn(AdminUserDO.class); + rule.addDeptColumn(DeptDO.class, "id"); + // user + rule.addUserColumn(AdminUserDO.class, "id"); + }; + } + +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/framework/datapermission/package-info.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/framework/datapermission/package-info.java new file mode 100644 index 0000000..a160a16 --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/framework/datapermission/package-info.java @@ -0,0 +1,4 @@ +/** + * system 模块的数据权限配置 + */ +package com.tashow.cloud.system.framework.datapermission; diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/framework/operatelog/core/AdminUserParseFunction.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/framework/operatelog/core/AdminUserParseFunction.java new file mode 100644 index 0000000..167ce02 --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/framework/operatelog/core/AdminUserParseFunction.java @@ -0,0 +1,51 @@ +package com.tashow.cloud.system.framework.operatelog.core; + +import cn.hutool.core.convert.Convert; +import cn.hutool.core.util.StrUtil; +import com.tashow.cloud.system.dal.dataobject.user.AdminUserDO; +import com.tashow.cloud.system.service.user.AdminUserService; +import com.mzt.logapi.service.IParseFunction; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +/** + * 管理员名字的 {@link IParseFunction} 实现类 + * + * @author HUIHUI + */ +@Slf4j +@Component +public class AdminUserParseFunction implements IParseFunction { + + public static final String NAME = "getAdminUserById"; + + @Resource + private AdminUserService adminUserService; + + @Override + public String functionName() { + return NAME; + } + + @Override + public String apply(Object value) { + if (StrUtil.isEmptyIfStr(value)) { + return ""; + } + + // 获取用户信息 + AdminUserDO user = adminUserService.getUser(Convert.toLong(value)); + if (user == null) { + log.warn("[apply][获取用户{{}}为空", value); + return ""; + } + // 返回格式 芋道源码(13888888888) + String nickname = user.getNickname(); + if (StrUtil.isEmpty(user.getMobile())) { + return nickname; + } + return StrUtil.format("{}({})", nickname, user.getMobile()); + } + +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/framework/operatelog/core/AreaParseFunction.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/framework/operatelog/core/AreaParseFunction.java new file mode 100644 index 0000000..a392e20 --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/framework/operatelog/core/AreaParseFunction.java @@ -0,0 +1,39 @@ +package com.tashow.cloud.system.framework.operatelog.core; + +import cn.hutool.core.convert.Convert; +import cn.hutool.core.util.StrUtil; +import com.mzt.logapi.service.IParseFunction; +import com.tashow.cloud.common.util.ip.AreaUtils; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +/** + * 地名的 {@link IParseFunction} 实现类 + * + * @author HUIHUI + */ +@Slf4j +@Component +public class AreaParseFunction implements IParseFunction { + + public static final String NAME = "getArea"; + + @Override + public boolean executeBefore() { + return true; // 先转换值后对比 + } + + @Override + public String functionName() { + return NAME; + } + + @Override + public String apply(Object value) { + if (StrUtil.isEmptyIfStr(value)) { + return ""; + } + return AreaUtils.format(Convert.toInt(value)); + } + +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/framework/operatelog/core/BooleanParseFunction.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/framework/operatelog/core/BooleanParseFunction.java new file mode 100644 index 0000000..22faabb --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/framework/operatelog/core/BooleanParseFunction.java @@ -0,0 +1,39 @@ +package com.tashow.cloud.system.framework.operatelog.core; + +import cn.hutool.core.util.StrUtil; +import com.tashow.cloud.excel.dict.core.DictFrameworkUtils; +import com.tashow.cloud.infraapi.enums.DictTypeConstants; +import com.mzt.logapi.service.IParseFunction; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +/** + * 是否类型的 {@link IParseFunction} 实现类 + * + * @author HUIHUI + */ +@Component +@Slf4j +public class BooleanParseFunction implements IParseFunction { + + public static final String NAME = "getBoolean"; + + @Override + public boolean executeBefore() { + return true; // 先转换值后对比 + } + + @Override + public String functionName() { + return NAME; + } + + @Override + public String apply(Object value) { + if (StrUtil.isEmptyIfStr(value)) { + return ""; + } + return DictFrameworkUtils.getDictDataLabel(DictTypeConstants.BOOLEAN_STRING, value.toString()); + } + +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/framework/operatelog/core/DeptParseFunction.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/framework/operatelog/core/DeptParseFunction.java new file mode 100644 index 0000000..71fbf30 --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/framework/operatelog/core/DeptParseFunction.java @@ -0,0 +1,46 @@ +package com.tashow.cloud.system.framework.operatelog.core; + +import cn.hutool.core.convert.Convert; +import cn.hutool.core.util.StrUtil; +import com.tashow.cloud.system.dal.dataobject.dept.DeptDO; +import com.tashow.cloud.system.service.dept.DeptService; +import com.mzt.logapi.service.IParseFunction; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +/** + * 部门名字的 {@link IParseFunction} 实现类 + * + * @author HUIHUI + */ +@Slf4j +@Component +public class DeptParseFunction implements IParseFunction { + + public static final String NAME = "getDeptById"; + + @Resource + private DeptService deptService; + + @Override + public String functionName() { + return NAME; + } + + @Override + public String apply(Object value) { + if (StrUtil.isEmptyIfStr(value)) { + return ""; + } + + // 获取部门信息 + DeptDO dept = deptService.getDept(Convert.toLong(value)); + if (dept == null) { + log.warn("[apply][获取部门{{}}为空", value); + return ""; + } + return dept.getName(); + } + +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/framework/operatelog/core/PostParseFunction.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/framework/operatelog/core/PostParseFunction.java new file mode 100644 index 0000000..71dab95 --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/framework/operatelog/core/PostParseFunction.java @@ -0,0 +1,46 @@ +package com.tashow.cloud.system.framework.operatelog.core; + +import cn.hutool.core.convert.Convert; +import cn.hutool.core.util.StrUtil; +import com.tashow.cloud.system.dal.dataobject.dept.PostDO; +import com.tashow.cloud.system.service.dept.PostService; +import com.mzt.logapi.service.IParseFunction; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +/** + * 岗位名字的 {@link IParseFunction} 实现类 + * + * @author HUIHUI + */ +@Slf4j +@Component +public class PostParseFunction implements IParseFunction { + + public static final String NAME = "getPostById"; + + @Resource + private PostService postService; + + @Override + public String functionName() { + return NAME; + } + + @Override + public String apply(Object value) { + if (StrUtil.isEmptyIfStr(value)) { + return ""; + } + + // 获取岗位信息 + PostDO post = postService.getPost(Convert.toLong(value)); + if (post == null) { + log.warn("[apply][获取岗位{{}}为空", value); + return ""; + } + return post.getName(); + } + +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/framework/operatelog/core/SexParseFunction.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/framework/operatelog/core/SexParseFunction.java new file mode 100644 index 0000000..a2f5e9e --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/framework/operatelog/core/SexParseFunction.java @@ -0,0 +1,39 @@ +package com.tashow.cloud.system.framework.operatelog.core; + +import cn.hutool.core.util.StrUtil; +import com.tashow.cloud.excel.dict.core.DictFrameworkUtils; +import com.tashow.cloud.systemapi.enums.DictTypeConstants; +import com.mzt.logapi.service.IParseFunction; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +/** + * 行业的 {@link IParseFunction} 实现类 + * + * @author HUIHUI + */ +@Component +@Slf4j +public class SexParseFunction implements IParseFunction { + + public static final String NAME = "getSex"; + + @Override + public boolean executeBefore() { + return true; // 先转换值后对比 + } + + @Override + public String functionName() { + return NAME; + } + + @Override + public String apply(Object value) { + if (StrUtil.isEmptyIfStr(value)) { + return ""; + } + return DictFrameworkUtils.getDictDataLabel(DictTypeConstants.USER_SEX, value.toString()); + } + +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/framework/operatelog/package-info.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/framework/operatelog/package-info.java new file mode 100644 index 0000000..fb8e562 --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/framework/operatelog/package-info.java @@ -0,0 +1,4 @@ +/** + * 占位文件,避免文件夹缩进 + */ +package com.tashow.cloud.system.framework.operatelog; diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/framework/package-info.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/framework/package-info.java new file mode 100644 index 0000000..711d921 --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/framework/package-info.java @@ -0,0 +1,6 @@ +/** + * 属于 system 模块的 framework 封装 + * + * @author 芋道源码 + */ +package com.tashow.cloud.system.framework; diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/framework/rpc/config/RpcConfiguration.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/framework/rpc/config/RpcConfiguration.java new file mode 100644 index 0000000..3281ff4 --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/framework/rpc/config/RpcConfiguration.java @@ -0,0 +1,12 @@ +package com.tashow.cloud.system.framework.rpc.config; + +import com.tashow.cloud.infraapi.api.config.ConfigApi; +import com.tashow.cloud.infraapi.api.file.FileApi; +import com.tashow.cloud.infraapi.api.websocket.WebSocketSenderApi; +import org.springframework.cloud.openfeign.EnableFeignClients; +import org.springframework.context.annotation.Configuration; + +@Configuration(proxyBeanMethods = false) +@EnableFeignClients(clients = {FileApi.class, WebSocketSenderApi.class, ConfigApi.class}) +public class RpcConfiguration { +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/framework/rpc/package-info.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/framework/rpc/package-info.java new file mode 100644 index 0000000..7c17f39 --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/framework/rpc/package-info.java @@ -0,0 +1,4 @@ +/** + * 占位 + */ +package com.tashow.cloud.system.framework.rpc; diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/framework/security/config/SecurityConfiguration.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/framework/security/config/SecurityConfiguration.java new file mode 100644 index 0000000..e491ebf --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/framework/security/config/SecurityConfiguration.java @@ -0,0 +1,35 @@ +package com.tashow.cloud.system.framework.security.config; + +import com.tashow.cloud.security.security.config.AuthorizeRequestsCustomizer; +import com.tashow.cloud.systemapi.enums.ApiConstants; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configurers.AuthorizeHttpRequestsConfigurer; + +/** + * System 模块的 Security 配置 + */ +@Configuration(proxyBeanMethods = false, value = "systemSecurityConfiguration") +public class SecurityConfiguration { + + @Bean("systemAuthorizeRequestsCustomizer") + public AuthorizeRequestsCustomizer authorizeRequestsCustomizer() { + return new AuthorizeRequestsCustomizer() { + + @Override + public void customize(AuthorizeHttpRequestsConfigurer.AuthorizationManagerRequestMatcherRegistry registry) { + // TODO 芋艿:这个每个项目都需要重复配置,得捉摸有没通用的方案 + // Druid 监控 + registry.requestMatchers("/druid/**").permitAll(); + // Spring Boot Actuator 的安全配置 + registry.requestMatchers("/actuator").permitAll() + .requestMatchers("/actuator/**").permitAll(); + // RPC 服务的安全配置 + registry.requestMatchers(ApiConstants.PREFIX + "/**").permitAll(); + } + + }; + } + +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/framework/security/core/package-info.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/framework/security/core/package-info.java new file mode 100644 index 0000000..61d4642 --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/framework/security/core/package-info.java @@ -0,0 +1,4 @@ +/** + * 占位 + */ +package com.tashow.cloud.system.framework.security.core; diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/framework/sms/config/SmsCodeProperties.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/framework/sms/config/SmsCodeProperties.java new file mode 100644 index 0000000..d522b9e --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/framework/sms/config/SmsCodeProperties.java @@ -0,0 +1,41 @@ +package com.tashow.cloud.system.framework.sms.config; + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.validation.annotation.Validated; + +import jakarta.validation.constraints.NotNull; +import java.time.Duration; + +@ConfigurationProperties(prefix = "tashow.sms-code") +@Validated +@Data +public class SmsCodeProperties { + + /** + * 过期时间 + */ + @NotNull(message = "过期时间不能为空") + private Duration expireTimes; + /** + * 短信发送频率 + */ + @NotNull(message = "短信发送频率不能为空") + private Duration sendFrequency; + /** + * 每日发送最大数量 + */ + @NotNull(message = "每日发送最大数量不能为空") + private Integer sendMaximumQuantityPerDay; + /** + * 验证码最小值 + */ + @NotNull(message = "验证码最小值不能为空") + private Integer beginCode; + /** + * 验证码最大值 + */ + @NotNull(message = "验证码最大值不能为空") + private Integer endCode; + +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/framework/sms/config/SmsConfiguration.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/framework/sms/config/SmsConfiguration.java new file mode 100644 index 0000000..69a13d1 --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/framework/sms/config/SmsConfiguration.java @@ -0,0 +1,23 @@ +package com.tashow.cloud.system.framework.sms.config; + +import com.tashow.cloud.system.framework.sms.core.client.SmsClientFactory; +import com.tashow.cloud.system.framework.sms.core.client.impl.SmsClientFactoryImpl; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * 短信配置类,包括短信客户端、短信验证码两部分 + * + * @author 芋道源码 + */ +@Configuration(proxyBeanMethods = false) +@EnableConfigurationProperties(SmsCodeProperties.class) +public class SmsConfiguration { + + @Bean + public SmsClientFactory smsClientFactory() { + return new SmsClientFactoryImpl(); + } + +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/framework/sms/core/client/SmsClient.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/framework/sms/core/client/SmsClient.java new file mode 100644 index 0000000..ca6b55f --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/framework/sms/core/client/SmsClient.java @@ -0,0 +1,56 @@ +package com.tashow.cloud.system.framework.sms.core.client; + +import com.tashow.cloud.common.core.KeyValue; +import com.tashow.cloud.system.framework.sms.core.client.dto.SmsReceiveRespDTO; +import com.tashow.cloud.system.framework.sms.core.client.dto.SmsSendRespDTO; +import com.tashow.cloud.system.framework.sms.core.client.dto.SmsTemplateRespDTO; + +import java.util.List; + +/** + * 短信客户端,用于对接各短信平台的 SDK,实现短信发送等功能 + * + * @author zzf + * @since 2021/1/25 14:14 + */ +public interface SmsClient { + + /** + * 获得渠道编号 + * + * @return 渠道编号 + */ + Long getId(); + + /** + * 发送消息 + * + * @param logId 日志编号 + * @param mobile 手机号 + * @param apiTemplateId 短信 API 的模板编号 + * @param templateParams 短信模板参数。通过 List 数组,保证参数的顺序 + * @return 短信发送结果 + */ + SmsSendRespDTO sendSms(Long logId, String mobile, String apiTemplateId, + List> templateParams) throws Throwable; + + /** + * 解析接收短信的接收结果 + * + * @param text 结果 + * @return 结果内容 + * @throws Throwable 当解析 text 发生异常时,则会抛出异常 + */ + List parseSmsReceiveStatus(String text) throws Throwable; + + /** + * 查询指定的短信模板 + * + * 如果查询失败,则返回 null 空 + * + * @param apiTemplateId 短信 API 的模板编号 + * @return 短信模板 + */ + SmsTemplateRespDTO getSmsTemplate(String apiTemplateId) throws Throwable; + +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/framework/sms/core/client/SmsClientFactory.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/framework/sms/core/client/SmsClientFactory.java new file mode 100644 index 0000000..81602ed --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/framework/sms/core/client/SmsClientFactory.java @@ -0,0 +1,37 @@ +package com.tashow.cloud.system.framework.sms.core.client; + +import com.tashow.cloud.system.framework.sms.core.property.SmsChannelProperties; + +/** + * 短信客户端的工厂接口 + * + * @author zzf + * @since 2021/1/28 14:01 + */ +public interface SmsClientFactory { + + /** + * 获得短信 Client + * + * @param channelId 渠道编号 + * @return 短信 Client + */ + SmsClient getSmsClient(Long channelId); + + /** + * 获得短信 Client + * + * @param channelCode 渠道编码 + * @return 短信 Client + */ + SmsClient getSmsClient(String channelCode); + + /** + * 创建短信 Client + * + * @param properties 配置对象 + * @return 短信 Client + */ + SmsClient createOrUpdateSmsClient(SmsChannelProperties properties); + +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/framework/sms/core/client/dto/SmsReceiveRespDTO.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/framework/sms/core/client/dto/SmsReceiveRespDTO.java new file mode 100644 index 0000000..e089f9c --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/framework/sms/core/client/dto/SmsReceiveRespDTO.java @@ -0,0 +1,48 @@ +package com.tashow.cloud.system.framework.sms.core.client.dto; + +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * 消息接收 Response DTO + * + * @author 芋道源码 + */ +@Data +public class SmsReceiveRespDTO { + + /** + * 是否接收成功 + */ + private Boolean success; + /** + * API 接收结果的编码 + */ + private String errorCode; + /** + * API 接收结果的说明 + */ + private String errorMsg; + + /** + * 手机号 + */ + private String mobile; + /** + * 用户接收时间 + */ + private LocalDateTime receiveTime; + + /** + * 短信 API 发送返回的序号 + */ + private String serialNo; + /** + * 短信日志编号 + * + * 对应 SysSmsLogDO 的编号 + */ + private Long logId; + +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/framework/sms/core/client/dto/SmsSendRespDTO.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/framework/sms/core/client/dto/SmsSendRespDTO.java new file mode 100644 index 0000000..253b8c5 --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/framework/sms/core/client/dto/SmsSendRespDTO.java @@ -0,0 +1,43 @@ +package com.tashow.cloud.system.framework.sms.core.client.dto; + +import lombok.Data; + +/** + * 短信发送 Response DTO + * + * @author 芋道源码 + */ +@Data +public class SmsSendRespDTO { + + /** + * 是否成功 + */ + private Boolean success; + + /** + * API 请求编号 + */ + private String apiRequestId; + + // ==================== 成功时字段 ==================== + + /** + * 短信 API 发送返回的序号 + */ + private String serialNo; + + // ==================== 失败时字段 ==================== + + /** + * API 返回错误码 + * + * 由于第三方的错误码可能是字符串,所以使用 String 类型 + */ + private String apiCode; + /** + * API 返回提示 + */ + private String apiMsg; + +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/framework/sms/core/client/dto/SmsTemplateRespDTO.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/framework/sms/core/client/dto/SmsTemplateRespDTO.java new file mode 100644 index 0000000..7bbe36f --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/framework/sms/core/client/dto/SmsTemplateRespDTO.java @@ -0,0 +1,33 @@ +package com.tashow.cloud.system.framework.sms.core.client.dto; + +import com.tashow.cloud.system.framework.sms.core.enums.SmsTemplateAuditStatusEnum; +import lombok.Data; + +/** + * 短信模板 Response DTO + * + * @author 芋道源码 + */ +@Data +public class SmsTemplateRespDTO { + + /** + * 模板编号 + */ + private String id; + /** + * 短信内容 + */ + private String content; + /** + * 审核状态 + * + * 枚举 {@link SmsTemplateAuditStatusEnum} + */ + private Integer auditStatus; + /** + * 审核未通过的理由 + */ + private String auditReason; + +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/framework/sms/core/client/impl/AbstractSmsClient.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/framework/sms/core/client/impl/AbstractSmsClient.java new file mode 100644 index 0000000..6611f53 --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/framework/sms/core/client/impl/AbstractSmsClient.java @@ -0,0 +1,48 @@ +package com.tashow.cloud.system.framework.sms.core.client.impl; + +import com.tashow.cloud.system.framework.sms.core.client.SmsClient; +import com.tashow.cloud.system.framework.sms.core.property.SmsChannelProperties; +import lombok.extern.slf4j.Slf4j; + +/** + * 短信客户端的抽象类,提供模板方法,减少子类的冗余代码 + * + * @author zzf + * @since 2021/2/1 9:28 + */ +@Slf4j +public abstract class AbstractSmsClient implements SmsClient { + + /** + * 短信渠道配置 + */ + protected volatile SmsChannelProperties properties; + + public AbstractSmsClient(SmsChannelProperties properties) { + this.properties = properties; + } + + /** + * 初始化 + */ + public final void init() { + log.debug("[init][配置({}) 初始化完成]", properties); + } + + public final void refresh(SmsChannelProperties properties) { + // 判断是否更新 + if (properties.equals(this.properties)) { + return; + } + log.info("[refresh][配置({})发生变化,重新初始化]", properties); + this.properties = properties; + // 初始化 + this.init(); + } + + @Override + public Long getId() { + return properties.getId(); + } + +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/framework/sms/core/client/impl/AliyunSmsClient.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/framework/sms/core/client/impl/AliyunSmsClient.java new file mode 100644 index 0000000..3584dae --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/framework/sms/core/client/impl/AliyunSmsClient.java @@ -0,0 +1,193 @@ +package com.tashow.cloud.system.framework.sms.core.client.impl; + +import cn.hutool.core.date.format.FastDateFormat; +import cn.hutool.core.lang.Assert; +import cn.hutool.core.util.IdUtil; +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.crypto.SecureUtil; +import cn.hutool.crypto.digest.DigestUtil; +import cn.hutool.json.JSONArray; +import cn.hutool.json.JSONObject; +import cn.hutool.json.JSONUtil; +import com.tashow.cloud.common.core.KeyValue; +import com.tashow.cloud.common.util.collection.MapUtils; +import com.tashow.cloud.common.util.http.HttpUtils; +import com.tashow.cloud.common.util.json.JsonUtils; +import com.tashow.cloud.system.framework.sms.core.client.dto.SmsReceiveRespDTO; +import com.tashow.cloud.system.framework.sms.core.client.dto.SmsSendRespDTO; +import com.tashow.cloud.system.framework.sms.core.client.dto.SmsTemplateRespDTO; +import com.tashow.cloud.system.framework.sms.core.enums.SmsTemplateAuditStatusEnum; +import com.tashow.cloud.system.framework.sms.core.property.SmsChannelProperties; +import com.google.common.annotations.VisibleForTesting; +import lombok.SneakyThrows; +import lombok.extern.slf4j.Slf4j; + +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.util.*; +import java.util.stream.Collectors; + +import static com.tashow.cloud.common.util.collection.CollectionUtils.convertList; + +/** + * 阿里短信客户端的实现类 + * + * @author zzf + * @since 2021/1/25 14:17 + */ +@Slf4j +public class AliyunSmsClient extends AbstractSmsClient { + + private static final String URL = "https://dysmsapi.aliyuncs.com"; + private static final String HOST = "dysmsapi.aliyuncs.com"; + private static final String VERSION = "2017-05-25"; + + private static final String RESPONSE_CODE_SUCCESS = "OK"; + + public AliyunSmsClient(SmsChannelProperties properties) { + super(properties); + Assert.notEmpty(properties.getApiKey(), "apiKey 不能为空"); + Assert.notEmpty(properties.getApiSecret(), "apiSecret 不能为空"); + } + + @Override + public SmsSendRespDTO sendSms(Long sendLogId, String mobile, String apiTemplateId, + List> templateParams) throws Throwable { + Assert.notBlank(properties.getSignature(), "短信签名不能为空"); + // 1. 执行请求 + // 参考链接 https://api.aliyun.com/document/Dysmsapi/2017-05-25/SendSms + TreeMap queryParam = new TreeMap<>(); + queryParam.put("PhoneNumbers", mobile); + queryParam.put("SignName", properties.getSignature()); + queryParam.put("TemplateCode", apiTemplateId); + queryParam.put("TemplateParam", JsonUtils.toJsonString(MapUtils.convertMap(templateParams))); + queryParam.put("OutId", sendLogId); + JSONObject response = request("SendSms", queryParam); + + // 2. 解析请求 + return new SmsSendRespDTO() + .setSuccess(Objects.equals(response.getStr("Code"), RESPONSE_CODE_SUCCESS)) + .setSerialNo(response.getStr("BizId")) + .setApiRequestId(response.getStr("RequestId")) + .setApiCode(response.getStr("Code")) + .setApiMsg(response.getStr("Message")); + } + + @Override + public List parseSmsReceiveStatus(String text) { + JSONArray statuses = JSONUtil.parseArray(text); + // 字段参考 https://help.aliyun.com/zh/sms/developer-reference/smsreport-2 + return convertList(statuses, status -> { + JSONObject statusObj = (JSONObject) status; + return new SmsReceiveRespDTO() + .setSuccess(statusObj.getBool("success")) // 是否接收成功 + .setErrorCode(statusObj.getStr("err_code")) // 状态报告编码 + .setErrorMsg(statusObj.getStr("err_msg")) // 状态报告说明 + .setMobile(statusObj.getStr("phone_number")) // 手机号 + .setReceiveTime(statusObj.getLocalDateTime("report_time", null)) // 状态报告时间 + .setSerialNo(statusObj.getStr("biz_id")) // 发送序列号 + .setLogId(statusObj.getLong("out_id")); // 用户序列号 + }); + } + + @Override + public SmsTemplateRespDTO getSmsTemplate(String apiTemplateId) throws Throwable { + // 1. 执行请求 + // 参考链接 https://api.aliyun.com/document/Dysmsapi/2017-05-25/QuerySmsTemplate + TreeMap queryParam = new TreeMap<>(); + queryParam.put("TemplateCode", apiTemplateId); + JSONObject response = request("QuerySmsTemplate", queryParam); + + // 2.1 请求失败 + String code = response.getStr("Code"); + if (ObjectUtil.notEqual(code, RESPONSE_CODE_SUCCESS)) { + log.error("[getSmsTemplate][模版编号({}) 响应不正确({})]", apiTemplateId, response); + return null; + } + // 2.2 请求成功 + return new SmsTemplateRespDTO() + .setId(response.getStr("TemplateCode")) + .setContent(response.getStr("TemplateContent")) + .setAuditStatus(convertSmsTemplateAuditStatus(response.getInt("TemplateStatus"))) + .setAuditReason(response.getStr("Reason")); + } + + @VisibleForTesting + Integer convertSmsTemplateAuditStatus(Integer templateStatus) { + switch (templateStatus) { + case 0: return SmsTemplateAuditStatusEnum.CHECKING.getStatus(); + case 1: return SmsTemplateAuditStatusEnum.SUCCESS.getStatus(); + case 2: return SmsTemplateAuditStatusEnum.FAIL.getStatus(); + default: throw new IllegalArgumentException(String.format("未知审核状态(%d)", templateStatus)); + } + } + + /** + * 请求阿里云短信 + * + * @see V3 版本请求体&签名机制 + * @param apiName 请求的 API 名称 + * @param queryParams 请求参数 + * @return 请求结果 + */ + private JSONObject request(String apiName, TreeMap queryParams) { + // 1. 请求参数 + String queryString = queryParams.entrySet().stream() + .map(entry -> percentCode(entry.getKey()) + "=" + percentCode(String.valueOf(entry.getValue()))) + .collect(Collectors.joining("&")); + + // 2.1 请求 Header + TreeMap headers = new TreeMap<>(); + headers.put("host", HOST); + headers.put("x-acs-version", VERSION); + headers.put("x-acs-action", apiName); + headers.put("x-acs-date", FastDateFormat.getInstance("yyyy-MM-dd'T'HH:mm:ss'Z'", TimeZone.getTimeZone("GMT")).format(new Date())); + headers.put("x-acs-signature-nonce", IdUtil.randomUUID()); + + // 2.2 构建签名 Header + StringBuilder canonicalHeaders = new StringBuilder(); // 构造请求头,多个规范化消息头,按照消息头名称(小写)的字符代码顺序以升序排列后拼接在一起 + StringBuilder signedHeadersBuilder = new StringBuilder(); // 已签名消息头列表,多个请求头名称(小写)按首字母升序排列并以英文分号(;)分隔 + headers.entrySet().stream().filter(entry -> entry.getKey().toLowerCase().startsWith("x-acs-") + || entry.getKey().equalsIgnoreCase("host") + || entry.getKey().equalsIgnoreCase("content-type")) + .sorted(Map.Entry.comparingByKey()).forEach(entry -> { + String lowerKey = entry.getKey().toLowerCase(); + canonicalHeaders.append(lowerKey).append(":").append(String.valueOf(entry.getValue()).trim()).append("\n"); + signedHeadersBuilder.append(lowerKey).append(";"); + }); + String signedHeaders = signedHeadersBuilder.substring(0, signedHeadersBuilder.length() - 1); + + // 3. 请求 Body + String requestBody = ""; // 短信 API 为 RPC 接口,query parameters 在 uri 中拼接,因此 request body 如果没有特殊要求,设置为空。 + String hashedRequestBody = DigestUtil.sha256Hex(requestBody); + + // 4. 构建 Authorization 签名 + String canonicalRequest = "POST" + "\n" + "/" + "\n" + queryString + "\n" + + canonicalHeaders + "\n" + signedHeaders + "\n" + hashedRequestBody; + String hashedCanonicalRequest = DigestUtil.sha256Hex(canonicalRequest); + String stringToSign = "ACS3-HMAC-SHA256" + "\n" + hashedCanonicalRequest; + String signature = SecureUtil.hmacSha256(properties.getApiSecret()).digestHex(stringToSign); // 计算签名 + headers.put("Authorization", "ACS3-HMAC-SHA256" + " " + "Credential=" + properties.getApiKey() + + ", " + "SignedHeaders=" + signedHeaders + ", " + "Signature=" + signature); + + // 5. 发起请求 + String responseBody = HttpUtils.post(URL + "?" + queryString, headers, requestBody); + return JSONUtil.parseObj(responseBody); + } + + /** + * 对指定的字符串进行 URL 编码,并对特定的字符进行替换,以符合URL编码规范 + * + * @param str 需要进行 URL 编码的字符串 + * @return 编码后的字符串 + */ + @SneakyThrows + private static String percentCode(String str) { + Assert.notNull(str, "str 不能为空"); + return URLEncoder.encode(str, StandardCharsets.UTF_8.name()) + .replace("+", "%20") // 加号 "+" 被替换为 "%20" + .replace("*", "%2A") // 星号 "*" 被替换为 "%2A" + .replace("%7E", "~"); // 波浪号 "%7E" 被替换为 "~" + } + +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/framework/sms/core/client/impl/DebugDingTalkSmsClient.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/framework/sms/core/client/impl/DebugDingTalkSmsClient.java new file mode 100644 index 0000000..22083f6 --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/framework/sms/core/client/impl/DebugDingTalkSmsClient.java @@ -0,0 +1,91 @@ +package com.tashow.cloud.system.framework.sms.core.client.impl; + +import cn.hutool.core.codec.Base64; +import cn.hutool.core.lang.Assert; +import cn.hutool.core.map.MapUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.crypto.digest.DigestUtil; +import cn.hutool.crypto.digest.HmacAlgorithm; +import cn.hutool.http.HttpUtil; +import com.tashow.cloud.common.core.KeyValue; +import com.tashow.cloud.common.util.collection.MapUtils; +import com.tashow.cloud.common.util.json.JsonUtils; +import com.tashow.cloud.system.framework.sms.core.client.dto.SmsReceiveRespDTO; +import com.tashow.cloud.system.framework.sms.core.client.dto.SmsSendRespDTO; +import com.tashow.cloud.system.framework.sms.core.client.dto.SmsTemplateRespDTO; +import com.tashow.cloud.system.framework.sms.core.enums.SmsTemplateAuditStatusEnum; +import com.tashow.cloud.system.framework.sms.core.property.SmsChannelProperties; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; + +/** + * 基于钉钉 WebHook 实现的调试的短信客户端实现类 + * + * 考虑到省钱,我们使用钉钉 WebHook 模拟发送短信,方便调试。 + * + * @author 芋道源码 + */ +public class DebugDingTalkSmsClient extends AbstractSmsClient { + + public DebugDingTalkSmsClient(SmsChannelProperties properties) { + super(properties); + Assert.notEmpty(properties.getApiKey(), "apiKey 不能为空"); + Assert.notEmpty(properties.getApiSecret(), "apiSecret 不能为空"); + } + + @Override + public SmsSendRespDTO sendSms(Long sendLogId, String mobile, + String apiTemplateId, List> templateParams) throws Throwable { + // 构建请求 + String url = buildUrl("robot/send"); + Map params = new HashMap<>(); + params.put("msgtype", "text"); + String content = String.format("【模拟短信】\n手机号:%s\n短信日志编号:%d\n模板参数:%s", + mobile, sendLogId, MapUtils.convertMap(templateParams)); + params.put("text", MapUtil.builder().put("content", content).build()); + // 执行请求 + String responseText = HttpUtil.post(url, JsonUtils.toJsonString(params)); + // 解析结果 + Map responseObj = JsonUtils.parseObject(responseText, Map.class); + String errorCode = MapUtil.getStr(responseObj, "errcode"); + return new SmsSendRespDTO().setSuccess(Objects.equals(errorCode, "0")).setSerialNo(StrUtil.uuid()) + .setApiCode(errorCode).setApiMsg(MapUtil.getStr(responseObj, "errorMsg")); + } + + /** + * 构建请求地址 + * + * 参见 文档 + * + * @param path 请求路径 + * @return 请求地址 + */ + @SuppressWarnings("SameParameterValue") + private String buildUrl(String path) { + // 生成 timestamp + long timestamp = System.currentTimeMillis(); + // 生成 sign + String secret = properties.getApiSecret(); + String stringToSign = timestamp + "\n" + secret; + byte[] signData = DigestUtil.hmac(HmacAlgorithm.HmacSHA256, StrUtil.bytes(secret)).digest(stringToSign); + String sign = Base64.encode(signData); + // 构建最终 URL + return String.format("https://oapi.dingtalk.com/%s?access_token=%s×tamp=%d&sign=%s", + path, properties.getApiKey(), timestamp, sign); + } + + @Override + public List parseSmsReceiveStatus(String text) { + throw new UnsupportedOperationException("模拟短信客户端,暂时无需解析回调"); + } + + @Override + public SmsTemplateRespDTO getSmsTemplate(String apiTemplateId) { + return new SmsTemplateRespDTO().setId(apiTemplateId).setContent("") + .setAuditStatus(SmsTemplateAuditStatusEnum.SUCCESS.getStatus()).setAuditReason(""); + } + +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/framework/sms/core/client/impl/HuaweiSmsClient.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/framework/sms/core/client/impl/HuaweiSmsClient.java new file mode 100644 index 0000000..f61f3d8 --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/framework/sms/core/client/impl/HuaweiSmsClient.java @@ -0,0 +1,166 @@ +package com.tashow.cloud.system.framework.sms.core.client.impl; + +import cn.hutool.core.collection.ListUtil; +import cn.hutool.core.date.format.FastDateFormat; +import cn.hutool.core.lang.Assert; +import cn.hutool.core.util.CharsetUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.crypto.SecureUtil; +import cn.hutool.http.HttpUtil; +import cn.hutool.json.JSONObject; +import cn.hutool.json.JSONUtil; +import com.tashow.cloud.common.core.KeyValue; +import com.tashow.cloud.common.util.http.HttpUtils; +import com.tashow.cloud.common.util.json.JsonUtils; +import com.tashow.cloud.system.framework.sms.core.client.dto.SmsReceiveRespDTO; +import com.tashow.cloud.system.framework.sms.core.client.dto.SmsSendRespDTO; +import com.tashow.cloud.system.framework.sms.core.client.dto.SmsTemplateRespDTO; +import com.tashow.cloud.system.framework.sms.core.enums.SmsTemplateAuditStatusEnum; +import com.tashow.cloud.system.framework.sms.core.property.SmsChannelProperties; +import lombok.extern.slf4j.Slf4j; + +import java.io.UnsupportedEncodingException; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.time.Instant; +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.util.*; + +import static cn.hutool.crypto.digest.DigestUtil.sha256Hex; +import static com.tashow.cloud.common.util.collection.CollectionUtils.convertList; + +/** + * 华为短信客户端的实现类 + * + * @author scholar + * @since 2024/6/02 11:55 + */ +@Slf4j +public class HuaweiSmsClient extends AbstractSmsClient { + + private static final String URL = "https://smsapi.cn-north-4.myhuaweicloud.com:443/sms/batchSendSms/v1";//APP接入地址+接口访问URI + private static final String HOST = "smsapi.cn-north-4.myhuaweicloud.com:443"; + private static final String SIGNEDHEADERS = "content-type;host;x-sdk-date"; + + private static final String RESPONSE_CODE_SUCCESS = "000000"; + + public HuaweiSmsClient(SmsChannelProperties properties) { + super(properties); + Assert.notEmpty(properties.getApiKey(), "apiKey 不能为空"); + Assert.notEmpty(properties.getApiSecret(), "apiSecret 不能为空"); + validateSender(properties); + } + + /** + * 参数校验华为云的 sender 通道号 + * + * 原因是:验华为云发放短信的时候,需要额外的参数 sender + * + * 解决方案:考虑到不破坏原有的 apiKey + apiSecret 的结构,所以将 secretId 拼接到 apiKey 字段中,格式为 "secretId sdkAppId"。 + * + * @param properties 配置 + */ + private static void validateSender(SmsChannelProperties properties) { + String combineKey = properties.getApiKey(); + Assert.notEmpty(combineKey, "apiKey 不能为空"); + String[] keys = combineKey.trim().split(" "); + Assert.isTrue(keys.length == 2, "华为云短信 apiKey 配置格式错误,请配置 为[accessKeyId sender]"); + } + + private String getAccessKey() { + return StrUtil.subBefore(properties.getApiKey(), " ", true); + } + + private String getSender() { + return StrUtil.subAfter(properties.getApiKey(), " ", true); + } + + @Override + public SmsSendRespDTO sendSms(Long sendLogId, String mobile, String apiTemplateId, + List> templateParams) throws Throwable { + StringBuilder requestBody = new StringBuilder(); + appendToBody(requestBody, "from=", getSender()); + appendToBody(requestBody, "&to=", mobile); + appendToBody(requestBody, "&templateId=", apiTemplateId); + appendToBody(requestBody, "&templateParas=", JsonUtils.toJsonString( + convertList(templateParams, kv -> String.valueOf(kv.getValue())))); + appendToBody(requestBody, "&statusCallback=", properties.getCallbackUrl()); + appendToBody(requestBody, "&extend=", String.valueOf(sendLogId)); + JSONObject response = request("/sms/batchSendSms/v1/", "POST", requestBody.toString()); + + // 2. 解析请求 + if (!response.containsKey("result")) { // 例如说:密钥不正确 + return new SmsSendRespDTO().setSuccess(false) + .setApiCode(response.getStr("code")) + .setApiMsg(response.getStr("description")); + } + JSONObject sendResult = response.getJSONArray("result").getJSONObject(0); + return new SmsSendRespDTO().setSuccess(RESPONSE_CODE_SUCCESS.equals(response.getStr("code"))) + .setSerialNo(sendResult.getStr("smsMsgId")).setApiCode(sendResult.getStr("status")); + } + + /** + * 请求华为云短信 + * + * @see https://support.huaweicloud.com/api-msgsms/sms_05_0046.html + * @param uri 请求 URI + * @param method 请求 Method + * @param requestBody 请求 Body + * @return 请求结果 + */ + private JSONObject request(String uri, String method, String requestBody) { + // 1.1 请求 Header + TreeMap headers = new TreeMap<>(); + headers.put("Content-Type", "application/x-www-form-urlencoded"); + String sdkDate = FastDateFormat.getInstance("yyyyMMdd'T'HHmmss'Z'", TimeZone.getTimeZone("UTC")).format(new Date()); + headers.put("X-Sdk-Date", sdkDate); + headers.put("host", HOST); + + // 1.2 构建签名 Header + String canonicalQueryString = ""; // 查询参数为空 + String canonicalHeaders = "content-type:application/x-www-form-urlencoded\n" + + "host:"+ HOST +"\n" + "x-sdk-date:" + sdkDate + "\n"; + String canonicalRequest = method + "\n" + uri + "\n" + canonicalQueryString + "\n" + + canonicalHeaders + "\n" + SIGNEDHEADERS + "\n" + sha256Hex(requestBody); + String stringToSign = "SDK-HMAC-SHA256" + "\n" + sdkDate + "\n" + sha256Hex(canonicalRequest); + String signature = SecureUtil.hmacSha256(properties.getApiSecret()).digestHex(stringToSign); // 计算签名 + headers.put("Authorization", "SDK-HMAC-SHA256" + " " + "Access=" + getAccessKey() + + ", " + "SignedHeaders=" + SIGNEDHEADERS + ", " + "Signature=" + signature); + + // 2. 发起请求 + String responseBody = HttpUtils.post(URL, headers, requestBody); + return JSONUtil.parseObj(responseBody); + } + + @Override + public List parseSmsReceiveStatus(String requestBody) { + Map params = HttpUtil.decodeParamMap(requestBody, StandardCharsets.UTF_8); + // 字段参考 https://support.huaweicloud.com/api-msgsms/sms_05_0003.html + return ListUtil.of(new SmsReceiveRespDTO() + .setSuccess("DELIVRD".equals(params.get("status"))) // 是否接收成功 + .setErrorCode(params.get("status")) // 状态报告编码 + .setErrorMsg(params.get("statusDesc")) + .setMobile(params.get("to")) // 手机号 + .setReceiveTime(LocalDateTime.ofInstant(Instant.parse(params.get("updateTime")), ZoneId.of("UTC"))) // 状态报告时间 + .setSerialNo(params.get("smsMsgId")) // 发送序列号 + .setLogId(Long.valueOf(params.get("extend")))); // 用户序列号 + } + + @Override + public SmsTemplateRespDTO getSmsTemplate(String apiTemplateId) throws Throwable { + // 华为短信模板查询和发送短信,是不同的两套 key 和 secret,与阿里、腾讯的区别较大,这里模板查询校验暂不实现 + String[] strs = apiTemplateId.split(" "); + Assert.isTrue(strs.length == 2, "格式不正确,需要满足:apiTemplateId sender"); + return new SmsTemplateRespDTO().setId(apiTemplateId).setContent(null) + .setAuditStatus(SmsTemplateAuditStatusEnum.SUCCESS.getStatus()).setAuditReason(null); + } + + @SuppressWarnings("CharsetObjectCanBeUsed") + private static void appendToBody(StringBuilder body, String key, String value) throws UnsupportedEncodingException { + if (StrUtil.isNotEmpty(value)) { + body.append(key).append(URLEncoder.encode(value, CharsetUtil.CHARSET_UTF_8.name())); + } + } + +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/framework/sms/core/client/impl/QiniuSmsClient.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/framework/sms/core/client/impl/QiniuSmsClient.java new file mode 100644 index 0000000..57aeb19 --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/framework/sms/core/client/impl/QiniuSmsClient.java @@ -0,0 +1,155 @@ +package com.tashow.cloud.system.framework.sms.core.client.impl; + +import cn.hutool.core.collection.CollStreamUtil; +import cn.hutool.core.date.DateUtil; +import cn.hutool.core.date.LocalDateTimeUtil; +import cn.hutool.core.lang.Assert; +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.crypto.SecureUtil; +import cn.hutool.crypto.digest.HmacAlgorithm; +import cn.hutool.json.JSONObject; +import cn.hutool.json.JSONUtil; +import com.tashow.cloud.common.core.KeyValue; +import com.tashow.cloud.common.util.http.HttpUtils; +import com.tashow.cloud.system.framework.sms.core.client.dto.SmsReceiveRespDTO; +import com.tashow.cloud.system.framework.sms.core.client.dto.SmsSendRespDTO; +import com.tashow.cloud.system.framework.sms.core.client.dto.SmsTemplateRespDTO; +import com.tashow.cloud.system.framework.sms.core.enums.SmsTemplateAuditStatusEnum; +import com.tashow.cloud.system.framework.sms.core.property.SmsChannelProperties; +import com.google.common.annotations.VisibleForTesting; +import lombok.extern.slf4j.Slf4j; + +import java.util.*; +import java.util.function.Function; + +import static com.tashow.cloud.common.util.collection.CollectionUtils.convertList; + +/** + * 七牛云短信客户端的实现类 + * + * @author scholar + * @since 2024/08/26 15:35 + */ +@Slf4j +public class QiniuSmsClient extends AbstractSmsClient { + + private static final String HOST = "sms.qiniuapi.com"; + + public QiniuSmsClient(SmsChannelProperties properties) { + super(properties); + Assert.notEmpty(properties.getApiKey(), "apiKey 不能为空"); + Assert.notEmpty(properties.getApiSecret(), "apiSecret 不能为空"); + } + + public SmsSendRespDTO sendSms(Long sendLogId, String mobile, String apiTemplateId, + List> templateParams) throws Throwable { + // 1. 执行请求 + // 参考链接 https://developer.qiniu.com/sms/5824/through-the-api-send-text-messages + LinkedHashMap body = new LinkedHashMap<>(); + body.put("template_id", apiTemplateId); + body.put("mobile", mobile); + body.put("parameters", CollStreamUtil.toMap(templateParams, KeyValue::getKey, KeyValue::getValue)); + body.put("seq", Long.toString(sendLogId)); + JSONObject response = request("POST", body, "/v1/message/single"); + + // 2. 解析请求 + if (ObjectUtil.isNotEmpty(response.getStr("error"))) { + // 短信请求失败 + return new SmsSendRespDTO().setSuccess(false) + .setApiCode(response.getStr("error")) + .setApiRequestId(response.getStr("request_id")) + .setApiMsg(response.getStr("message")); + } + return new SmsSendRespDTO().setSuccess(response.containsKey("message_id")) + .setSerialNo(response.getStr("message_id")); + } + + /** + * 请求七牛云短信 + * + * @see + * @param httpMethod http请求方法 + * @param body http请求消息体 + * @param path URL path + * @return 请求结果 + */ + private JSONObject request(String httpMethod, LinkedHashMap body, String path) { + String signDate = DateUtil.date().setTimeZone(TimeZone.getTimeZone("UTC")).toString("yyyyMMdd'T'HHmmss'Z'"); + // 1. 请求头 + Map header = new HashMap<>(4); + header.put("HOST", HOST); + header.put("Authorization", getSignature(httpMethod, path, body != null ? JSONUtil.toJsonStr(body) : "", signDate)); + header.put("Content-Type", "application/json"); + header.put("X-Qiniu-Date", signDate); + + // 2. 发起请求 + String responseBody; + if (Objects.equals(httpMethod, "POST")){ + responseBody = HttpUtils.post("https://" + HOST + path, header, JSONUtil.toJsonStr(body)); + } else { + responseBody = HttpUtils.get("https://" + HOST + path, header); + } + return JSONUtil.parseObj(responseBody); + } + + private String getSignature(String method, String path, String body, String signDate) { + StringBuilder dataToSign = new StringBuilder(); + dataToSign.append(method.toUpperCase()).append(" ").append(path) + .append("\nHost: ").append(HOST) + .append("\n").append("Content-Type").append(": ").append("application/json") + .append("\n").append("X-Qiniu-Date").append(": ").append(signDate) + .append("\n\n"); + if (ObjectUtil.isNotEmpty(body)) { + dataToSign.append(body); + } + String signature = SecureUtil.hmac(HmacAlgorithm.HmacSHA1, properties.getApiSecret()) + .digestBase64(dataToSign.toString(), true); + return "Qiniu " + properties.getApiKey() + ":" + signature; + } + + @Override + public List parseSmsReceiveStatus(String text) { + JSONObject status = JSONUtil.parseObj(text); + // 字段参考 https://developer.qiniu.com/sms/5910/message-push + return convertList(status.getJSONArray("items"), new Function() { + + @Override + public SmsReceiveRespDTO apply(Object item) { + JSONObject statusObj = (JSONObject) item; + return new SmsReceiveRespDTO() + .setSuccess("DELIVRD".equals(statusObj.getStr("status"))) // 是否接收成功 + .setErrorMsg(statusObj.getStr("status")) // 状态报告编码 + .setMobile(statusObj.getStr("mobile")) // 手机号 + .setReceiveTime(LocalDateTimeUtil.of(statusObj.getLong("delivrd_at") * 1000L)) // 状态报告时间 + .setSerialNo(statusObj.getStr("message_id")) // 发送序列号 + .setLogId(statusObj.getLong("seq")); // 用户序列号 + } + + }); + } + + @Override + public SmsTemplateRespDTO getSmsTemplate(String apiTemplateId) throws Throwable { + // 1. 执行请求 + // 参考链接 https://developer.qiniu.com/sms/5969/query-a-single-template + JSONObject response = request("GET", null, "/v1/template/" + apiTemplateId); + + // 2.2 解析请求 + return new SmsTemplateRespDTO() + .setId(response.getStr("id")) + .setContent(response.getStr("template")) + .setAuditStatus(convertSmsTemplateAuditStatus(response.getStr("audit_status"))) + .setAuditReason(response.getStr("reject_reason")); + } + + @VisibleForTesting + Integer convertSmsTemplateAuditStatus(String templateStatus) { + switch (templateStatus) { + case "passed": return SmsTemplateAuditStatusEnum.SUCCESS.getStatus(); + case "reviewing": return SmsTemplateAuditStatusEnum.CHECKING.getStatus(); + case "rejected": return SmsTemplateAuditStatusEnum.FAIL.getStatus(); + default: + throw new IllegalArgumentException(String.format("未知审核状态(%str)", templateStatus)); + } + } +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/framework/sms/core/client/impl/SmsClientFactoryImpl.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/framework/sms/core/client/impl/SmsClientFactoryImpl.java new file mode 100644 index 0000000..036f75a --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/framework/sms/core/client/impl/SmsClientFactoryImpl.java @@ -0,0 +1,90 @@ +package com.tashow.cloud.system.framework.sms.core.client.impl; + +import com.tashow.cloud.system.framework.sms.core.client.SmsClient; +import com.tashow.cloud.system.framework.sms.core.client.SmsClientFactory; +import com.tashow.cloud.system.framework.sms.core.enums.SmsChannelEnum; +import com.tashow.cloud.system.framework.sms.core.property.SmsChannelProperties; +import lombok.extern.slf4j.Slf4j; +import org.springframework.util.Assert; +import org.springframework.validation.annotation.Validated; + +import java.util.Arrays; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +/** + * 短信客户端工厂接口 + * + * @author zzf + */ +@Validated +@Slf4j +public class SmsClientFactoryImpl implements SmsClientFactory { + + /** + * 短信客户端 Map + * key:渠道编号,使用 {@link SmsChannelProperties#getId()} + */ + private final ConcurrentMap channelIdClients = new ConcurrentHashMap<>(); + + /** + * 短信客户端 Map + * key:渠道编码,使用 {@link SmsChannelProperties#getCode()} ()} + * + * 注意,一些场景下,需要获得某个渠道类型的客户端,所以需要使用它。 + * 例如说,解析短信接收结果,是相对通用的,不需要使用某个渠道编号的 {@link #channelIdClients} + */ + private final ConcurrentMap channelCodeClients = new ConcurrentHashMap<>(); + + public SmsClientFactoryImpl() { + // 初始化 channelCodeClients 集合 + Arrays.stream(SmsChannelEnum.values()).forEach(channel -> { + // 创建一个空的 SmsChannelProperties 对象 + SmsChannelProperties properties = new SmsChannelProperties().setCode(channel.getCode()) + .setApiKey("default default").setApiSecret("default"); + // 创建 Sms 客户端 + AbstractSmsClient smsClient = createSmsClient(properties); + channelCodeClients.put(channel.getCode(), smsClient); + }); + } + + @Override + public SmsClient getSmsClient(Long channelId) { + return channelIdClients.get(channelId); + } + + @Override + public SmsClient getSmsClient(String channelCode) { + return channelCodeClients.get(channelCode); + } + + @Override + public SmsClient createOrUpdateSmsClient(SmsChannelProperties properties) { + AbstractSmsClient client = channelIdClients.get(properties.getId()); + if (client == null) { + client = this.createSmsClient(properties); + client.init(); + channelIdClients.put(client.getId(), client); + } else { + client.refresh(properties); + } + return client; + } + + private AbstractSmsClient createSmsClient(SmsChannelProperties properties) { + SmsChannelEnum channelEnum = SmsChannelEnum.getByCode(properties.getCode()); + Assert.notNull(channelEnum, String.format("渠道类型(%s) 为空", channelEnum)); + // 创建客户端 + switch (channelEnum) { + case ALIYUN: return new AliyunSmsClient(properties); + case DEBUG_DING_TALK: return new DebugDingTalkSmsClient(properties); + case TENCENT: return new TencentSmsClient(properties); + case HUAWEI: return new HuaweiSmsClient(properties); + case QINIU: return new QiniuSmsClient(properties); + } + // 创建失败,错误日志 + 抛出异常 + log.error("[createSmsClient][配置({}) 找不到合适的客户端实现]", properties); + throw new IllegalArgumentException(String.format("配置(%s) 找不到合适的客户端实现", properties)); + } + +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/framework/sms/core/client/impl/TencentSmsClient.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/framework/sms/core/client/impl/TencentSmsClient.java new file mode 100644 index 0000000..02293a5 --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/framework/sms/core/client/impl/TencentSmsClient.java @@ -0,0 +1,201 @@ +package com.tashow.cloud.system.framework.sms.core.client.impl; + +import cn.hutool.core.date.format.FastDateFormat; +import cn.hutool.core.lang.Assert; +import cn.hutool.core.util.HexUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.crypto.digest.DigestUtil; +import cn.hutool.crypto.digest.HmacAlgorithm; +import cn.hutool.json.JSONArray; +import cn.hutool.json.JSONObject; +import cn.hutool.json.JSONUtil; +import com.tashow.cloud.common.core.KeyValue; +import com.tashow.cloud.common.util.collection.ArrayUtils; +import com.tashow.cloud.common.util.http.HttpUtils; +import com.tashow.cloud.system.framework.sms.core.client.dto.SmsReceiveRespDTO; +import com.tashow.cloud.system.framework.sms.core.client.dto.SmsSendRespDTO; +import com.tashow.cloud.system.framework.sms.core.client.dto.SmsTemplateRespDTO; +import com.tashow.cloud.system.framework.sms.core.enums.SmsTemplateAuditStatusEnum; +import com.tashow.cloud.system.framework.sms.core.property.SmsChannelProperties; +import com.google.common.annotations.VisibleForTesting; + +import java.nio.charset.StandardCharsets; +import java.util.*; + +import static cn.hutool.crypto.digest.DigestUtil.sha256Hex; +import static com.tashow.cloud.common.util.collection.CollectionUtils.convertList; + +/** + * 腾讯云短信功能实现 + * + * 参见 文档 + * + * @author shiwp + */ +public class TencentSmsClient extends AbstractSmsClient { + + private static final String HOST = "sms.tencentcloudapi.com"; + private static final String VERSION = "2021-01-11"; + private static final String REGION = "ap-guangzhou"; + + /** + * 调用成功 code + */ + public static final String API_CODE_SUCCESS = "Ok"; + + /** + * 是否国际/港澳台短信: + * + * 0:表示国内短信。 + * 1:表示国际/港澳台短信。 + */ + private static final long INTERNATIONAL_CHINA = 0L; + + public TencentSmsClient(SmsChannelProperties properties) { + super(properties); + Assert.notEmpty(properties.getApiSecret(), "apiSecret 不能为空"); + validateSdkAppId(properties); + } + + /** + * 参数校验腾讯云的 SDK AppId + * + * 原因是:腾讯云发放短信的时候,需要额外的参数 sdkAppId + * + * 解决方案:考虑到不破坏原有的 apiKey + apiSecret 的结构,所以将 secretId 拼接到 apiKey 字段中,格式为 "secretId sdkAppId"。 + * + * @param properties 配置 + */ + private static void validateSdkAppId(SmsChannelProperties properties) { + String combineKey = properties.getApiKey(); + Assert.notEmpty(combineKey, "apiKey 不能为空"); + String[] keys = combineKey.trim().split(" "); + Assert.isTrue(keys.length == 2, "腾讯云短信 apiKey 配置格式错误,请配置 为[secretId sdkAppId]"); + } + + private String getSdkAppId() { + return StrUtil.subAfter(properties.getApiKey(), " ", true); + } + + private String getApiKey() { + return StrUtil.subBefore(properties.getApiKey(), " ", true); + } + + @Override + public SmsSendRespDTO sendSms(Long sendLogId, String mobile, + String apiTemplateId, List> templateParams) throws Throwable { + // 1. 执行请求 + // 参考链接 https://cloud.tencent.com/document/product/382/55981 + TreeMap body = new TreeMap<>(); + body.put("PhoneNumberSet", new String[]{mobile}); + body.put("SmsSdkAppId", getSdkAppId()); + body.put("SignName", properties.getSignature()); + body.put("TemplateId", apiTemplateId); + body.put("TemplateParamSet", ArrayUtils.toArray(templateParams, param -> String.valueOf(param.getValue()))); + JSONObject response = request("SendSms", body); + + // 2. 解析请求 + JSONObject responseResult = response.getJSONObject("Response"); + JSONObject error = responseResult.getJSONObject("Error"); + if (error != null) { + return new SmsSendRespDTO().setSuccess(false) + .setApiRequestId(responseResult.getStr("RequestId")) + .setApiCode(error.getStr("Code")) + .setApiMsg(error.getStr("Message")); + } + JSONObject sendResult = responseResult.getJSONArray("SendStatusSet").getJSONObject(0); + return new SmsSendRespDTO().setSuccess(Objects.equals(API_CODE_SUCCESS, sendResult.getStr("Code"))) + .setApiRequestId(responseResult.getStr("RequestId")) + .setSerialNo(sendResult.getStr("SerialNo")) + .setApiMsg(sendResult.getStr("Message")); + } + + @Override + public List parseSmsReceiveStatus(String text) { + JSONArray statuses = JSONUtil.parseArray(text); + // 字段参考 + return convertList(statuses, status -> { + JSONObject statusObj = (JSONObject) status; + return new SmsReceiveRespDTO() + .setSuccess("SUCCESS".equals(statusObj.getStr("report_status"))) // 是否接收成功 + .setErrorCode(statusObj.getStr("errmsg")) // 状态报告编码 + .setMobile(statusObj.getStr("mobile")) // 手机号 + .setReceiveTime(statusObj.getLocalDateTime("user_receive_time", null)) // 状态报告时间 + .setSerialNo(statusObj.getStr("sid")); // 发送序列号 + }); + } + + @Override + public SmsTemplateRespDTO getSmsTemplate(String apiTemplateId) throws Throwable { + // 1. 构建请求 + // 参考链接 https://cloud.tencent.com/document/product/382/52067 + TreeMap body = new TreeMap<>(); + body.put("International", INTERNATIONAL_CHINA); + body.put("TemplateIdSet", new Integer[]{Integer.valueOf(apiTemplateId)}); + JSONObject response = request("DescribeSmsTemplateList", body); + + // 2. 解析请求 + JSONObject statusResult = response.getJSONObject("Response") + .getJSONArray("DescribeTemplateStatusSet").getJSONObject(0); + return new SmsTemplateRespDTO().setId(apiTemplateId) + .setContent(statusResult.get("TemplateContent").toString()) + .setAuditStatus(convertSmsTemplateAuditStatus(statusResult.getInt("StatusCode"))) + .setAuditReason(statusResult.get("ReviewReply").toString()); + } + + @VisibleForTesting + Integer convertSmsTemplateAuditStatus(int templateStatus) { + switch (templateStatus) { + case 1: return SmsTemplateAuditStatusEnum.CHECKING.getStatus(); + case 0: return SmsTemplateAuditStatusEnum.SUCCESS.getStatus(); + case -1: return SmsTemplateAuditStatusEnum.FAIL.getStatus(); + default: throw new IllegalArgumentException(String.format("未知审核状态(%d)", templateStatus)); + } + } + + /** + * 请求腾讯云短信 + * + * @see 签名方法 v3 + * + * @param action 请求的 API 名称 + * @param body 请求参数 + * @return 请求结果 + */ + private JSONObject request(String action, TreeMap body) { + // 1.1 请求 Header + Map headers = new HashMap<>(); + headers.put("Content-Type", "application/json; charset=utf-8"); + headers.put("Host", HOST); + headers.put("X-TC-Action", action); + Date now = new Date(); + String nowStr = FastDateFormat.getInstance("yyyy-MM-dd", TimeZone.getTimeZone("UTC")).format(now); + headers.put("X-TC-Timestamp", String.valueOf(now.getTime() / 1000)); + headers.put("X-TC-Version", VERSION); + headers.put("X-TC-Region", REGION); + + // 1.2 构建签名 Header + String canonicalQueryString = ""; + String canonicalHeaders = "content-type:application/json; charset=utf-8\n" + + "host:" + HOST + "\n" + "x-tc-action:" + action.toLowerCase() + "\n"; + String signedHeaders = "content-type;host;x-tc-action"; + String canonicalRequest = "POST" + "\n" + "/" + "\n" + canonicalQueryString + "\n" + canonicalHeaders + "\n" + + signedHeaders + "\n" + sha256Hex(JSONUtil.toJsonStr(body)); + String credentialScope = nowStr + "/" + "sms" + "/" + "tc3_request"; + String stringToSign = "TC3-HMAC-SHA256" + "\n" + now.getTime() / 1000 + "\n" + credentialScope + "\n" + + sha256Hex(canonicalRequest); + byte[] secretService = hmac256(hmac256(("TC3" + properties.getApiSecret()).getBytes(StandardCharsets.UTF_8), nowStr), "sms"); + String signature = HexUtil.encodeHexStr(hmac256(hmac256(secretService, "tc3_request"), stringToSign)); + headers.put("Authorization", "TC3-HMAC-SHA256" + " " + "Credential=" + getApiKey() + "/" + credentialScope + ", " + + "SignedHeaders=" + signedHeaders + ", " + "Signature=" + signature); + + // 2. 发起请求 + String responseBody = HttpUtils.post("https://" + HOST, headers, JSONUtil.toJsonStr(body)); + return JSONUtil.parseObj(responseBody); + } + + private static byte[] hmac256(byte[] key, String msg) { + return DigestUtil.hmac(HmacAlgorithm.HmacSHA256, key).digest(msg); + } + +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/framework/sms/core/enums/SmsChannelEnum.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/framework/sms/core/enums/SmsChannelEnum.java new file mode 100644 index 0000000..7ae3995 --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/framework/sms/core/enums/SmsChannelEnum.java @@ -0,0 +1,38 @@ +package com.tashow.cloud.system.framework.sms.core.enums; + +import cn.hutool.core.util.ArrayUtil; +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * 短信渠道枚举 + * + * @author zzf + * @since 2021/1/25 10:56 + */ +@Getter +@AllArgsConstructor +public enum SmsChannelEnum { + + DEBUG_DING_TALK("DEBUG_DING_TALK", "调试(钉钉)"), + ALIYUN("ALIYUN", "阿里云"), + TENCENT("TENCENT", "腾讯云"), + HUAWEI("HUAWEI", "华为云"), + QINIU("QINIU", "七牛云"), + ; + + /** + * 编码 + */ + private final String code; + /** + * 名字 + */ + private final String name; + + public static SmsChannelEnum getByCode(String code) { + return ArrayUtil.firstMatch(o -> o.getCode().equals(code), values()); + } + +} + diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/framework/sms/core/enums/SmsTemplateAuditStatusEnum.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/framework/sms/core/enums/SmsTemplateAuditStatusEnum.java new file mode 100644 index 0000000..9e3a93f --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/framework/sms/core/enums/SmsTemplateAuditStatusEnum.java @@ -0,0 +1,21 @@ +package com.tashow.cloud.system.framework.sms.core.enums; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * 短信模板的审核状态枚举 + * + * @author 芋道源码 + */ +@AllArgsConstructor +@Getter +public enum SmsTemplateAuditStatusEnum { + + CHECKING(1), + SUCCESS(2), + FAIL(3); + + private final Integer status; + +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/framework/sms/core/property/SmsChannelProperties.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/framework/sms/core/property/SmsChannelProperties.java new file mode 100644 index 0000000..cefabe5 --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/framework/sms/core/property/SmsChannelProperties.java @@ -0,0 +1,51 @@ +package com.tashow.cloud.system.framework.sms.core.property; + +import com.tashow.cloud.system.framework.sms.core.enums.SmsChannelEnum; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import lombok.Data; +import org.springframework.validation.annotation.Validated; + +/** + * 短信渠道配置类 + * + * @author zzf + * @since 2021/1/25 17:01 + */ +@Data +@Validated +public class SmsChannelProperties { + + /** + * 渠道编号 + */ + @NotNull(message = "短信渠道 ID 不能为空") + private Long id; + /** + * 短信签名 + */ + @NotEmpty(message = "短信签名不能为空") + private String signature; + /** + * 渠道编码 + * + * 枚举 {@link SmsChannelEnum} + */ + @NotEmpty(message = "渠道编码不能为空") + private String code; + /** + * 短信 API 的账号 + */ + @NotEmpty(message = "短信 API 的账号不能为空") + private String apiKey; + /** + * 短信 API 的密钥 + */ + @NotEmpty(message = "短信 API 的密钥不能为空") + private String apiSecret; + /** + * 短信发送回调 URL + */ + private String callbackUrl; + +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/job/demo/DemoJob.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/job/demo/DemoJob.java new file mode 100644 index 0000000..39b8829 --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/job/demo/DemoJob.java @@ -0,0 +1,16 @@ +package com.tashow.cloud.system.job.demo; + +import com.tashow.cloud.tenant.core.job.TenantJob; +import com.xxl.job.core.handler.annotation.XxlJob; +import org.springframework.stereotype.Component; + +@Component +public class DemoJob { + + @XxlJob("demoJob") + @TenantJob + public void execute() { + System.out.println("美滋滋"); + } + +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/job/package-info.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/job/package-info.java new file mode 100644 index 0000000..6e074cb --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/job/package-info.java @@ -0,0 +1 @@ +package com.tashow.cloud.system.job; diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/mq/consumer/mail/MailSendConsumer.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/mq/consumer/mail/MailSendConsumer.java new file mode 100644 index 0000000..b5a138d --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/mq/consumer/mail/MailSendConsumer.java @@ -0,0 +1,33 @@ +package com.tashow.cloud.system.mq.consumer.mail; + +import com.tashow.cloud.system.mq.message.mail.MailSendMessage; +import com.tashow.cloud.system.service.mail.MailSendService; +import com.tashow.cloud.system.mq.message.mail.MailSendMessage; +import com.tashow.cloud.system.service.mail.MailSendService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.context.event.EventListener; +import org.springframework.scheduling.annotation.Async; +import org.springframework.stereotype.Component; + +import jakarta.annotation.Resource; + +/** + * 针对 {@link MailSendMessage} 的消费者 + * + * @author 芋道源码 + */ +@Component +@Slf4j +public class MailSendConsumer { + + @Resource + private MailSendService mailSendService; + + @EventListener + @Async // Spring Event 默认在 Producer 发送的线程,通过 @Async 实现异步 + public void onMessage(MailSendMessage message) { + log.info("[onMessage][消息内容({})]", message); + mailSendService.doSendMail(message); + } + +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/mq/consumer/sms/SmsSendConsumer.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/mq/consumer/sms/SmsSendConsumer.java new file mode 100644 index 0000000..7ed01f9 --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/mq/consumer/sms/SmsSendConsumer.java @@ -0,0 +1,33 @@ +package com.tashow.cloud.system.mq.consumer.sms; + +import com.tashow.cloud.system.mq.message.sms.SmsSendMessage; +import com.tashow.cloud.system.service.sms.SmsSendService; +import com.tashow.cloud.system.mq.message.sms.SmsSendMessage; +import com.tashow.cloud.system.service.sms.SmsSendService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.context.event.EventListener; +import org.springframework.scheduling.annotation.Async; +import org.springframework.stereotype.Component; + +import jakarta.annotation.Resource; + +/** + * 针对 {@link SmsSendMessage} 的消费者 + * + * @author zzf + */ +@Component +@Slf4j +public class SmsSendConsumer { + + @Resource + private SmsSendService smsSendService; + + @EventListener + @Async // Spring Event 默认在 Producer 发送的线程,通过 @Async 实现异步 + public void onMessage(SmsSendMessage message) { + log.info("[onMessage][消息内容({})]", message); + smsSendService.doSendSms(message); + } + +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/mq/message/mail/MailSendMessage.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/mq/message/mail/MailSendMessage.java new file mode 100644 index 0000000..5ad1dfc --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/mq/message/mail/MailSendMessage.java @@ -0,0 +1,47 @@ +package com.tashow.cloud.system.mq.message.mail; + +import lombok.Data; + +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; + +/** + * 邮箱发送消息 + * + * @author 芋道源码 + */ +@Data +public class MailSendMessage { + + /** + * 邮件日志编号 + */ + @NotNull(message = "邮件日志编号不能为空") + private Long logId; + /** + * 接收邮件地址 + */ + @NotNull(message = "接收邮件地址不能为空") + private String mail; + /** + * 邮件账号编号 + */ + @NotNull(message = "邮件账号编号不能为空") + private Long accountId; + + /** + * 邮件发件人 + */ + private String nickname; + /** + * 邮件标题 + */ + @NotEmpty(message = "邮件标题不能为空") + private String title; + /** + * 邮件内容 + */ + @NotEmpty(message = "邮件内容不能为空") + private String content; + +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/mq/message/sms/SmsSendMessage.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/mq/message/sms/SmsSendMessage.java new file mode 100644 index 0000000..6aa0cd3 --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/mq/message/sms/SmsSendMessage.java @@ -0,0 +1,42 @@ +package com.tashow.cloud.system.mq.message.sms; + +import com.tashow.cloud.common.core.KeyValue; +import lombok.Data; + +import jakarta.validation.constraints.NotNull; +import java.util.List; + +/** + * 短信发送消息 + * + * @author 芋道源码 + */ +@Data +public class SmsSendMessage { + + /** + * 短信日志编号 + */ + @NotNull(message = "短信日志编号不能为空") + private Long logId; + /** + * 手机号 + */ + @NotNull(message = "手机号不能为空") + private String mobile; + /** + * 短信渠道编号 + */ + @NotNull(message = "短信渠道编号不能为空") + private Long channelId; + /** + * 短信 API 的模板编号 + */ + @NotNull(message = "短信 API 的模板编号不能为空") + private String apiTemplateId; + /** + * 短信模板参数 + */ + private List> templateParams; + +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/mq/producer/mail/MailProducer.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/mq/producer/mail/MailProducer.java new file mode 100644 index 0000000..3ae694d --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/mq/producer/mail/MailProducer.java @@ -0,0 +1,41 @@ +package com.tashow.cloud.system.mq.producer.mail; + +import com.tashow.cloud.system.mq.message.mail.MailSendMessage; +import lombok.extern.slf4j.Slf4j; +import org.springframework.context.ApplicationContext; +import org.springframework.stereotype.Component; + +import jakarta.annotation.Resource; + +/** + * Mail 邮件相关消息的 Producer + * + * @author wangjingyi + * @since 2021/4/19 13:33 + */ +@Slf4j +@Component +public class MailProducer { + + @Resource + private ApplicationContext applicationContext; + + /** + * 发送 {@link MailSendMessage} 消息 + * + * @param sendLogId 发送日志编码 + * @param mail 接收邮件地址 + * @param accountId 邮件账号编号 + * @param nickname 邮件发件人 + * @param title 邮件标题 + * @param content 邮件内容 + */ + public void sendMailSendMessage(Long sendLogId, String mail, Long accountId, + String nickname, String title, String content) { + MailSendMessage message = new MailSendMessage() + .setLogId(sendLogId).setMail(mail).setAccountId(accountId) + .setNickname(nickname).setTitle(title).setContent(content); + applicationContext.publishEvent(message); + } + +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/mq/producer/sms/SmsProducer.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/mq/producer/sms/SmsProducer.java new file mode 100644 index 0000000..de94220 --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/mq/producer/sms/SmsProducer.java @@ -0,0 +1,41 @@ +package com.tashow.cloud.system.mq.producer.sms; + +import com.tashow.cloud.common.core.KeyValue; +import com.tashow.cloud.system.mq.message.sms.SmsSendMessage; +import lombok.extern.slf4j.Slf4j; +import org.springframework.context.ApplicationContext; +import org.springframework.stereotype.Component; + +import jakarta.annotation.Resource; +import java.util.List; + +/** + * Sms 短信相关消息的 Producer + * + * @author zzf + * @since 2021/3/9 16:35 + */ +@Slf4j +@Component +public class SmsProducer { + + @Resource + private ApplicationContext applicationContext; + + /** + * 发送 {@link SmsSendMessage} 消息 + * + * @param logId 短信日志编号 + * @param mobile 手机号 + * @param channelId 渠道编号 + * @param apiTemplateId 短信模板编号 + * @param templateParams 短信模板参数 + */ + public void sendSmsSendMessage(Long logId, String mobile, + Long channelId, String apiTemplateId, List> templateParams) { + SmsSendMessage message = new SmsSendMessage().setLogId(logId).setMobile(mobile); + message.setChannelId(channelId).setApiTemplateId(apiTemplateId).setTemplateParams(templateParams); + applicationContext.publishEvent(message); + } + +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/package-info.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/package-info.java new file mode 100644 index 0000000..f2f323a --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/package-info.java @@ -0,0 +1,8 @@ +/** + * system 模块下,我们放通用业务,支撑上层的核心业务。 + * 例如说:用户、部门、权限、数据字典等等 + * + * 1. Controller URL:以 /system/ 开头,避免和其它 Module 冲突 + * 2. DataObject 表名:以 system_ 开头,方便在数据库中区分 + */ +package com.tashow.cloud.system; diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/service/auth/AdminAuthService.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/service/auth/AdminAuthService.java new file mode 100644 index 0000000..ad120c6 --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/service/auth/AdminAuthService.java @@ -0,0 +1,87 @@ +package com.tashow.cloud.system.service.auth; + +import com.tashow.cloud.system.dal.dataobject.user.AdminUserDO; +import com.tashow.cloud.system.controller.admin.auth.vo.*; +import jakarta.validation.Valid; + +/** + * 管理后台的认证 Service 接口 + * + * 提供用户的登录、登出的能力 + * + * @author 芋道源码 + */ +public interface AdminAuthService { + + /** + * 验证账号 + 密码。如果通过,则返回用户 + * + * @param username 账号 + * @param password 密码 + * @return 用户 + */ + AdminUserDO authenticate(String username, String password); + + /** + * 账号登录 + * + * @param reqVO 登录信息 + * @return 登录结果 + */ + AuthLoginRespVO login(@Valid AuthLoginReqVO reqVO); + + /** + * 基于 token 退出登录 + * + * @param token token + * @param logType 登出类型 + */ + void logout(String token, Integer logType); + + /** + * 短信验证码发送 + * + * @param reqVO 发送请求 + */ + void sendSmsCode(AuthSmsSendReqVO reqVO); + + /** + * 短信登录 + * + * @param reqVO 登录信息 + * @return 登录结果 + */ + AuthLoginRespVO smsLogin(AuthSmsLoginReqVO reqVO); + + /** + * 社交快捷登录,使用 code 授权码 + * + * @param reqVO 登录信息 + * @return 登录结果 + */ + AuthLoginRespVO socialLogin(@Valid AuthSocialLoginReqVO reqVO); + + /** + * 刷新访问令牌 + * + * @param refreshToken 刷新令牌 + * @return 登录结果 + */ + AuthLoginRespVO refreshToken(String refreshToken); + + /** + * 用户注册 + * + * @param createReqVO 注册用户 + * @return 注册结果 + */ + AuthLoginRespVO register(AuthRegisterReqVO createReqVO); + + /** + * 重置密码 + * + * @param reqVO 验证码信息 + */ + void resetPassword(AuthResetPasswordReqVO reqVO); + +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/service/auth/AdminAuthServiceImpl.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/service/auth/AdminAuthServiceImpl.java new file mode 100644 index 0000000..a05da92 --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/service/auth/AdminAuthServiceImpl.java @@ -0,0 +1,304 @@ +package com.tashow.cloud.system.service.auth; + +import cn.hutool.core.util.ObjectUtil; +import com.tashow.cloud.common.enums.CommonStatusEnum; +import com.tashow.cloud.common.enums.UserTypeEnum; +import com.tashow.cloud.common.util.monitor.TracerUtils; +import com.tashow.cloud.common.util.servlet.ServletUtils; +import com.tashow.cloud.common.util.validation.ValidationUtils; +import com.tashow.cloud.systemapi.api.logger.dto.LoginLogCreateReqDTO; +import com.tashow.cloud.systemapi.api.sms.SmsCodeApi; +import com.tashow.cloud.systemapi.api.sms.dto.code.SmsCodeUseReqDTO; +import com.tashow.cloud.systemapi.api.social.dto.SocialUserBindReqDTO; +import com.tashow.cloud.systemapi.api.social.dto.SocialUserRespDTO; +import com.tashow.cloud.system.convert.auth.AuthConvert; +import com.tashow.cloud.system.dal.dataobject.oauth2.OAuth2AccessTokenDO; +import com.tashow.cloud.system.dal.dataobject.user.AdminUserDO; +import com.tashow.cloud.systemapi.enums.logger.LoginLogTypeEnum; +import com.tashow.cloud.systemapi.enums.logger.LoginResultEnum; +import com.tashow.cloud.systemapi.enums.oauth2.OAuth2ClientConstants; +import com.tashow.cloud.systemapi.enums.sms.SmsSceneEnum; +import com.tashow.cloud.system.service.logger.LoginLogService; +import com.tashow.cloud.system.service.member.MemberService; +import com.tashow.cloud.system.service.oauth2.OAuth2TokenService; +import com.tashow.cloud.system.service.social.SocialUserService; +import com.tashow.cloud.system.service.user.AdminUserService; +import com.google.common.annotations.VisibleForTesting; +import com.tashow.cloud.system.controller.admin.auth.vo.*; +import com.tashow.cloud.systemapi.enums.ErrorCodeConstants; +import com.xingyuv.captcha.model.common.ResponseModel; +import com.xingyuv.captcha.model.vo.CaptchaVO; +import com.xingyuv.captcha.service.CaptchaService; +import jakarta.annotation.Resource; +import jakarta.validation.Validator; +import lombok.Setter; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.Objects; + +import static com.tashow.cloud.common.exception.util.ServiceExceptionUtil.exception; +import static com.tashow.cloud.common.util.servlet.ServletUtils.getClientIP; + + +/** + * Auth Service 实现类 + * + * @author 芋道源码 + */ +@Service +@Slf4j +public class AdminAuthServiceImpl implements AdminAuthService { + + @Resource + private AdminUserService userService; + @Resource + private LoginLogService loginLogService; + @Resource + private OAuth2TokenService oauth2TokenService; + @Resource + private SocialUserService socialUserService; + @Resource + private MemberService memberService; + @Resource + private Validator validator; + @Resource + private CaptchaService captchaService; + @Resource + private SmsCodeApi smsCodeApi; + + /** + * 验证码的开关,默认为 true + */ + @Value("${tashow.captcha.enable:true}") + @Setter // 为了单测:开启或者关闭验证码 + private Boolean captchaEnable; + + @Override + public AdminUserDO authenticate(String username, String password) { + final LoginLogTypeEnum logTypeEnum = LoginLogTypeEnum.LOGIN_USERNAME; + // 校验账号是否存在 + AdminUserDO user = userService.getUserByUsername(username); + if (user == null) { + createLoginLog(null, username, logTypeEnum, LoginResultEnum.BAD_CREDENTIALS); + throw exception(ErrorCodeConstants.AUTH_LOGIN_BAD_CREDENTIALS); + } + if (!userService.isPasswordMatch(password, user.getPassword())) { + createLoginLog(user.getId(), username, logTypeEnum, LoginResultEnum.BAD_CREDENTIALS); + throw exception(ErrorCodeConstants.AUTH_LOGIN_BAD_CREDENTIALS); + } + // 校验是否禁用 + if (CommonStatusEnum.isDisable(user.getStatus())) { + createLoginLog(user.getId(), username, logTypeEnum, LoginResultEnum.USER_DISABLED); + throw exception(ErrorCodeConstants.AUTH_LOGIN_USER_DISABLED); + } + return user; + } + + @Override + public AuthLoginRespVO login(AuthLoginReqVO reqVO) { + // 校验验证码 + validateCaptcha(reqVO); + + // 使用账号密码,进行登录 + AdminUserDO user = authenticate(reqVO.getUsername(), reqVO.getPassword()); + + // 如果 socialType 非空,说明需要绑定社交用户 + if (reqVO.getSocialType() != null) { + socialUserService.bindSocialUser(new SocialUserBindReqDTO(user.getId(), getUserType().getValue(), + reqVO.getSocialType(), reqVO.getSocialCode(), reqVO.getSocialState())); + } + // 创建 Token 令牌,记录登录日志 + return createTokenAfterLoginSuccess(user.getId(), reqVO.getUsername(), LoginLogTypeEnum.LOGIN_USERNAME); + } + + @Override + public void sendSmsCode(AuthSmsSendReqVO reqVO) { + // 如果是重置密码场景,需要校验图形验证码是否正确 + if (Objects.equals(SmsSceneEnum.ADMIN_MEMBER_RESET_PASSWORD.getScene(), reqVO.getScene())) { + ResponseModel response = doValidateCaptcha(reqVO); + if (!response.isSuccess()) { + throw exception(ErrorCodeConstants.AUTH_REGISTER_CAPTCHA_CODE_ERROR, response.getRepMsg()); + } + } + + // 登录场景,验证是否存在 + if (userService.getUserByMobile(reqVO.getMobile()) == null) { + throw exception(ErrorCodeConstants.AUTH_MOBILE_NOT_EXISTS); + } + // 发送验证码 + smsCodeApi.sendSmsCode(AuthConvert.INSTANCE.convert(reqVO).setCreateIp(getClientIP())); + } + + @Override + public AuthLoginRespVO smsLogin(AuthSmsLoginReqVO reqVO) { + // 校验验证码 + smsCodeApi.useSmsCode(AuthConvert.INSTANCE.convert(reqVO, SmsSceneEnum.ADMIN_MEMBER_LOGIN.getScene(), getClientIP())).checkError(); + + // 获得用户信息 + AdminUserDO user = userService.getUserByMobile(reqVO.getMobile()); + if (user == null) { + throw exception(ErrorCodeConstants.USER_NOT_EXISTS); + } + + // 创建 Token 令牌,记录登录日志 + return createTokenAfterLoginSuccess(user.getId(), reqVO.getMobile(), LoginLogTypeEnum.LOGIN_MOBILE); + } + + private void createLoginLog(Long userId, String username, + LoginLogTypeEnum logTypeEnum, LoginResultEnum loginResult) { + // 插入登录日志 + LoginLogCreateReqDTO reqDTO = new LoginLogCreateReqDTO(); + reqDTO.setLogType(logTypeEnum.getType()); + reqDTO.setTraceId(TracerUtils.getTraceId()); + reqDTO.setUserId(userId); + reqDTO.setUserType(getUserType().getValue()); + reqDTO.setUsername(username); + reqDTO.setUserAgent(ServletUtils.getUserAgent()); + reqDTO.setUserIp(getClientIP()); + reqDTO.setResult(loginResult.getResult()); + loginLogService.createLoginLog(reqDTO); + // 更新最后登录时间 + if (userId != null && Objects.equals(LoginResultEnum.SUCCESS.getResult(), loginResult.getResult())) { + userService.updateUserLogin(userId, getClientIP()); + } + } + + @Override + public AuthLoginRespVO socialLogin(AuthSocialLoginReqVO reqVO) { + // 使用 code 授权码,进行登录。然后,获得到绑定的用户编号 + SocialUserRespDTO socialUser = socialUserService.getSocialUserByCode(UserTypeEnum.ADMIN.getValue(), reqVO.getType(), + reqVO.getCode(), reqVO.getState()); + if (socialUser == null || socialUser.getUserId() == null) { + throw exception(ErrorCodeConstants.AUTH_THIRD_LOGIN_NOT_BIND); + } + + // 获得用户 + AdminUserDO user = userService.getUser(socialUser.getUserId()); + if (user == null) { + throw exception(ErrorCodeConstants.USER_NOT_EXISTS); + } + + // 创建 Token 令牌,记录登录日志 + return createTokenAfterLoginSuccess(user.getId(), user.getUsername(), LoginLogTypeEnum.LOGIN_SOCIAL); + } + + @VisibleForTesting + void validateCaptcha(AuthLoginReqVO reqVO) { + ResponseModel response = doValidateCaptcha(reqVO); + // 校验验证码 + if (!response.isSuccess()) { + // 创建登录失败日志(验证码不正确) + createLoginLog(null, reqVO.getUsername(), LoginLogTypeEnum.LOGIN_USERNAME, LoginResultEnum.CAPTCHA_CODE_ERROR); + throw exception(ErrorCodeConstants.AUTH_LOGIN_CAPTCHA_CODE_ERROR, response.getRepMsg()); + } + } + + private ResponseModel doValidateCaptcha(CaptchaVerificationReqVO reqVO) { + // 如果验证码关闭,则不进行校验 + if (!captchaEnable) { + return ResponseModel.success(); + } + ValidationUtils.validate(validator, reqVO, CaptchaVerificationReqVO.CodeEnableGroup.class); + CaptchaVO captchaVO = new CaptchaVO(); + captchaVO.setCaptchaVerification(reqVO.getCaptchaVerification()); + return captchaService.verification(captchaVO); + } + + private AuthLoginRespVO createTokenAfterLoginSuccess(Long userId, String username, LoginLogTypeEnum logType) { + // 插入登陆日志 + createLoginLog(userId, username, logType, LoginResultEnum.SUCCESS); + // 创建访问令牌 + OAuth2AccessTokenDO accessTokenDO = oauth2TokenService.createAccessToken(userId, getUserType().getValue(), + OAuth2ClientConstants.CLIENT_ID_DEFAULT, null); + // 构建返回结果 + return AuthConvert.INSTANCE.convert(accessTokenDO); + } + + @Override + public AuthLoginRespVO refreshToken(String refreshToken) { + OAuth2AccessTokenDO accessTokenDO = oauth2TokenService.refreshAccessToken(refreshToken, OAuth2ClientConstants.CLIENT_ID_DEFAULT); + return AuthConvert.INSTANCE.convert(accessTokenDO); + } + + @Override + public void logout(String token, Integer logType) { + // 删除访问令牌 + OAuth2AccessTokenDO accessTokenDO = oauth2TokenService.removeAccessToken(token); + if (accessTokenDO == null) { + return; + } + // 删除成功,则记录登出日志 + createLogoutLog(accessTokenDO.getUserId(), accessTokenDO.getUserType(), logType); + } + + private void createLogoutLog(Long userId, Integer userType, Integer logType) { + LoginLogCreateReqDTO reqDTO = new LoginLogCreateReqDTO(); + reqDTO.setLogType(logType); + reqDTO.setTraceId(TracerUtils.getTraceId()); + reqDTO.setUserId(userId); + reqDTO.setUserType(userType); + if (ObjectUtil.equal(getUserType().getValue(), userType)) { + reqDTO.setUsername(getUsername(userId)); + } else { + reqDTO.setUsername(memberService.getMemberUserMobile(userId)); + } + reqDTO.setUserAgent(ServletUtils.getUserAgent()); + reqDTO.setUserIp(getClientIP()); + reqDTO.setResult(LoginResultEnum.SUCCESS.getResult()); + loginLogService.createLoginLog(reqDTO); + } + + private String getUsername(Long userId) { + if (userId == null) { + return null; + } + AdminUserDO user = userService.getUser(userId); + return user != null ? user.getUsername() : null; + } + + private UserTypeEnum getUserType() { + return UserTypeEnum.ADMIN; + } + + @Override + public AuthLoginRespVO register(AuthRegisterReqVO registerReqVO) { + // 1. 校验验证码 + validateCaptcha(registerReqVO); + + // 2. 校验用户名是否已存在 + Long userId = userService.registerUser(registerReqVO); + + // 3. 创建 Token 令牌,记录登录日志 + return createTokenAfterLoginSuccess(userId, registerReqVO.getUsername(), LoginLogTypeEnum.LOGIN_USERNAME); + } + + @VisibleForTesting + void validateCaptcha(AuthRegisterReqVO reqVO) { + ResponseModel response = doValidateCaptcha(reqVO); + // 验证不通过 + if (!response.isSuccess()) { + throw exception(ErrorCodeConstants.AUTH_REGISTER_CAPTCHA_CODE_ERROR, response.getRepMsg()); + } + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void resetPassword(AuthResetPasswordReqVO reqVO) { + AdminUserDO userByMobile = userService.getUserByMobile(reqVO.getMobile()); + if (userByMobile == null) { + throw exception(ErrorCodeConstants.USER_MOBILE_NOT_EXISTS); + } + + smsCodeApi.useSmsCode(new SmsCodeUseReqDTO() + .setCode(reqVO.getCode()) + .setMobile(reqVO.getMobile()) + .setScene(SmsSceneEnum.ADMIN_MEMBER_RESET_PASSWORD.getScene()) + .setUsedIp(getClientIP()) + ).checkError(); + + userService.updateUserPassword(userByMobile.getId(), reqVO.getPassword()); + } +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/service/dept/DeptService.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/service/dept/DeptService.java new file mode 100644 index 0000000..c0f0cf8 --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/service/dept/DeptService.java @@ -0,0 +1,119 @@ +package com.tashow.cloud.system.service.dept; + +import com.tashow.cloud.common.util.collection.CollectionUtils; +import com.tashow.cloud.system.controller.admin.dept.vo.dept.DeptListReqVO; +import com.tashow.cloud.system.controller.admin.dept.vo.dept.DeptSaveReqVO; +import com.tashow.cloud.system.dal.dataobject.dept.DeptDO; +import com.tashow.cloud.system.controller.admin.dept.vo.dept.DeptListReqVO; +import com.tashow.cloud.system.controller.admin.dept.vo.dept.DeptSaveReqVO; + +import java.util.*; + +/** + * 部门 Service 接口 + * + * @author 芋道源码 + */ +public interface DeptService { + + /** + * 创建部门 + * + * @param createReqVO 部门信息 + * @return 部门编号 + */ + Long createDept(DeptSaveReqVO createReqVO); + + /** + * 更新部门 + * + * @param updateReqVO 部门信息 + */ + void updateDept(DeptSaveReqVO updateReqVO); + + /** + * 删除部门 + * + * @param id 部门编号 + */ + void deleteDept(Long id); + + /** + * 获得部门信息 + * + * @param id 部门编号 + * @return 部门信息 + */ + DeptDO getDept(Long id); + + /** + * 获得部门信息数组 + * + * @param ids 部门编号数组 + * @return 部门信息数组 + */ + List getDeptList(Collection ids); + + /** + * 筛选部门列表 + * + * @param reqVO 筛选条件请求 VO + * @return 部门列表 + */ + List getDeptList(DeptListReqVO reqVO); + + /** + * 获得指定编号的部门 Map + * + * @param ids 部门编号数组 + * @return 部门 Map + */ + default Map getDeptMap(Collection ids) { + List list = getDeptList(ids); + return CollectionUtils.convertMap(list, DeptDO::getId); + } + + /** + * 获得指定部门的所有子部门 + * + * @param id 部门编号 + * @return 子部门列表 + */ + default List getChildDeptList(Long id) { + return getChildDeptList(Collections.singleton(id)); + } + + /** + * 获得指定部门的所有子部门 + * + * @param ids 部门编号数组 + * @return 子部门列表 + */ + List getChildDeptList(Collection ids); + + /** + * 获得指定领导者的部门列表 + * + * @param id 领导者编号 + * @return 部门列表 + */ + List getDeptListByLeaderUserId(Long id); + + /** + * 获得所有子部门,从缓存中 + * + * @param id 父部门编号 + * @return 子部门列表 + */ + Set getChildDeptIdListFromCache(Long id); + + /** + * 校验部门们是否有效。如下情况,视为无效: + * 1. 部门编号不存在 + * 2. 部门被禁用 + * + * @param ids 角色编号数组 + */ + void validateDeptList(Collection ids); + +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/service/dept/DeptServiceImpl.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/service/dept/DeptServiceImpl.java new file mode 100644 index 0000000..e23a7f5 --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/service/dept/DeptServiceImpl.java @@ -0,0 +1,224 @@ +package com.tashow.cloud.system.service.dept; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.util.ObjectUtil; +import com.tashow.cloud.common.enums.CommonStatusEnum; +import com.tashow.cloud.common.util.object.BeanUtils; +import com.tashow.cloud.permission.core.annotation.DataPermission; +import com.tashow.cloud.system.controller.admin.dept.vo.dept.DeptListReqVO; +import com.tashow.cloud.system.controller.admin.dept.vo.dept.DeptSaveReqVO; +import com.tashow.cloud.system.dal.dataobject.dept.DeptDO; +import com.tashow.cloud.system.dal.mysql.dept.DeptMapper; +import com.tashow.cloud.system.dal.redis.RedisKeyConstants; +import com.google.common.annotations.VisibleForTesting; +import com.tashow.cloud.systemapi.enums.ErrorCodeConstants; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import org.springframework.cache.annotation.CacheEvict; +import org.springframework.cache.annotation.Cacheable; +import org.springframework.stereotype.Service; +import org.springframework.validation.annotation.Validated; + +import java.util.*; + +import static com.tashow.cloud.common.exception.util.ServiceExceptionUtil.exception; +import static com.tashow.cloud.common.util.collection.CollectionUtils.convertSet; + + +/** + * 部门 Service 实现类 + * + * @author 芋道源码 + */ +@Service +@Validated +@Slf4j +public class DeptServiceImpl implements DeptService { + + @Resource + private DeptMapper deptMapper; + + @Override + @CacheEvict(cacheNames = RedisKeyConstants.DEPT_CHILDREN_ID_LIST, + allEntries = true) // allEntries 清空所有缓存,因为操作一个部门,涉及到多个缓存 + public Long createDept(DeptSaveReqVO createReqVO) { + if (createReqVO.getParentId() == null) { + createReqVO.setParentId(DeptDO.PARENT_ID_ROOT); + } + // 校验父部门的有效性 + validateParentDept(null, createReqVO.getParentId()); + // 校验部门名的唯一性 + validateDeptNameUnique(null, createReqVO.getParentId(), createReqVO.getName()); + + // 插入部门 + DeptDO dept = BeanUtils.toBean(createReqVO, DeptDO.class); + deptMapper.insert(dept); + return dept.getId(); + } + + @Override + @CacheEvict(cacheNames = RedisKeyConstants.DEPT_CHILDREN_ID_LIST, + allEntries = true) // allEntries 清空所有缓存,因为操作一个部门,涉及到多个缓存 + public void updateDept(DeptSaveReqVO updateReqVO) { + if (updateReqVO.getParentId() == null) { + updateReqVO.setParentId(DeptDO.PARENT_ID_ROOT); + } + // 校验自己存在 + validateDeptExists(updateReqVO.getId()); + // 校验父部门的有效性 + validateParentDept(updateReqVO.getId(), updateReqVO.getParentId()); + // 校验部门名的唯一性 + validateDeptNameUnique(updateReqVO.getId(), updateReqVO.getParentId(), updateReqVO.getName()); + + // 更新部门 + DeptDO updateObj = BeanUtils.toBean(updateReqVO, DeptDO.class); + deptMapper.updateById(updateObj); + } + + @Override + @CacheEvict(cacheNames = RedisKeyConstants.DEPT_CHILDREN_ID_LIST, + allEntries = true) // allEntries 清空所有缓存,因为操作一个部门,涉及到多个缓存 + public void deleteDept(Long id) { + // 校验是否存在 + validateDeptExists(id); + // 校验是否有子部门 + if (deptMapper.selectCountByParentId(id) > 0) { + throw exception(ErrorCodeConstants.DEPT_EXITS_CHILDREN); + } + // 删除部门 + deptMapper.deleteById(id); + } + + @VisibleForTesting + void validateDeptExists(Long id) { + if (id == null) { + return; + } + DeptDO dept = deptMapper.selectById(id); + if (dept == null) { + throw exception(ErrorCodeConstants.DEPT_NOT_FOUND); + } + } + + @VisibleForTesting + void validateParentDept(Long id, Long parentId) { + if (parentId == null || DeptDO.PARENT_ID_ROOT.equals(parentId)) { + return; + } + // 1. 不能设置自己为父部门 + if (Objects.equals(id, parentId)) { + throw exception(ErrorCodeConstants.DEPT_PARENT_ERROR); + } + // 2. 父部门不存在 + DeptDO parentDept = deptMapper.selectById(parentId); + if (parentDept == null) { + throw exception(ErrorCodeConstants.DEPT_PARENT_NOT_EXITS); + } + // 3. 递归校验父部门,如果父部门是自己的子部门,则报错,避免形成环路 + if (id == null) { // id 为空,说明新增,不需要考虑环路 + return; + } + for (int i = 0; i < Short.MAX_VALUE; i++) { + // 3.1 校验环路 + parentId = parentDept.getParentId(); + if (Objects.equals(id, parentId)) { + throw exception(ErrorCodeConstants.DEPT_PARENT_IS_CHILD); + } + // 3.2 继续递归下一级父部门 + if (parentId == null || DeptDO.PARENT_ID_ROOT.equals(parentId)) { + break; + } + parentDept = deptMapper.selectById(parentId); + if (parentDept == null) { + break; + } + } + } + + @VisibleForTesting + void validateDeptNameUnique(Long id, Long parentId, String name) { + DeptDO dept = deptMapper.selectByParentIdAndName(parentId, name); + if (dept == null) { + return; + } + // 如果 id 为空,说明不用比较是否为相同 id 的部门 + if (id == null) { + throw exception(ErrorCodeConstants.DEPT_NAME_DUPLICATE); + } + if (ObjectUtil.notEqual(dept.getId(), id)) { + throw exception(ErrorCodeConstants.DEPT_NAME_DUPLICATE); + } + } + + @Override + public DeptDO getDept(Long id) { + return deptMapper.selectById(id); + } + + @Override + public List getDeptList(Collection ids) { + if (CollUtil.isEmpty(ids)) { + return Collections.emptyList(); + } + return deptMapper.selectBatchIds(ids); + } + + @Override + public List getDeptList(DeptListReqVO reqVO) { + List list = deptMapper.selectList(reqVO); + list.sort(Comparator.comparing(DeptDO::getSort)); + return list; + } + + @Override + public List getChildDeptList(Collection ids) { + List children = new LinkedList<>(); + // 遍历每一层 + Collection parentIds = ids; + for (int i = 0; i < Short.MAX_VALUE; i++) { // 使用 Short.MAX_VALUE 避免 bug 场景下,存在死循环 + // 查询当前层,所有的子部门 + List depts = deptMapper.selectListByParentId(parentIds); + // 1. 如果没有子部门,则结束遍历 + if (CollUtil.isEmpty(depts)) { + break; + } + // 2. 如果有子部门,继续遍历 + children.addAll(depts); + parentIds = convertSet(depts, DeptDO::getId); + } + return children; + } + + @Override + public List getDeptListByLeaderUserId(Long id) { + return deptMapper.selectListByLeaderUserId(id); + } + + @Override + @DataPermission(enable = false) // 禁用数据权限,避免建立不正确的缓存 + @Cacheable(cacheNames = RedisKeyConstants.DEPT_CHILDREN_ID_LIST, key = "#id") + public Set getChildDeptIdListFromCache(Long id) { + List children = getChildDeptList(id); + return convertSet(children, DeptDO::getId); + } + + @Override + public void validateDeptList(Collection ids) { + if (CollUtil.isEmpty(ids)) { + return; + } + // 获得科室信息 + Map deptMap = getDeptMap(ids); + // 校验 + ids.forEach(id -> { + DeptDO dept = deptMap.get(id); + if (dept == null) { + throw exception(ErrorCodeConstants.DEPT_NOT_FOUND); + } + if (!CommonStatusEnum.ENABLE.getStatus().equals(dept.getStatus())) { + throw exception(ErrorCodeConstants.DEPT_NOT_ENABLE, dept.getName()); + } + }); + } + +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/service/dept/PostService.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/service/dept/PostService.java new file mode 100644 index 0000000..e2cfce4 --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/service/dept/PostService.java @@ -0,0 +1,86 @@ +package com.tashow.cloud.system.service.dept; + +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.system.controller.admin.dept.vo.post.PostPageReqVO; +import com.tashow.cloud.system.controller.admin.dept.vo.post.PostSaveReqVO; +import com.tashow.cloud.system.dal.dataobject.dept.PostDO; +import com.tashow.cloud.system.controller.admin.dept.vo.post.PostPageReqVO; +import com.tashow.cloud.system.controller.admin.dept.vo.post.PostSaveReqVO; +import org.springframework.lang.Nullable; + +import java.util.Collection; +import java.util.List; + +/** + * 岗位 Service 接口 + * + * @author 芋道源码 + */ +public interface PostService { + + /** + * 创建岗位 + * + * @param createReqVO 岗位信息 + * @return 岗位编号 + */ + Long createPost(PostSaveReqVO createReqVO); + + /** + * 更新岗位 + * + * @param updateReqVO 岗位信息 + */ + void updatePost(PostSaveReqVO updateReqVO); + + /** + * 删除岗位信息 + * + * @param id 岗位编号 + */ + void deletePost(Long id); + + /** + * 获得岗位列表 + * + * @param ids 岗位编号数组 + * @return 部门列表 + */ + List getPostList(@Nullable Collection ids); + + /** + * 获得符合条件的岗位列表 + * + * @param ids 岗位编号数组。如果为空,不进行筛选 + * @param statuses 状态数组。如果为空,不进行筛选 + * @return 部门列表 + */ + List getPostList(@Nullable Collection ids, + @Nullable Collection statuses); + + /** + * 获得岗位分页列表 + * + * @param reqVO 分页条件 + * @return 部门分页列表 + */ + PageResult getPostPage(PostPageReqVO reqVO); + + /** + * 获得岗位信息 + * + * @param id 岗位编号 + * @return 岗位信息 + */ + PostDO getPost(Long id); + + /** + * 校验岗位们是否有效。如下情况,视为无效: + * 1. 岗位编号不存在 + * 2. 岗位被禁用 + * + * @param ids 岗位编号数组 + */ + void validatePostList(Collection ids); + +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/service/dept/PostServiceImpl.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/service/dept/PostServiceImpl.java new file mode 100644 index 0000000..733457b --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/service/dept/PostServiceImpl.java @@ -0,0 +1,156 @@ +package com.tashow.cloud.system.service.dept; + +import cn.hutool.core.collection.CollUtil; +import com.tashow.cloud.common.enums.CommonStatusEnum; +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.common.util.object.BeanUtils; +import com.tashow.cloud.system.controller.admin.dept.vo.post.PostPageReqVO; +import com.tashow.cloud.system.controller.admin.dept.vo.post.PostSaveReqVO; +import com.tashow.cloud.system.dal.dataobject.dept.PostDO; +import com.tashow.cloud.system.dal.mysql.dept.PostMapper; +import com.tashow.cloud.system.controller.admin.dept.vo.post.PostPageReqVO; +import com.tashow.cloud.system.controller.admin.dept.vo.post.PostSaveReqVO; +import com.tashow.cloud.systemapi.enums.ErrorCodeConstants; +import org.springframework.stereotype.Service; +import org.springframework.validation.annotation.Validated; + +import jakarta.annotation.Resource; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +import static com.tashow.cloud.common.exception.util.ServiceExceptionUtil.exception; +import static com.tashow.cloud.common.util.collection.CollectionUtils.convertMap; + + +/** + * 岗位 Service 实现类 + * + * @author 芋道源码 + */ +@Service +@Validated +public class PostServiceImpl implements PostService { + + @Resource + private PostMapper postMapper; + + @Override + public Long createPost(PostSaveReqVO createReqVO) { + // 校验正确性 + validatePostForCreateOrUpdate(null, createReqVO.getName(), createReqVO.getCode()); + + // 插入岗位 + PostDO post = BeanUtils.toBean(createReqVO, PostDO.class); + postMapper.insert(post); + return post.getId(); + } + + @Override + public void updatePost(PostSaveReqVO updateReqVO) { + // 校验正确性 + validatePostForCreateOrUpdate(updateReqVO.getId(), updateReqVO.getName(), updateReqVO.getCode()); + + // 更新岗位 + PostDO updateObj = BeanUtils.toBean(updateReqVO, PostDO.class); + postMapper.updateById(updateObj); + } + + @Override + public void deletePost(Long id) { + // 校验是否存在 + validatePostExists(id); + // 删除部门 + postMapper.deleteById(id); + } + + private void validatePostForCreateOrUpdate(Long id, String name, String code) { + // 校验自己存在 + validatePostExists(id); + // 校验岗位名的唯一性 + validatePostNameUnique(id, name); + // 校验岗位编码的唯一性 + validatePostCodeUnique(id, code); + } + + private void validatePostNameUnique(Long id, String name) { + PostDO post = postMapper.selectByName(name); + if (post == null) { + return; + } + // 如果 id 为空,说明不用比较是否为相同 id 的岗位 + if (id == null) { + throw exception(ErrorCodeConstants.POST_NAME_DUPLICATE); + } + if (!post.getId().equals(id)) { + throw exception(ErrorCodeConstants.POST_NAME_DUPLICATE); + } + } + + private void validatePostCodeUnique(Long id, String code) { + PostDO post = postMapper.selectByCode(code); + if (post == null) { + return; + } + // 如果 id 为空,说明不用比较是否为相同 id 的岗位 + if (id == null) { + throw exception(ErrorCodeConstants.POST_CODE_DUPLICATE); + } + if (!post.getId().equals(id)) { + throw exception(ErrorCodeConstants.POST_CODE_DUPLICATE); + } + } + + private void validatePostExists(Long id) { + if (id == null) { + return; + } + if (postMapper.selectById(id) == null) { + throw exception(ErrorCodeConstants.POST_NOT_FOUND); + } + } + + @Override + public List getPostList(Collection ids) { + if (CollUtil.isEmpty(ids)) { + return Collections.emptyList(); + } + return postMapper.selectBatchIds(ids); + } + + @Override + public List getPostList(Collection ids, Collection statuses) { + return postMapper.selectList(ids, statuses); + } + + @Override + public PageResult getPostPage(PostPageReqVO reqVO) { + return postMapper.selectPage(reqVO); + } + + @Override + public PostDO getPost(Long id) { + return postMapper.selectById(id); + } + + @Override + public void validatePostList(Collection ids) { + if (CollUtil.isEmpty(ids)) { + return; + } + // 获得岗位信息 + List posts = postMapper.selectBatchIds(ids); + Map postMap = convertMap(posts, PostDO::getId); + // 校验 + ids.forEach(id -> { + PostDO post = postMap.get(id); + if (post == null) { + throw exception(ErrorCodeConstants.POST_NOT_FOUND); + } + if (!CommonStatusEnum.ENABLE.getStatus().equals(post.getStatus())) { + throw exception(ErrorCodeConstants.POST_NOT_ENABLE, post.getName()); + } + }); + } +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/service/dict/DictDataService.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/service/dict/DictDataService.java new file mode 100644 index 0000000..fff14e9 --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/service/dict/DictDataService.java @@ -0,0 +1,112 @@ +package com.tashow.cloud.system.service.dict; + +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.system.controller.admin.dict.vo.data.DictDataPageReqVO; +import com.tashow.cloud.system.controller.admin.dict.vo.data.DictDataSaveReqVO; +import com.tashow.cloud.system.dal.dataobject.dict.DictDataDO; +import com.tashow.cloud.system.controller.admin.dict.vo.data.DictDataPageReqVO; +import com.tashow.cloud.system.controller.admin.dict.vo.data.DictDataSaveReqVO; +import org.springframework.lang.Nullable; + +import java.util.Collection; +import java.util.List; + +/** + * 字典数据 Service 接口 + * + * @author ruoyi + */ +public interface DictDataService { + + /** + * 创建字典数据 + * + * @param createReqVO 字典数据信息 + * @return 字典数据编号 + */ + Long createDictData(DictDataSaveReqVO createReqVO); + + /** + * 更新字典数据 + * + * @param updateReqVO 字典数据信息 + */ + void updateDictData(DictDataSaveReqVO updateReqVO); + + /** + * 删除字典数据 + * + * @param id 字典数据编号 + */ + void deleteDictData(Long id); + + /** + * 获得字典数据列表 + * + * @param status 状态 + * @param dictType 字典类型 + * @return 字典数据全列表 + */ + List getDictDataList(@Nullable Integer status, @Nullable String dictType); + + /** + * 获得字典数据分页列表 + * + * @param pageReqVO 分页请求 + * @return 字典数据分页列表 + */ + PageResult getDictDataPage(DictDataPageReqVO pageReqVO); + + /** + * 获得字典数据详情 + * + * @param id 字典数据编号 + * @return 字典数据 + */ + DictDataDO getDictData(Long id); + + /** + * 获得指定字典类型的数据数量 + * + * @param dictType 字典类型 + * @return 数据数量 + */ + long getDictDataCountByDictType(String dictType); + + /** + * 校验字典数据们是否有效。如下情况,视为无效: + * 1. 字典数据不存在 + * 2. 字典数据被禁用 + * + * @param dictType 字典类型 + * @param values 字典数据值的数组 + */ + void validateDictDataList(String dictType, Collection values); + + /** + * 获得指定的字典数据 + * + * @param dictType 字典类型 + * @param value 字典数据值 + * @return 字典数据 + */ + DictDataDO getDictData(String dictType, String value); + + /** + * 解析获得指定的字典数据,从缓存中 + * + * @param dictType 字典类型 + * @param label 字典数据标签 + * @return 字典数据 + */ + DictDataDO parseDictData(String dictType, String label); + + /** + * 获得指定数据类型的字典数据列表 + * + * @param dictType 字典类型 + * @return 字典数据列表 + */ + List getDictDataListByDictType(String dictType); + +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/service/dict/DictDataServiceImpl.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/service/dict/DictDataServiceImpl.java new file mode 100644 index 0000000..c20a093 --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/service/dict/DictDataServiceImpl.java @@ -0,0 +1,181 @@ +package com.tashow.cloud.system.service.dict; + +import cn.hutool.core.collection.CollUtil; +import com.tashow.cloud.common.enums.CommonStatusEnum; +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.common.util.collection.CollectionUtils; +import com.tashow.cloud.common.util.object.BeanUtils; +import com.tashow.cloud.system.controller.admin.dict.vo.data.DictDataPageReqVO; +import com.tashow.cloud.system.controller.admin.dict.vo.data.DictDataSaveReqVO; +import com.tashow.cloud.system.dal.dataobject.dict.DictDataDO; +import com.tashow.cloud.system.dal.dataobject.dict.DictTypeDO; +import com.tashow.cloud.system.dal.mysql.dict.DictDataMapper; +import com.google.common.annotations.VisibleForTesting; +import com.tashow.cloud.system.controller.admin.dict.vo.data.DictDataPageReqVO; +import com.tashow.cloud.system.controller.admin.dict.vo.data.DictDataSaveReqVO; +import com.tashow.cloud.systemapi.enums.ErrorCodeConstants; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +import java.util.Collection; +import java.util.Comparator; +import java.util.List; +import java.util.Map; + +import static com.tashow.cloud.common.exception.util.ServiceExceptionUtil.exception; + +/** + * 字典数据 Service 实现类 + * + * @author ruoyi + */ +@Service +@Slf4j +public class DictDataServiceImpl implements DictDataService { + + /** + * 排序 dictType > sort + */ + private static final Comparator COMPARATOR_TYPE_AND_SORT = Comparator + .comparing(DictDataDO::getDictType) + .thenComparingInt(DictDataDO::getSort); + + @Resource + private DictTypeService dictTypeService; + + @Resource + private DictDataMapper dictDataMapper; + + @Override + public List getDictDataList(Integer status, String dictType) { + List list = dictDataMapper.selectListByStatusAndDictType(status, dictType); + list.sort(COMPARATOR_TYPE_AND_SORT); + return list; + } + + @Override + public PageResult getDictDataPage(DictDataPageReqVO pageReqVO) { + return dictDataMapper.selectPage(pageReqVO); + } + + @Override + public DictDataDO getDictData(Long id) { + return dictDataMapper.selectById(id); + } + + @Override + public Long createDictData(DictDataSaveReqVO createReqVO) { + // 校验字典类型有效 + validateDictTypeExists(createReqVO.getDictType()); + // 校验字典数据的值的唯一性 + validateDictDataValueUnique(null, createReqVO.getDictType(), createReqVO.getValue()); + + // 插入字典类型 + DictDataDO dictData = BeanUtils.toBean(createReqVO, DictDataDO.class); + dictDataMapper.insert(dictData); + return dictData.getId(); + } + + @Override + public void updateDictData(DictDataSaveReqVO updateReqVO) { + // 校验自己存在 + validateDictDataExists(updateReqVO.getId()); + // 校验字典类型有效 + validateDictTypeExists(updateReqVO.getDictType()); + // 校验字典数据的值的唯一性 + validateDictDataValueUnique(updateReqVO.getId(), updateReqVO.getDictType(), updateReqVO.getValue()); + + // 更新字典类型 + DictDataDO updateObj = BeanUtils.toBean(updateReqVO, DictDataDO.class); + dictDataMapper.updateById(updateObj); + } + + @Override + public void deleteDictData(Long id) { + // 校验是否存在 + validateDictDataExists(id); + + // 删除字典数据 + dictDataMapper.deleteById(id); + } + + @Override + public long getDictDataCountByDictType(String dictType) { + return dictDataMapper.selectCountByDictType(dictType); + } + + @VisibleForTesting + public void validateDictDataValueUnique(Long id, String dictType, String value) { + DictDataDO dictData = dictDataMapper.selectByDictTypeAndValue(dictType, value); + if (dictData == null) { + return; + } + // 如果 id 为空,说明不用比较是否为相同 id 的字典数据 + if (id == null) { + throw exception(ErrorCodeConstants.DICT_DATA_VALUE_DUPLICATE); + } + if (!dictData.getId().equals(id)) { + throw exception(ErrorCodeConstants.DICT_DATA_VALUE_DUPLICATE); + } + } + + @VisibleForTesting + public void validateDictDataExists(Long id) { + if (id == null) { + return; + } + DictDataDO dictData = dictDataMapper.selectById(id); + if (dictData == null) { + throw exception(ErrorCodeConstants.DICT_DATA_NOT_EXISTS); + } + } + + @VisibleForTesting + public void validateDictTypeExists(String type) { + DictTypeDO dictType = dictTypeService.getDictType(type); + if (dictType == null) { + throw exception(ErrorCodeConstants.DICT_TYPE_NOT_EXISTS); + } + if (!CommonStatusEnum.ENABLE.getStatus().equals(dictType.getStatus())) { + throw exception(ErrorCodeConstants.DICT_TYPE_NOT_ENABLE); + } + } + + @Override + public void validateDictDataList(String dictType, Collection values) { + if (CollUtil.isEmpty(values)) { + return; + } + Map dictDataMap = CollectionUtils.convertMap( + dictDataMapper.selectByDictTypeAndValues(dictType, values), DictDataDO::getValue); + // 校验 + values.forEach(value -> { + DictDataDO dictData = dictDataMap.get(value); + if (dictData == null) { + throw exception(ErrorCodeConstants.DICT_DATA_NOT_EXISTS); + } + if (!CommonStatusEnum.ENABLE.getStatus().equals(dictData.getStatus())) { + throw exception(ErrorCodeConstants.DICT_DATA_NOT_ENABLE, dictData.getLabel()); + } + }); + } + + @Override + public DictDataDO getDictData(String dictType, String value) { + return dictDataMapper.selectByDictTypeAndValue(dictType, value); + } + + @Override + public DictDataDO parseDictData(String dictType, String label) { + return dictDataMapper.selectByDictTypeAndLabel(dictType, label); + } + + @Override + public List getDictDataListByDictType(String dictType) { + List list = dictDataMapper.selectList(DictDataDO::getDictType, dictType); + list.sort(Comparator.comparing(DictDataDO::getSort)); + return list; + } + +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/service/dict/DictTypeService.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/service/dict/DictTypeService.java new file mode 100644 index 0000000..8f85a0d --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/service/dict/DictTypeService.java @@ -0,0 +1,72 @@ +package com.tashow.cloud.system.service.dict; + +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.system.controller.admin.dict.vo.type.DictTypePageReqVO; +import com.tashow.cloud.system.controller.admin.dict.vo.type.DictTypeSaveReqVO; +import com.tashow.cloud.system.dal.dataobject.dict.DictTypeDO; +import com.tashow.cloud.system.controller.admin.dict.vo.type.DictTypePageReqVO; +import com.tashow.cloud.system.controller.admin.dict.vo.type.DictTypeSaveReqVO; + +import java.util.List; + +/** + * 字典类型 Service 接口 + * + * @author 芋道源码 + */ +public interface DictTypeService { + + /** + * 创建字典类型 + * + * @param createReqVO 字典类型信息 + * @return 字典类型编号 + */ + Long createDictType(DictTypeSaveReqVO createReqVO); + + /** + * 更新字典类型 + * + * @param updateReqVO 字典类型信息 + */ + void updateDictType(DictTypeSaveReqVO updateReqVO); + + /** + * 删除字典类型 + * + * @param id 字典类型编号 + */ + void deleteDictType(Long id); + + /** + * 获得字典类型分页列表 + * + * @param pageReqVO 分页请求 + * @return 字典类型分页列表 + */ + PageResult getDictTypePage(DictTypePageReqVO pageReqVO); + + /** + * 获得字典类型详情 + * + * @param id 字典类型编号 + * @return 字典类型 + */ + DictTypeDO getDictType(Long id); + + /** + * 获得字典类型详情 + * + * @param type 字典类型 + * @return 字典类型详情 + */ + DictTypeDO getDictType(String type); + + /** + * 获得全部字典类型列表 + * + * @return 字典类型列表 + */ + List getDictTypeList(); + +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/service/dict/DictTypeServiceImpl.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/service/dict/DictTypeServiceImpl.java new file mode 100644 index 0000000..7ff022e --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/service/dict/DictTypeServiceImpl.java @@ -0,0 +1,140 @@ +package com.tashow.cloud.system.service.dict; + +import cn.hutool.core.util.StrUtil; +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.common.util.date.LocalDateTimeUtils; +import com.tashow.cloud.common.util.object.BeanUtils; +import com.tashow.cloud.system.controller.admin.dict.vo.type.DictTypePageReqVO; +import com.tashow.cloud.system.controller.admin.dict.vo.type.DictTypeSaveReqVO; +import com.tashow.cloud.system.dal.dataobject.dict.DictTypeDO; +import com.tashow.cloud.system.dal.mysql.dict.DictTypeMapper; +import com.google.common.annotations.VisibleForTesting; +import com.tashow.cloud.systemapi.enums.ErrorCodeConstants; +import org.springframework.stereotype.Service; + +import jakarta.annotation.Resource; +import java.time.LocalDateTime; +import java.util.List; + +import static com.tashow.cloud.common.exception.util.ServiceExceptionUtil.exception; + +/** + * 字典类型 Service 实现类 + * + * @author 芋道源码 + */ +@Service +public class DictTypeServiceImpl implements DictTypeService { + + @Resource + private DictDataService dictDataService; + + @Resource + private DictTypeMapper dictTypeMapper; + + @Override + public PageResult getDictTypePage(DictTypePageReqVO pageReqVO) { + return dictTypeMapper.selectPage(pageReqVO); + } + + @Override + public DictTypeDO getDictType(Long id) { + return dictTypeMapper.selectById(id); + } + + @Override + public DictTypeDO getDictType(String type) { + return dictTypeMapper.selectByType(type); + } + + @Override + public Long createDictType(DictTypeSaveReqVO createReqVO) { + // 校验字典类型的名字的唯一性 + validateDictTypeNameUnique(null, createReqVO.getName()); + // 校验字典类型的类型的唯一性 + validateDictTypeUnique(null, createReqVO.getType()); + + // 插入字典类型 + DictTypeDO dictType = BeanUtils.toBean(createReqVO, DictTypeDO.class); + dictType.setDeletedTime(LocalDateTimeUtils.EMPTY); // 唯一索引,避免 null 值 + dictTypeMapper.insert(dictType); + return dictType.getId(); + } + + @Override + public void updateDictType(DictTypeSaveReqVO updateReqVO) { + // 校验自己存在 + validateDictTypeExists(updateReqVO.getId()); + // 校验字典类型的名字的唯一性 + validateDictTypeNameUnique(updateReqVO.getId(), updateReqVO.getName()); + // 校验字典类型的类型的唯一性 + validateDictTypeUnique(updateReqVO.getId(), updateReqVO.getType()); + + // 更新字典类型 + DictTypeDO updateObj = BeanUtils.toBean(updateReqVO, DictTypeDO.class); + dictTypeMapper.updateById(updateObj); + } + + @Override + public void deleteDictType(Long id) { + // 校验是否存在 + DictTypeDO dictType = validateDictTypeExists(id); + // 校验是否有字典数据 + if (dictDataService.getDictDataCountByDictType(dictType.getType()) > 0) { + throw exception(ErrorCodeConstants.DICT_TYPE_HAS_CHILDREN); + } + // 删除字典类型 + dictTypeMapper.updateToDelete(id, LocalDateTime.now()); + } + + @Override + public List getDictTypeList() { + return dictTypeMapper.selectList(); + } + + @VisibleForTesting + void validateDictTypeNameUnique(Long id, String name) { + DictTypeDO dictType = dictTypeMapper.selectByName(name); + if (dictType == null) { + return; + } + // 如果 id 为空,说明不用比较是否为相同 id 的字典类型 + if (id == null) { + throw exception(ErrorCodeConstants.DICT_TYPE_NAME_DUPLICATE); + } + if (!dictType.getId().equals(id)) { + throw exception(ErrorCodeConstants.DICT_TYPE_NAME_DUPLICATE); + } + } + + @VisibleForTesting + void validateDictTypeUnique(Long id, String type) { + if (StrUtil.isEmpty(type)) { + return; + } + DictTypeDO dictType = dictTypeMapper.selectByType(type); + if (dictType == null) { + return; + } + // 如果 id 为空,说明不用比较是否为相同 id 的字典类型 + if (id == null) { + throw exception(ErrorCodeConstants.DICT_TYPE_TYPE_DUPLICATE); + } + if (!dictType.getId().equals(id)) { + throw exception(ErrorCodeConstants.DICT_TYPE_TYPE_DUPLICATE); + } + } + + @VisibleForTesting + DictTypeDO validateDictTypeExists(Long id) { + if (id == null) { + return null; + } + DictTypeDO dictType = dictTypeMapper.selectById(id); + if (dictType == null) { + throw exception(ErrorCodeConstants.DICT_TYPE_NOT_EXISTS); + } + return dictType; + } + +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/service/logger/LoginLogService.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/service/logger/LoginLogService.java new file mode 100644 index 0000000..fe6afe1 --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/service/logger/LoginLogService.java @@ -0,0 +1,32 @@ +package com.tashow.cloud.system.service.logger; + +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.systemapi.api.logger.dto.LoginLogCreateReqDTO; +import com.tashow.cloud.system.controller.admin.logger.vo.loginlog.LoginLogPageReqVO; +import com.tashow.cloud.system.dal.dataobject.logger.LoginLogDO; + +import com.tashow.cloud.systemapi.api.logger.dto.LoginLogCreateReqDTO; +import com.tashow.cloud.system.controller.admin.logger.vo.loginlog.LoginLogPageReqVO; +import jakarta.validation.Valid; + +/** + * 登录日志 Service 接口 + */ +public interface LoginLogService { + + /** + * 获得登录日志分页 + * + * @param pageReqVO 分页条件 + * @return 登录日志分页 + */ + PageResult getLoginLogPage(LoginLogPageReqVO pageReqVO); + + /** + * 创建登录日志 + * + * @param reqDTO 日志信息 + */ + void createLoginLog(@Valid LoginLogCreateReqDTO reqDTO); + +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/service/logger/LoginLogServiceImpl.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/service/logger/LoginLogServiceImpl.java new file mode 100644 index 0000000..8b2c183 --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/service/logger/LoginLogServiceImpl.java @@ -0,0 +1,37 @@ +package com.tashow.cloud.system.service.logger; + +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.common.util.object.BeanUtils; +import com.tashow.cloud.systemapi.api.logger.dto.LoginLogCreateReqDTO; +import com.tashow.cloud.system.controller.admin.logger.vo.loginlog.LoginLogPageReqVO; +import com.tashow.cloud.system.dal.dataobject.logger.LoginLogDO; +import com.tashow.cloud.system.dal.mysql.logger.LoginLogMapper; +import com.tashow.cloud.systemapi.api.logger.dto.LoginLogCreateReqDTO; +import com.tashow.cloud.system.controller.admin.logger.vo.loginlog.LoginLogPageReqVO; +import org.springframework.stereotype.Service; +import org.springframework.validation.annotation.Validated; + +import jakarta.annotation.Resource; + +/** + * 登录日志 Service 实现 + */ +@Service +@Validated +public class LoginLogServiceImpl implements LoginLogService { + + @Resource + private LoginLogMapper loginLogMapper; + + @Override + public PageResult getLoginLogPage(LoginLogPageReqVO pageReqVO) { + return loginLogMapper.selectPage(pageReqVO); + } + + @Override + public void createLoginLog(LoginLogCreateReqDTO reqDTO) { + LoginLogDO loginLog = BeanUtils.toBean(reqDTO, LoginLogDO.class); + loginLogMapper.insert(loginLog); + } + +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/service/logger/OperateLogService.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/service/logger/OperateLogService.java new file mode 100644 index 0000000..3d5d111 --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/service/logger/OperateLogService.java @@ -0,0 +1,42 @@ +package com.tashow.cloud.system.service.logger; + +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.systemapi.api.logger.dto.OperateLogCreateReqDTO; +import com.tashow.cloud.systemapi.api.logger.dto.OperateLogPageReqDTO; +import com.tashow.cloud.system.controller.admin.logger.vo.operatelog.OperateLogPageReqVO; +import com.tashow.cloud.system.dal.dataobject.logger.OperateLogDO; +import com.tashow.cloud.systemapi.api.logger.dto.OperateLogCreateReqDTO; +import com.tashow.cloud.systemapi.api.logger.dto.OperateLogPageReqDTO; +import com.tashow.cloud.system.controller.admin.logger.vo.operatelog.OperateLogPageReqVO; + +/** + * 操作日志 Service 接口 + * + * @author 芋道源码 + */ +public interface OperateLogService { + + /** + * 记录操作日志 + * + * @param createReqDTO 创建请求 + */ + void createOperateLog(OperateLogCreateReqDTO createReqDTO); + + /** + * 获得操作日志分页列表 + * + * @param pageReqVO 分页条件 + * @return 操作日志分页列表 + */ + PageResult getOperateLogPage(OperateLogPageReqVO pageReqVO); + + /** + * 获得操作日志分页列表 + * + * @param pageReqVO 分页条件 + * @return 操作日志分页列表 + */ + PageResult getOperateLogPage(OperateLogPageReqDTO pageReqVO); + +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/service/logger/OperateLogServiceImpl.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/service/logger/OperateLogServiceImpl.java new file mode 100644 index 0000000..d063774 --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/service/logger/OperateLogServiceImpl.java @@ -0,0 +1,47 @@ +package com.tashow.cloud.system.service.logger; + +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.common.util.object.BeanUtils; +import com.tashow.cloud.systemapi.api.logger.dto.OperateLogCreateReqDTO; +import com.tashow.cloud.systemapi.api.logger.dto.OperateLogPageReqDTO; +import com.tashow.cloud.system.controller.admin.logger.vo.operatelog.OperateLogPageReqVO; +import com.tashow.cloud.system.dal.dataobject.logger.OperateLogDO; +import com.tashow.cloud.system.dal.mysql.logger.OperateLogMapper; +import com.tashow.cloud.systemapi.api.logger.dto.OperateLogCreateReqDTO; +import com.tashow.cloud.systemapi.api.logger.dto.OperateLogPageReqDTO; +import com.tashow.cloud.system.controller.admin.logger.vo.operatelog.OperateLogPageReqVO; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.validation.annotation.Validated; + +/** + * 操作日志 Service 实现类 + * + * @author 芋道源码 + */ +@Service +@Validated +@Slf4j +public class OperateLogServiceImpl implements OperateLogService { + + @Resource + private OperateLogMapper operateLogMapper; + + @Override + public void createOperateLog(OperateLogCreateReqDTO createReqDTO) { + OperateLogDO log = BeanUtils.toBean(createReqDTO, OperateLogDO.class); + operateLogMapper.insert(log); + } + + @Override + public PageResult getOperateLogPage(OperateLogPageReqVO pageReqVO) { + return operateLogMapper.selectPage(pageReqVO); + } + + @Override + public PageResult getOperateLogPage(OperateLogPageReqDTO pageReqDTO) { + return operateLogMapper.selectPage(pageReqDTO); + } + +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/service/mail/MailAccountService.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/service/mail/MailAccountService.java new file mode 100644 index 0000000..e5734f9 --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/service/mail/MailAccountService.java @@ -0,0 +1,74 @@ +package com.tashow.cloud.system.service.mail; + +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.system.controller.admin.mail.vo.account.MailAccountPageReqVO; +import com.tashow.cloud.system.controller.admin.mail.vo.account.MailAccountSaveReqVO; +import com.tashow.cloud.system.dal.dataobject.mail.MailAccountDO; + +import com.tashow.cloud.system.controller.admin.mail.vo.account.MailAccountPageReqVO; +import com.tashow.cloud.system.controller.admin.mail.vo.account.MailAccountSaveReqVO; +import jakarta.validation.Valid; +import java.util.List; + +/** + * 邮箱账号 Service 接口 + * + * @author wangjingyi + * @since 2022-03-21 + */ +public interface MailAccountService { + + /** + * 创建邮箱账号 + * + * @param createReqVO 邮箱账号信息 + * @return 编号 + */ + Long createMailAccount(@Valid MailAccountSaveReqVO createReqVO); + + /** + * 修改邮箱账号 + * + * @param updateReqVO 邮箱账号信息 + */ + void updateMailAccount(@Valid MailAccountSaveReqVO updateReqVO); + + /** + * 删除邮箱账号 + * + * @param id 编号 + */ + void deleteMailAccount(Long id); + + /** + * 获取邮箱账号信息 + * + * @param id 编号 + * @return 邮箱账号信息 + */ + MailAccountDO getMailAccount(Long id); + + /** + * 从缓存中获取邮箱账号 + * + * @param id 编号 + * @return 邮箱账号 + */ + MailAccountDO getMailAccountFromCache(Long id); + + /** + * 获取邮箱账号分页信息 + * + * @param pageReqVO 邮箱账号分页参数 + * @return 邮箱账号分页信息 + */ + PageResult getMailAccountPage(MailAccountPageReqVO pageReqVO); + + /** + * 获取邮箱数组信息 + * + * @return 邮箱账号信息数组 + */ + List getMailAccountList(); + +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/service/mail/MailAccountServiceImpl.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/service/mail/MailAccountServiceImpl.java new file mode 100644 index 0000000..7fd665c --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/service/mail/MailAccountServiceImpl.java @@ -0,0 +1,102 @@ +package com.tashow.cloud.system.service.mail; + +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.common.util.object.BeanUtils; +import com.tashow.cloud.system.controller.admin.mail.vo.account.MailAccountPageReqVO; +import com.tashow.cloud.system.controller.admin.mail.vo.account.MailAccountSaveReqVO; +import com.tashow.cloud.system.dal.dataobject.mail.MailAccountDO; +import com.tashow.cloud.system.dal.mysql.mail.MailAccountMapper; +import com.tashow.cloud.system.dal.redis.RedisKeyConstants; +import com.tashow.cloud.system.controller.admin.mail.vo.account.MailAccountPageReqVO; +import com.tashow.cloud.system.controller.admin.mail.vo.account.MailAccountSaveReqVO; +import com.tashow.cloud.systemapi.enums.ErrorCodeConstants; +import lombok.extern.slf4j.Slf4j; +import org.springframework.cache.annotation.CacheEvict; +import org.springframework.cache.annotation.Cacheable; +import org.springframework.stereotype.Service; +import org.springframework.validation.annotation.Validated; + +import jakarta.annotation.Resource; +import java.util.List; + +import static com.tashow.cloud.common.exception.util.ServiceExceptionUtil.exception; +import static com.tashow.cloud.systemapi.enums.ErrorCodeConstants.MAIL_ACCOUNT_NOT_EXISTS; +import static com.tashow.cloud.systemapi.enums.ErrorCodeConstants.MAIL_ACCOUNT_RELATE_TEMPLATE_EXISTS; + +/** + * 邮箱账号 Service 实现类 + * + * @author wangjingyi + * @since 2022-03-21 + */ +@Service +@Validated +@Slf4j +public class MailAccountServiceImpl implements MailAccountService { + + @Resource + private MailAccountMapper mailAccountMapper; + + @Resource + private MailTemplateService mailTemplateService; + + @Override + public Long createMailAccount(MailAccountSaveReqVO createReqVO) { + MailAccountDO account = BeanUtils.toBean(createReqVO, MailAccountDO.class); + mailAccountMapper.insert(account); + return account.getId(); + } + + @Override + @CacheEvict(value = RedisKeyConstants.MAIL_ACCOUNT, key = "#updateReqVO.id") + public void updateMailAccount(MailAccountSaveReqVO updateReqVO) { + // 校验是否存在 + validateMailAccountExists(updateReqVO.getId()); + + // 更新 + MailAccountDO updateObj = BeanUtils.toBean(updateReqVO, MailAccountDO.class); + mailAccountMapper.updateById(updateObj); + } + + @Override + @CacheEvict(value = RedisKeyConstants.MAIL_ACCOUNT, key = "#id") + public void deleteMailAccount(Long id) { + // 校验是否存在账号 + validateMailAccountExists(id); + // 校验是否存在关联模版 + if (mailTemplateService.getMailTemplateCountByAccountId(id) > 0) { + throw exception(ErrorCodeConstants.MAIL_ACCOUNT_RELATE_TEMPLATE_EXISTS); + } + + // 删除 + mailAccountMapper.deleteById(id); + } + + private void validateMailAccountExists(Long id) { + if (mailAccountMapper.selectById(id) == null) { + throw exception(ErrorCodeConstants.MAIL_ACCOUNT_NOT_EXISTS); + } + } + + @Override + public MailAccountDO getMailAccount(Long id) { + return mailAccountMapper.selectById(id); + } + + @Override + @Cacheable(value = RedisKeyConstants.MAIL_ACCOUNT, key = "#id", unless = "#result == null") + public MailAccountDO getMailAccountFromCache(Long id) { + return getMailAccount(id); + } + + @Override + public PageResult getMailAccountPage(MailAccountPageReqVO pageReqVO) { + return mailAccountMapper.selectPage(pageReqVO); + } + + @Override + public List getMailAccountList() { + return mailAccountMapper.selectList(); + } + +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/service/mail/MailLogService.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/service/mail/MailLogService.java new file mode 100644 index 0000000..a48b4a2 --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/service/mail/MailLogService.java @@ -0,0 +1,62 @@ +package com.tashow.cloud.system.service.mail; + +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.system.controller.admin.mail.vo.log.MailLogPageReqVO; +import com.tashow.cloud.system.dal.dataobject.mail.MailAccountDO; +import com.tashow.cloud.system.dal.dataobject.mail.MailLogDO; +import com.tashow.cloud.system.dal.dataobject.mail.MailTemplateDO; +import com.tashow.cloud.system.controller.admin.mail.vo.log.MailLogPageReqVO; + +import java.util.Map; + +/** + * 邮件日志 Service 接口 + * + * @author wangjingyi + * @since 2022-03-21 + */ +public interface MailLogService { + + /** + * 邮件日志分页 + * + * @param pageVO 分页参数 + * @return 分页结果 + */ + PageResult getMailLogPage(MailLogPageReqVO pageVO); + + /** + * 获得指定编号的邮件日志 + * + * @param id 日志编号 + * @return 邮件日志 + */ + MailLogDO getMailLog(Long id); + + /** + * 创建邮件日志 + * + * @param userId 用户编码 + * @param userType 用户类型 + * @param toMail 收件人邮件 + * @param account 邮件账号信息 + * @param template 模版信息 + * @param templateContent 模版内容 + * @param templateParams 模版参数 + * @param isSend 是否发送成功 + * @return 日志编号 + */ + Long createMailLog(Long userId, Integer userType, String toMail, + MailAccountDO account, MailTemplateDO template , + String templateContent, Map templateParams, Boolean isSend); + + /** + * 更新邮件发送结果 + * + * @param logId 日志编号 + * @param messageId 发送后的消息编号 + * @param exception 发送异常 + */ + void updateMailSendResult(Long logId, String messageId, Exception exception); + +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/service/mail/MailLogServiceImpl.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/service/mail/MailLogServiceImpl.java new file mode 100644 index 0000000..716b003 --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/service/mail/MailLogServiceImpl.java @@ -0,0 +1,80 @@ +package com.tashow.cloud.system.service.mail; + +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.system.controller.admin.mail.vo.log.MailLogPageReqVO; +import com.tashow.cloud.system.dal.dataobject.mail.MailAccountDO; +import com.tashow.cloud.system.dal.dataobject.mail.MailLogDO; +import com.tashow.cloud.system.dal.dataobject.mail.MailTemplateDO; +import com.tashow.cloud.system.dal.mysql.mail.MailLogMapper; +import com.tashow.cloud.systemapi.enums.mail.MailSendStatusEnum; +import com.tashow.cloud.system.controller.admin.mail.vo.log.MailLogPageReqVO; +import com.tashow.cloud.systemapi.enums.mail.MailSendStatusEnum; +import org.springframework.stereotype.Service; +import org.springframework.validation.annotation.Validated; + +import jakarta.annotation.Resource; +import java.time.LocalDateTime; +import java.util.Map; +import java.util.Objects; + +import static cn.hutool.core.exceptions.ExceptionUtil.getRootCauseMessage; + +/** + * 邮件日志 Service 实现类 + * + * @author wangjingyi + * @since 2022-03-21 + */ +@Service +@Validated +public class MailLogServiceImpl implements MailLogService { + + @Resource + private MailLogMapper mailLogMapper; + + @Override + public PageResult getMailLogPage(MailLogPageReqVO pageVO) { + return mailLogMapper.selectPage(pageVO); + } + + @Override + public MailLogDO getMailLog(Long id) { + return mailLogMapper.selectById(id); + } + + @Override + public Long createMailLog(Long userId, Integer userType, String toMail, + MailAccountDO account, MailTemplateDO template, + String templateContent, Map templateParams, Boolean isSend) { + MailLogDO.MailLogDOBuilder logDOBuilder = MailLogDO.builder(); + // 根据是否要发送,设置状态 + logDOBuilder.sendStatus(Objects.equals(isSend, true) ? MailSendStatusEnum.INIT.getStatus() + : MailSendStatusEnum.IGNORE.getStatus()) + // 用户信息 + .userId(userId).userType(userType).toMail(toMail) + .accountId(account.getId()).fromMail(account.getMail()) + // 模板相关字段 + .templateId(template.getId()).templateCode(template.getCode()).templateNickname(template.getNickname()) + .templateTitle(template.getTitle()).templateContent(templateContent).templateParams(templateParams); + + // 插入数据库 + MailLogDO logDO = logDOBuilder.build(); + mailLogMapper.insert(logDO); + return logDO.getId(); + } + + @Override + public void updateMailSendResult(Long logId, String messageId, Exception exception) { + // 1. 成功 + if (exception == null) { + mailLogMapper.updateById(new MailLogDO().setId(logId).setSendTime(LocalDateTime.now()) + .setSendStatus(MailSendStatusEnum.SUCCESS.getStatus()).setSendMessageId(messageId)); + return; + } + // 2. 失败 + mailLogMapper.updateById(new MailLogDO().setId(logId).setSendTime(LocalDateTime.now()) + .setSendStatus(MailSendStatusEnum.FAILURE.getStatus()).setSendException(getRootCauseMessage(exception))); + + } + +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/service/mail/MailSendService.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/service/mail/MailSendService.java new file mode 100644 index 0000000..02c7297 --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/service/mail/MailSendService.java @@ -0,0 +1,60 @@ +package com.tashow.cloud.system.service.mail; + +import com.tashow.cloud.system.mq.message.mail.MailSendMessage; + +import java.util.Map; + +/** + * 邮件发送 Service 接口 + * + * @author wangjingyi + * @since 2022-03-21 + */ +public interface MailSendService { + + /** + * 发送单条邮件给管理后台的用户 + * + * @param mail 邮箱 + * @param userId 用户编码 + * @param templateCode 邮件模版编码 + * @param templateParams 邮件模版参数 + * @return 发送日志编号 + */ + Long sendSingleMailToAdmin(String mail, Long userId, + String templateCode, Map templateParams); + + /** + * 发送单条邮件给用户 APP 的用户 + * + * @param mail 邮箱 + * @param userId 用户编码 + * @param templateCode 邮件模版编码 + * @param templateParams 邮件模版参数 + * @return 发送日志编号 + */ + Long sendSingleMailToMember(String mail, Long userId, + String templateCode, Map templateParams); + + /** + * 发送单条邮件给用户 + * + * @param mail 邮箱 + * @param userId 用户编码 + * @param userType 用户类型 + * @param templateCode 邮件模版编码 + * @param templateParams 邮件模版参数 + * @return 发送日志编号 + */ + Long sendSingleMail(String mail, Long userId, Integer userType, + String templateCode, Map templateParams); + + /** + * 执行真正的邮件发送 + * 注意,该方法仅仅提供给 MQ Consumer 使用 + * + * @param message 邮件 + */ + void doSendMail(MailSendMessage message); + +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/service/mail/MailSendServiceImpl.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/service/mail/MailSendServiceImpl.java new file mode 100644 index 0000000..cc94817 --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/service/mail/MailSendServiceImpl.java @@ -0,0 +1,175 @@ +package com.tashow.cloud.system.service.mail; + +import cn.hutool.core.util.StrUtil; +import com.tashow.cloud.common.enums.CommonStatusEnum; +import com.tashow.cloud.common.enums.UserTypeEnum; +import com.tashow.cloud.system.dal.dataobject.mail.MailAccountDO; +import com.tashow.cloud.system.dal.dataobject.mail.MailTemplateDO; +import com.tashow.cloud.system.dal.dataobject.user.AdminUserDO; +import com.tashow.cloud.system.mq.message.mail.MailSendMessage; +import com.tashow.cloud.system.mq.producer.mail.MailProducer; +import com.tashow.cloud.system.service.member.MemberService; +import com.tashow.cloud.system.service.user.AdminUserService; +import com.google.common.annotations.VisibleForTesting; +import com.tashow.cloud.systemapi.enums.ErrorCodeConstants; +import com.tashow.cloud.system.service.member.MemberService; +import com.tashow.cloud.system.service.user.AdminUserService; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import org.dromara.hutool.extra.mail.*; +import org.springframework.stereotype.Service; +import org.springframework.validation.annotation.Validated; + +import java.util.Map; + +import static com.tashow.cloud.common.exception.util.ServiceExceptionUtil.exception; + +/** + * 邮箱发送 Service 实现类 + * + * @author wangjingyi + * @since 2022-03-21 + */ +@Service +@Validated +@Slf4j +public class MailSendServiceImpl implements MailSendService { + + @Resource + private AdminUserService adminUserService; + @Resource + private MemberService memberService; + + @Resource + private MailAccountService mailAccountService; + @Resource + private MailTemplateService mailTemplateService; + + @Resource + private MailLogService mailLogService; + @Resource + private MailProducer mailProducer; + + @Override + public Long sendSingleMailToAdmin(String mail, Long userId, + String templateCode, Map templateParams) { + // 如果 mail 为空,则加载用户编号对应的邮箱 + if (StrUtil.isEmpty(mail)) { + AdminUserDO user = adminUserService.getUser(userId); + if (user != null) { + mail = user.getEmail(); + } + } + // 执行发送 + return sendSingleMail(mail, userId, UserTypeEnum.ADMIN.getValue(), templateCode, templateParams); + } + + @Override + public Long sendSingleMailToMember(String mail, Long userId, + String templateCode, Map templateParams) { + // 如果 mail 为空,则加载用户编号对应的邮箱 + if (StrUtil.isEmpty(mail)) { + mail = memberService.getMemberUserEmail(userId); + } + // 执行发送 + return sendSingleMail(mail, userId, UserTypeEnum.MEMBER.getValue(), templateCode, templateParams); + } + + @Override + public Long sendSingleMail(String mail, Long userId, Integer userType, + String templateCode, Map templateParams) { + // 校验邮箱模版是否合法 + MailTemplateDO template = validateMailTemplate(templateCode); + // 校验邮箱账号是否合法 + MailAccountDO account = validateMailAccount(template.getAccountId()); + + // 校验邮箱是否存在 + mail = validateMail(mail); + validateTemplateParams(template, templateParams); + + // 创建发送日志。如果模板被禁用,则不发送短信,只记录日志 + Boolean isSend = CommonStatusEnum.ENABLE.getStatus().equals(template.getStatus()); + String title = mailTemplateService.formatMailTemplateContent(template.getTitle(), templateParams); + String content = mailTemplateService.formatMailTemplateContent(template.getContent(), templateParams); + Long sendLogId = mailLogService.createMailLog(userId, userType, mail, + account, template, content, templateParams, isSend); + // 发送 MQ 消息,异步执行发送短信 + if (isSend) { + mailProducer.sendMailSendMessage(sendLogId, mail, account.getId(), + template.getNickname(), title, content); + } + return sendLogId; + } + + @Override + public void doSendMail(MailSendMessage message) { + // 1. 创建发送账号 + MailAccountDO account = validateMailAccount(message.getAccountId()); + MailAccount mailAccount = buildMailAccount(account, message.getNickname()); + // 2. 发送邮件 + try { + String messageId = MailUtil.send(mailAccount, message.getMail(), + message.getTitle(), message.getContent(), true); + // 3. 更新结果(成功) + mailLogService.updateMailSendResult(message.getLogId(), messageId, null); + } catch (Exception e) { + // 3. 更新结果(异常) + mailLogService.updateMailSendResult(message.getLogId(), null, e); + } + } + + private MailAccount buildMailAccount(MailAccountDO account, String nickname) { + String from = StrUtil.isNotEmpty(nickname) ? nickname + " <" + account.getMail() + ">" : account.getMail(); + return new MailAccount().setFrom(from).setAuth(true) + .setUser(account.getUsername()).setPass(account.getPassword().toCharArray()) + .setHost(account.getHost()).setPort(account.getPort()) + .setSslEnable(account.getSslEnable()).setStarttlsEnable(account.getStarttlsEnable()); + } + + @VisibleForTesting + MailTemplateDO validateMailTemplate(String templateCode) { + // 获得邮件模板。考虑到效率,从缓存中获取 + MailTemplateDO template = mailTemplateService.getMailTemplateByCodeFromCache(templateCode); + // 邮件模板不存在 + if (template == null) { + throw exception(ErrorCodeConstants.MAIL_TEMPLATE_NOT_EXISTS); + } + return template; + } + + @VisibleForTesting + MailAccountDO validateMailAccount(Long accountId) { + // 获得邮箱账号。考虑到效率,从缓存中获取 + MailAccountDO account = mailAccountService.getMailAccountFromCache(accountId); + // 邮箱账号不存在 + if (account == null) { + throw exception(ErrorCodeConstants.MAIL_ACCOUNT_NOT_EXISTS); + } + return account; + } + + @VisibleForTesting + String validateMail(String mail) { + if (StrUtil.isEmpty(mail)) { + throw exception(ErrorCodeConstants.MAIL_SEND_MAIL_NOT_EXISTS); + } + return mail; + } + + /** + * 校验邮件参数是否确实 + * + * @param template 邮箱模板 + * @param templateParams 参数列表 + */ + @VisibleForTesting + void validateTemplateParams(MailTemplateDO template, Map templateParams) { + template.getParams().forEach(key -> { + Object value = templateParams.get(key); + if (value == null) { + throw exception(ErrorCodeConstants.MAIL_SEND_TEMPLATE_PARAM_MISS, key); + } + }); + } + +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/service/mail/MailTemplateService.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/service/mail/MailTemplateService.java new file mode 100644 index 0000000..33afcf3 --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/service/mail/MailTemplateService.java @@ -0,0 +1,92 @@ +package com.tashow.cloud.system.service.mail; + +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.system.controller.admin.mail.vo.template.MailTemplatePageReqVO; +import com.tashow.cloud.system.controller.admin.mail.vo.template.MailTemplateSaveReqVO; +import com.tashow.cloud.system.dal.dataobject.mail.MailTemplateDO; + +import com.tashow.cloud.system.controller.admin.mail.vo.template.MailTemplatePageReqVO; +import com.tashow.cloud.system.controller.admin.mail.vo.template.MailTemplateSaveReqVO; +import jakarta.validation.Valid; +import java.util.List; +import java.util.Map; + +/** + * 邮件模版 Service 接口 + * + * @author wangjingyi + * @since 2022-03-21 + */ +public interface MailTemplateService { + + /** + * 邮件模版创建 + * + * @param createReqVO 邮件信息 + * @return 编号 + */ + Long createMailTemplate(@Valid MailTemplateSaveReqVO createReqVO); + + /** + * 邮件模版修改 + * + * @param updateReqVO 邮件信息 + */ + void updateMailTemplate(@Valid MailTemplateSaveReqVO updateReqVO); + + /** + * 邮件模版删除 + * + * @param id 编号 + */ + void deleteMailTemplate(Long id); + + /** + * 获取邮件模版 + * + * @param id 编号 + * @return 邮件模版 + */ + MailTemplateDO getMailTemplate(Long id); + + /** + * 获取邮件模版分页 + * + * @param pageReqVO 模版信息 + * @return 邮件模版分页信息 + */ + PageResult getMailTemplatePage(MailTemplatePageReqVO pageReqVO); + + /** + * 获取邮件模板数组 + * + * @return 模版数组 + */ + List getMailTemplateList(); + + /** + * 从缓存中获取邮件模版 + * + * @param code 模板编码 + * @return 邮件模板 + */ + MailTemplateDO getMailTemplateByCodeFromCache(String code); + + /** + * 邮件模版内容合成 + * + * @param content 邮件模版 + * @param params 合成参数 + * @return 格式化后的内容 + */ + String formatMailTemplateContent(String content, Map params); + + /** + * 获得指定邮件账号下的邮件模板数量 + * + * @param accountId 账号编号 + * @return 数量 + */ + long getMailTemplateCountByAccountId(Long accountId); + +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/service/mail/MailTemplateServiceImpl.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/service/mail/MailTemplateServiceImpl.java new file mode 100644 index 0000000..b636662 --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/service/mail/MailTemplateServiceImpl.java @@ -0,0 +1,141 @@ +package com.tashow.cloud.system.service.mail; + +import cn.hutool.core.util.ObjUtil; +import cn.hutool.core.util.ReUtil; +import cn.hutool.core.util.StrUtil; +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.common.util.object.BeanUtils; +import com.tashow.cloud.system.controller.admin.mail.vo.template.MailTemplatePageReqVO; +import com.tashow.cloud.system.controller.admin.mail.vo.template.MailTemplateSaveReqVO; +import com.tashow.cloud.system.dal.dataobject.mail.MailTemplateDO; +import com.tashow.cloud.system.dal.mysql.mail.MailTemplateMapper; +import com.tashow.cloud.system.dal.redis.RedisKeyConstants; +import com.google.common.annotations.VisibleForTesting; +import com.tashow.cloud.system.controller.admin.mail.vo.template.MailTemplatePageReqVO; +import com.tashow.cloud.system.controller.admin.mail.vo.template.MailTemplateSaveReqVO; +import com.tashow.cloud.systemapi.enums.ErrorCodeConstants; +import lombok.extern.slf4j.Slf4j; +import org.springframework.cache.annotation.CacheEvict; +import org.springframework.cache.annotation.Cacheable; +import org.springframework.stereotype.Service; +import org.springframework.validation.annotation.Validated; + +import jakarta.annotation.Resource; +import jakarta.validation.Valid; +import java.util.List; +import java.util.Map; +import java.util.regex.Pattern; + +import static com.tashow.cloud.common.exception.util.ServiceExceptionUtil.exception; +import static com.tashow.cloud.systemapi.enums.ErrorCodeConstants.MAIL_TEMPLATE_CODE_EXISTS; +import static com.tashow.cloud.systemapi.enums.ErrorCodeConstants.MAIL_TEMPLATE_NOT_EXISTS; + +/** + * 邮箱模版 Service 实现类 + * + * @author wangjingyi + * @since 2022-03-21 + */ +@Service +@Validated +@Slf4j +public class MailTemplateServiceImpl implements MailTemplateService { + + /** + * 正则表达式,匹配 {} 中的变量 + */ + private static final Pattern PATTERN_PARAMS = Pattern.compile("\\{(.*?)}"); + + @Resource + private MailTemplateMapper mailTemplateMapper; + + @Override + public Long createMailTemplate(MailTemplateSaveReqVO createReqVO) { + // 校验 code 是否唯一 + validateCodeUnique(null, createReqVO.getCode()); + + // 插入 + MailTemplateDO template = BeanUtils.toBean(createReqVO, MailTemplateDO.class) + .setParams(parseTemplateContentParams(createReqVO.getContent())); + mailTemplateMapper.insert(template); + return template.getId(); + } + + @Override + @CacheEvict(cacheNames = RedisKeyConstants.MAIL_TEMPLATE, + allEntries = true) // allEntries 清空所有缓存,因为可能修改到 code 字段,不好清理 + public void updateMailTemplate(@Valid MailTemplateSaveReqVO updateReqVO) { + // 校验是否存在 + validateMailTemplateExists(updateReqVO.getId()); + // 校验 code 是否唯一 + validateCodeUnique(updateReqVO.getId(),updateReqVO.getCode()); + + // 更新 + MailTemplateDO updateObj = BeanUtils.toBean(updateReqVO, MailTemplateDO.class) + .setParams(parseTemplateContentParams(updateReqVO.getContent())); + mailTemplateMapper.updateById(updateObj); + } + + @VisibleForTesting + void validateCodeUnique(Long id, String code) { + MailTemplateDO template = mailTemplateMapper.selectByCode(code); + if (template == null) { + return; + } + // 存在 template 记录的情况下 + if (id == null // 新增时,说明重复 + || ObjUtil.notEqual(id, template.getId())) { // 更新时,如果 id 不一致,说明重复 + throw exception(ErrorCodeConstants.MAIL_TEMPLATE_CODE_EXISTS); + } + } + + @Override + @CacheEvict(cacheNames = RedisKeyConstants.MAIL_TEMPLATE, + allEntries = true) // allEntries 清空所有缓存,因为 id 不是直接的缓存 code,不好清理 + public void deleteMailTemplate(Long id) { + // 校验是否存在 + validateMailTemplateExists(id); + + // 删除 + mailTemplateMapper.deleteById(id); + } + + private void validateMailTemplateExists(Long id) { + if (mailTemplateMapper.selectById(id) == null) { + throw exception(ErrorCodeConstants.MAIL_TEMPLATE_NOT_EXISTS); + } + } + + @Override + public MailTemplateDO getMailTemplate(Long id) {return mailTemplateMapper.selectById(id);} + + @Override + @Cacheable(value = RedisKeyConstants.MAIL_TEMPLATE, key = "#code", unless = "#result == null") + public MailTemplateDO getMailTemplateByCodeFromCache(String code) { + return mailTemplateMapper.selectByCode(code); + } + + @Override + public PageResult getMailTemplatePage(MailTemplatePageReqVO pageReqVO) { + return mailTemplateMapper.selectPage(pageReqVO); + } + + @Override + public List getMailTemplateList() {return mailTemplateMapper.selectList();} + + @Override + public String formatMailTemplateContent(String content, Map params) { + return StrUtil.format(content, params); + } + + @VisibleForTesting + public List parseTemplateContentParams(String content) { + return ReUtil.findAllGroup1(PATTERN_PARAMS, content); + } + + @Override + public long getMailTemplateCountByAccountId(Long accountId) { + return mailTemplateMapper.selectCountByAccountId(accountId); + } + +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/service/member/MemberService.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/service/member/MemberService.java new file mode 100644 index 0000000..884a2dc --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/service/member/MemberService.java @@ -0,0 +1,26 @@ +package com.tashow.cloud.system.service.member; + +/** + * Member Service 接口 + * + * @author 芋道源码 + */ +public interface MemberService { + + /** + * 获得会员用户的手机号码 + * + * @param id 会员用户编号 + * @return 手机号码 + */ + String getMemberUserMobile(Long id); + + /** + * 获得会员用户的邮箱 + * + * @param id 会员用户编号 + * @return 邮箱 + */ + String getMemberUserEmail(Long id); + +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/service/member/MemberServiceImpl.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/service/member/MemberServiceImpl.java new file mode 100644 index 0000000..3d5c388 --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/service/member/MemberServiceImpl.java @@ -0,0 +1,54 @@ +package com.tashow.cloud.system.service.member; + +import cn.hutool.core.util.ClassUtil; +import cn.hutool.core.util.ReflectUtil; +import cn.hutool.extra.spring.SpringUtil; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; + +/** + * Member Service 实现类 + * + * @author 芋道源码 + */ +@Service +public class MemberServiceImpl implements MemberService { + + @Value("${tashow.info.base-package}") + private String basePackage; + + private volatile Object memberUserApi; + + @Override + public String getMemberUserMobile(Long id) { + Object user = getMemberUser(id); + if (user == null) { + return null; + } + return ReflectUtil.invoke(user, "getMobile"); + } + + @Override + public String getMemberUserEmail(Long id) { + Object user = getMemberUser(id); + if (user == null) { + return null; + } + return ReflectUtil.invoke(user, "getEmail"); + } + + private Object getMemberUser(Long id) { + if (id == null) { + return null; + } + return ReflectUtil.invoke(getMemberUserApi(), "getUser", id); + } + + private Object getMemberUserApi() { + if (memberUserApi == null) { + memberUserApi = SpringUtil.getBean(ClassUtil.loadClass(String.format("%s.module.member.api.user.MemberUserApi", basePackage))); + } + return memberUserApi; + } + +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/service/member/package-info.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/service/member/package-info.java new file mode 100644 index 0000000..73f74fe --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/service/member/package-info.java @@ -0,0 +1,4 @@ +/** + * yudao-module-member 模块的适配,解除 yudao-module-system 对它们的依赖 + */ +package com.tashow.cloud.system.service.member; diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/service/notice/NoticeService.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/service/notice/NoticeService.java new file mode 100644 index 0000000..cafbef6 --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/service/notice/NoticeService.java @@ -0,0 +1,53 @@ +package com.tashow.cloud.system.service.notice; + +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.system.controller.admin.notice.vo.NoticePageReqVO; +import com.tashow.cloud.system.controller.admin.notice.vo.NoticeSaveReqVO; +import com.tashow.cloud.system.dal.dataobject.notice.NoticeDO; +import com.tashow.cloud.system.controller.admin.notice.vo.NoticePageReqVO; +import com.tashow.cloud.system.controller.admin.notice.vo.NoticeSaveReqVO; + +/** + * 通知公告 Service 接口 + */ +public interface NoticeService { + + /** + * 创建通知公告 + * + * @param createReqVO 通知公告 + * @return 编号 + */ + Long createNotice(NoticeSaveReqVO createReqVO); + + /** + * 更新通知公告 + * + * @param reqVO 通知公告 + */ + void updateNotice(NoticeSaveReqVO reqVO); + + /** + * 删除通知公告 + * + * @param id 编号 + */ + void deleteNotice(Long id); + + /** + * 获得通知公告分页列表 + * + * @param reqVO 分页条件 + * @return 部门分页列表 + */ + PageResult getNoticePage(NoticePageReqVO reqVO); + + /** + * 获得通知公告 + * + * @param id 编号 + * @return 通知公告 + */ + NoticeDO getNotice(Long id); + +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/service/notice/NoticeServiceImpl.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/service/notice/NoticeServiceImpl.java new file mode 100644 index 0000000..43445ef --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/service/notice/NoticeServiceImpl.java @@ -0,0 +1,76 @@ +package com.tashow.cloud.system.service.notice; + +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.common.util.object.BeanUtils; +import com.tashow.cloud.system.controller.admin.notice.vo.NoticePageReqVO; +import com.tashow.cloud.system.controller.admin.notice.vo.NoticeSaveReqVO; +import com.tashow.cloud.system.dal.dataobject.notice.NoticeDO; +import com.tashow.cloud.system.dal.mysql.notice.NoticeMapper; +import com.google.common.annotations.VisibleForTesting; +import com.tashow.cloud.system.controller.admin.notice.vo.NoticePageReqVO; +import com.tashow.cloud.system.controller.admin.notice.vo.NoticeSaveReqVO; +import com.tashow.cloud.systemapi.enums.ErrorCodeConstants; +import org.springframework.stereotype.Service; + +import jakarta.annotation.Resource; + +import static com.tashow.cloud.common.exception.util.ServiceExceptionUtil.exception; +import static com.tashow.cloud.systemapi.enums.ErrorCodeConstants.NOTICE_NOT_FOUND; + +/** + * 通知公告 Service 实现类 + * + * @author 芋道源码 + */ +@Service +public class NoticeServiceImpl implements NoticeService { + + @Resource + private NoticeMapper noticeMapper; + + @Override + public Long createNotice(NoticeSaveReqVO createReqVO) { + NoticeDO notice = BeanUtils.toBean(createReqVO, NoticeDO.class); + noticeMapper.insert(notice); + return notice.getId(); + } + + @Override + public void updateNotice(NoticeSaveReqVO updateReqVO) { + // 校验是否存在 + validateNoticeExists(updateReqVO.getId()); + // 更新通知公告 + NoticeDO updateObj = BeanUtils.toBean(updateReqVO, NoticeDO.class); + noticeMapper.updateById(updateObj); + } + + @Override + public void deleteNotice(Long id) { + // 校验是否存在 + validateNoticeExists(id); + // 删除通知公告 + noticeMapper.deleteById(id); + } + + @Override + public PageResult getNoticePage(NoticePageReqVO reqVO) { + return noticeMapper.selectPage(reqVO); + } + + @Override + public NoticeDO getNotice(Long id) { + return noticeMapper.selectById(id); + } + + @VisibleForTesting + public void validateNoticeExists(Long id) { + if (id == null) { + return; + } + NoticeDO notice = noticeMapper.selectById(id); + if (notice == null) { + throw exception(ErrorCodeConstants.NOTICE_NOT_FOUND); + } + } + +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/service/notify/NotifyMessageService.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/service/notify/NotifyMessageService.java new file mode 100644 index 0000000..d983c3a --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/service/notify/NotifyMessageService.java @@ -0,0 +1,99 @@ +package com.tashow.cloud.system.service.notify; + +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.system.controller.admin.notify.vo.message.NotifyMessageMyPageReqVO; +import com.tashow.cloud.system.controller.admin.notify.vo.message.NotifyMessagePageReqVO; +import com.tashow.cloud.system.dal.dataobject.notify.NotifyMessageDO; +import com.tashow.cloud.system.dal.dataobject.notify.NotifyTemplateDO; +import com.tashow.cloud.system.controller.admin.notify.vo.message.NotifyMessageMyPageReqVO; +import com.tashow.cloud.system.controller.admin.notify.vo.message.NotifyMessagePageReqVO; + +import java.util.Collection; +import java.util.List; +import java.util.Map; + +/** + * 站内信 Service 接口 + * + * @author xrcoder + */ +public interface NotifyMessageService { + + /** + * 创建站内信 + * + * @param userId 用户编号 + * @param userType 用户类型 + * @param template 模版信息 + * @param templateContent 模版内容 + * @param templateParams 模版参数 + * @return 站内信编号 + */ + Long createNotifyMessage(Long userId, Integer userType, + NotifyTemplateDO template, String templateContent, Map templateParams); + + /** + * 获得站内信分页 + * + * @param pageReqVO 分页查询 + * @return 站内信分页 + */ + PageResult getNotifyMessagePage(NotifyMessagePageReqVO pageReqVO); + + /** + * 获得【我的】站内信分页 + * + * @param pageReqVO 分页查询 + * @param userId 用户编号 + * @param userType 用户类型 + * @return 站内信分页 + */ + PageResult getMyMyNotifyMessagePage(NotifyMessageMyPageReqVO pageReqVO, Long userId, Integer userType); + + /** + * 获得站内信 + * + * @param id 编号 + * @return 站内信 + */ + NotifyMessageDO getNotifyMessage(Long id); + + /** + * 获得【我的】未读站内信列表 + * + * @param userId 用户编号 + * @param userType 用户类型 + * @param size 数量 + * @return 站内信列表 + */ + List getUnreadNotifyMessageList(Long userId, Integer userType, Integer size); + + /** + * 统计用户未读站内信条数 + * + * @param userId 用户编号 + * @param userType 用户类型 + * @return 返回未读站内信条数 + */ + Long getUnreadNotifyMessageCount(Long userId, Integer userType); + + /** + * 标记站内信为已读 + * + * @param ids 站内信编号集合 + * @param userId 用户编号 + * @param userType 用户类型 + * @return 更新到的条数 + */ + int updateNotifyMessageRead(Collection ids, Long userId, Integer userType); + + /** + * 标记所有站内信为已读 + * + * @param userId 用户编号 + * @param userType 用户类型 + * @return 更新到的条数 + */ + int updateAllNotifyMessageRead(Long userId, Integer userType); + +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/service/notify/NotifyMessageServiceImpl.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/service/notify/NotifyMessageServiceImpl.java new file mode 100644 index 0000000..0f41285 --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/service/notify/NotifyMessageServiceImpl.java @@ -0,0 +1,77 @@ +package com.tashow.cloud.system.service.notify; + +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.system.controller.admin.notify.vo.message.NotifyMessageMyPageReqVO; +import com.tashow.cloud.system.controller.admin.notify.vo.message.NotifyMessagePageReqVO; +import com.tashow.cloud.system.dal.dataobject.notify.NotifyMessageDO; +import com.tashow.cloud.system.dal.dataobject.notify.NotifyTemplateDO; +import com.tashow.cloud.system.dal.mysql.notify.NotifyMessageMapper; +import com.tashow.cloud.system.controller.admin.notify.vo.message.NotifyMessageMyPageReqVO; +import com.tashow.cloud.system.controller.admin.notify.vo.message.NotifyMessagePageReqVO; +import org.springframework.stereotype.Service; +import org.springframework.validation.annotation.Validated; + +import jakarta.annotation.Resource; +import java.util.Collection; +import java.util.List; +import java.util.Map; + +/** + * 站内信 Service 实现类 + * + * @author xrcoder + */ +@Service +@Validated +public class NotifyMessageServiceImpl implements NotifyMessageService { + + @Resource + private NotifyMessageMapper notifyMessageMapper; + + @Override + public Long createNotifyMessage(Long userId, Integer userType, + NotifyTemplateDO template, String templateContent, Map templateParams) { + NotifyMessageDO message = new NotifyMessageDO().setUserId(userId).setUserType(userType) + .setTemplateId(template.getId()).setTemplateCode(template.getCode()) + .setTemplateType(template.getType()).setTemplateNickname(template.getNickname()) + .setTemplateContent(templateContent).setTemplateParams(templateParams).setReadStatus(false); + notifyMessageMapper.insert(message); + return message.getId(); + } + + @Override + public PageResult getNotifyMessagePage(NotifyMessagePageReqVO pageReqVO) { + return notifyMessageMapper.selectPage(pageReqVO); + } + + @Override + public PageResult getMyMyNotifyMessagePage(NotifyMessageMyPageReqVO pageReqVO, Long userId, Integer userType) { + return notifyMessageMapper.selectPage(pageReqVO, userId, userType); + } + + @Override + public NotifyMessageDO getNotifyMessage(Long id) { + return notifyMessageMapper.selectById(id); + } + + @Override + public List getUnreadNotifyMessageList(Long userId, Integer userType, Integer size) { + return notifyMessageMapper.selectUnreadListByUserIdAndUserType(userId, userType, size); + } + + @Override + public Long getUnreadNotifyMessageCount(Long userId, Integer userType) { + return notifyMessageMapper.selectUnreadCountByUserIdAndUserType(userId, userType); + } + + @Override + public int updateNotifyMessageRead(Collection ids, Long userId, Integer userType) { + return notifyMessageMapper.updateListRead(ids, userId, userType); + } + + @Override + public int updateAllNotifyMessageRead(Long userId, Integer userType) { + return notifyMessageMapper.updateListRead(userId, userType); + } + +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/service/notify/NotifySendService.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/service/notify/NotifySendService.java new file mode 100644 index 0000000..0017aad --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/service/notify/NotifySendService.java @@ -0,0 +1,55 @@ +package com.tashow.cloud.system.service.notify; + +import java.util.List; +import java.util.Map; + +/** + * 站内信发送 Service 接口 + * + * @author xrcoder + */ +public interface NotifySendService { + + /** + * 发送单条站内信给管理后台的用户 + * + * 在 mobile 为空时,使用 userId 加载对应管理员的手机号 + * + * @param userId 用户编号 + * @param templateCode 短信模板编号 + * @param templateParams 短信模板参数 + * @return 发送日志编号 + */ + Long sendSingleNotifyToAdmin(Long userId, + String templateCode, Map templateParams); + /** + * 发送单条站内信给用户 APP 的用户 + * + * 在 mobile 为空时,使用 userId 加载对应会员的手机号 + * + * @param userId 用户编号 + * @param templateCode 站内信模板编号 + * @param templateParams 站内信模板参数 + * @return 发送日志编号 + */ + Long sendSingleNotifyToMember(Long userId, + String templateCode, Map templateParams); + + /** + * 发送单条站内信给用户 + * + * @param userId 用户编号 + * @param userType 用户类型 + * @param templateCode 站内信模板编号 + * @param templateParams 站内信模板参数 + * @return 发送日志编号 + */ + Long sendSingleNotify( Long userId, Integer userType, + String templateCode, Map templateParams); + + default void sendBatchNotify(List mobiles, List userIds, Integer userType, + String templateCode, Map templateParams) { + throw new UnsupportedOperationException("暂时不支持该操作,感兴趣可以实现该功能哟!"); + } + +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/service/notify/NotifySendServiceImpl.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/service/notify/NotifySendServiceImpl.java new file mode 100644 index 0000000..0a53bc3 --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/service/notify/NotifySendServiceImpl.java @@ -0,0 +1,88 @@ +package com.tashow.cloud.system.service.notify; + +import com.tashow.cloud.common.enums.CommonStatusEnum; +import com.tashow.cloud.common.enums.UserTypeEnum; +import com.tashow.cloud.system.dal.dataobject.notify.NotifyTemplateDO; +import com.google.common.annotations.VisibleForTesting; +import com.tashow.cloud.systemapi.enums.ErrorCodeConstants; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.validation.annotation.Validated; + +import jakarta.annotation.Resource; +import java.util.Map; +import java.util.Objects; + +import static com.tashow.cloud.common.exception.util.ServiceExceptionUtil.exception; +import static com.tashow.cloud.systemapi.enums.ErrorCodeConstants.NOTICE_NOT_FOUND; +import static com.tashow.cloud.systemapi.enums.ErrorCodeConstants.NOTIFY_SEND_TEMPLATE_PARAM_MISS; + +/** + * 站内信发送 Service 实现类 + * + * @author xrcoder + */ +@Service +@Validated +@Slf4j +public class NotifySendServiceImpl implements NotifySendService { + + @Resource + private NotifyTemplateService notifyTemplateService; + + @Resource + private NotifyMessageService notifyMessageService; + + @Override + public Long sendSingleNotifyToAdmin(Long userId, String templateCode, Map templateParams) { + return sendSingleNotify(userId, UserTypeEnum.ADMIN.getValue(), templateCode, templateParams); + } + + @Override + public Long sendSingleNotifyToMember(Long userId, String templateCode, Map templateParams) { + return sendSingleNotify(userId, UserTypeEnum.MEMBER.getValue(), templateCode, templateParams); + } + + @Override + public Long sendSingleNotify(Long userId, Integer userType, String templateCode, Map templateParams) { + // 校验模版 + NotifyTemplateDO template = validateNotifyTemplate(templateCode); + if (Objects.equals(template.getStatus(), CommonStatusEnum.DISABLE.getStatus())) { + log.info("[sendSingleNotify][模版({})已经关闭,无法给用户({}/{})发送]", templateCode, userId, userType); + return null; + } + // 校验参数 + validateTemplateParams(template, templateParams); + + // 发送站内信 + String content = notifyTemplateService.formatNotifyTemplateContent(template.getContent(), templateParams); + return notifyMessageService.createNotifyMessage(userId, userType, template, content, templateParams); + } + + @VisibleForTesting + public NotifyTemplateDO validateNotifyTemplate(String templateCode) { + // 获得站内信模板。考虑到效率,从缓存中获取 + NotifyTemplateDO template = notifyTemplateService.getNotifyTemplateByCodeFromCache(templateCode); + // 站内信模板不存在 + if (template == null) { + throw exception(ErrorCodeConstants.NOTICE_NOT_FOUND); + } + return template; + } + + /** + * 校验站内信模版参数是否确实 + * + * @param template 邮箱模板 + * @param templateParams 参数列表 + */ + @VisibleForTesting + public void validateTemplateParams(NotifyTemplateDO template, Map templateParams) { + template.getParams().forEach(key -> { + Object value = templateParams.get(key); + if (value == null) { + throw exception(ErrorCodeConstants.NOTIFY_SEND_TEMPLATE_PARAM_MISS, key); + } + }); + } +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/service/notify/NotifyTemplateService.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/service/notify/NotifyTemplateService.java new file mode 100644 index 0000000..2a66575 --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/service/notify/NotifyTemplateService.java @@ -0,0 +1,75 @@ +package com.tashow.cloud.system.service.notify; + +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.system.controller.admin.notify.vo.template.NotifyTemplatePageReqVO; +import com.tashow.cloud.system.controller.admin.notify.vo.template.NotifyTemplateSaveReqVO; +import com.tashow.cloud.system.dal.dataobject.notify.NotifyTemplateDO; + +import com.tashow.cloud.system.controller.admin.notify.vo.template.NotifyTemplatePageReqVO; +import com.tashow.cloud.system.controller.admin.notify.vo.template.NotifyTemplateSaveReqVO; +import jakarta.validation.Valid; +import java.util.Map; + +/** + * 站内信模版 Service 接口 + * + * @author xrcoder + */ +public interface NotifyTemplateService { + + /** + * 创建站内信模版 + * + * @param createReqVO 创建信息 + * @return 编号 + */ + Long createNotifyTemplate(@Valid NotifyTemplateSaveReqVO createReqVO); + + /** + * 更新站内信模版 + * + * @param updateReqVO 更新信息 + */ + void updateNotifyTemplate(@Valid NotifyTemplateSaveReqVO updateReqVO); + + /** + * 删除站内信模版 + * + * @param id 编号 + */ + void deleteNotifyTemplate(Long id); + + /** + * 获得站内信模版 + * + * @param id 编号 + * @return 站内信模版 + */ + NotifyTemplateDO getNotifyTemplate(Long id); + + /** + * 获得站内信模板,从缓存中 + * + * @param code 模板编码 + * @return 站内信模板 + */ + NotifyTemplateDO getNotifyTemplateByCodeFromCache(String code); + + /** + * 获得站内信模版分页 + * + * @param pageReqVO 分页查询 + * @return 站内信模版分页 + */ + PageResult getNotifyTemplatePage(NotifyTemplatePageReqVO pageReqVO); + + /** + * 格式化站内信内容 + * + * @param content 站内信模板的内容 + * @param params 站内信内容的参数 + * @return 格式化后的内容 + */ + String formatNotifyTemplateContent(String content, Map params); + +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/service/notify/NotifyTemplateServiceImpl.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/service/notify/NotifyTemplateServiceImpl.java new file mode 100644 index 0000000..7bebe78 --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/service/notify/NotifyTemplateServiceImpl.java @@ -0,0 +1,141 @@ +package com.tashow.cloud.system.service.notify; + +import cn.hutool.core.util.ReUtil; +import cn.hutool.core.util.StrUtil; +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.common.util.object.BeanUtils; +import com.tashow.cloud.system.controller.admin.notify.vo.template.NotifyTemplatePageReqVO; +import com.tashow.cloud.system.controller.admin.notify.vo.template.NotifyTemplateSaveReqVO; +import com.tashow.cloud.system.dal.dataobject.notify.NotifyTemplateDO; +import com.tashow.cloud.system.dal.mysql.notify.NotifyTemplateMapper; +import com.tashow.cloud.system.dal.redis.RedisKeyConstants; +import com.google.common.annotations.VisibleForTesting; +import com.tashow.cloud.system.controller.admin.notify.vo.template.NotifyTemplatePageReqVO; +import com.tashow.cloud.system.controller.admin.notify.vo.template.NotifyTemplateSaveReqVO; +import com.tashow.cloud.systemapi.enums.ErrorCodeConstants; +import lombok.extern.slf4j.Slf4j; +import org.springframework.cache.annotation.CacheEvict; +import org.springframework.cache.annotation.Cacheable; +import org.springframework.stereotype.Service; +import org.springframework.validation.annotation.Validated; + +import jakarta.annotation.Resource; +import java.util.List; +import java.util.Map; +import java.util.regex.Pattern; + +import static com.tashow.cloud.common.exception.util.ServiceExceptionUtil.exception; +import static com.tashow.cloud.systemapi.enums.ErrorCodeConstants.NOTIFY_TEMPLATE_CODE_DUPLICATE; +import static com.tashow.cloud.systemapi.enums.ErrorCodeConstants.NOTIFY_TEMPLATE_NOT_EXISTS; + +/** + * 站内信模版 Service 实现类 + * + * @author xrcoder + */ +@Service +@Validated +@Slf4j +public class NotifyTemplateServiceImpl implements NotifyTemplateService { + + /** + * 正则表达式,匹配 {} 中的变量 + */ + private static final Pattern PATTERN_PARAMS = Pattern.compile("\\{(.*?)}"); + + @Resource + private NotifyTemplateMapper notifyTemplateMapper; + + @Override + public Long createNotifyTemplate(NotifyTemplateSaveReqVO createReqVO) { + // 校验站内信编码是否重复 + validateNotifyTemplateCodeDuplicate(null, createReqVO.getCode()); + + // 插入 + NotifyTemplateDO notifyTemplate = BeanUtils.toBean(createReqVO, NotifyTemplateDO.class); + notifyTemplate.setParams(parseTemplateContentParams(notifyTemplate.getContent())); + notifyTemplateMapper.insert(notifyTemplate); + return notifyTemplate.getId(); + } + + @Override + @CacheEvict(cacheNames = RedisKeyConstants.NOTIFY_TEMPLATE, + allEntries = true) // allEntries 清空所有缓存,因为可能修改到 code 字段,不好清理 + public void updateNotifyTemplate(NotifyTemplateSaveReqVO updateReqVO) { + // 校验存在 + validateNotifyTemplateExists(updateReqVO.getId()); + // 校验站内信编码是否重复 + validateNotifyTemplateCodeDuplicate(updateReqVO.getId(), updateReqVO.getCode()); + + // 更新 + NotifyTemplateDO updateObj = BeanUtils.toBean(updateReqVO, NotifyTemplateDO.class); + updateObj.setParams(parseTemplateContentParams(updateObj.getContent())); + notifyTemplateMapper.updateById(updateObj); + } + + @VisibleForTesting + public List parseTemplateContentParams(String content) { + return ReUtil.findAllGroup1(PATTERN_PARAMS, content); + } + + @Override + @CacheEvict(cacheNames = RedisKeyConstants.NOTIFY_TEMPLATE, + allEntries = true) // allEntries 清空所有缓存,因为 id 不是直接的缓存 code,不好清理 + public void deleteNotifyTemplate(Long id) { + // 校验存在 + validateNotifyTemplateExists(id); + // 删除 + notifyTemplateMapper.deleteById(id); + } + + private void validateNotifyTemplateExists(Long id) { + if (notifyTemplateMapper.selectById(id) == null) { + throw exception(ErrorCodeConstants.NOTIFY_TEMPLATE_NOT_EXISTS); + } + } + + @Override + public NotifyTemplateDO getNotifyTemplate(Long id) { + return notifyTemplateMapper.selectById(id); + } + + @Override + @Cacheable(cacheNames = RedisKeyConstants.NOTIFY_TEMPLATE, key = "#code", + unless = "#result == null") + public NotifyTemplateDO getNotifyTemplateByCodeFromCache(String code) { + return notifyTemplateMapper.selectByCode(code); + } + + @Override + public PageResult getNotifyTemplatePage(NotifyTemplatePageReqVO pageReqVO) { + return notifyTemplateMapper.selectPage(pageReqVO); + } + + @VisibleForTesting + void validateNotifyTemplateCodeDuplicate(Long id, String code) { + NotifyTemplateDO template = notifyTemplateMapper.selectByCode(code); + if (template == null) { + return; + } + // 如果 id 为空,说明不用比较是否为相同 id 的字典类型 + if (id == null) { + throw exception(ErrorCodeConstants.NOTIFY_TEMPLATE_CODE_DUPLICATE, code); + } + if (!template.getId().equals(id)) { + throw exception(ErrorCodeConstants.NOTIFY_TEMPLATE_CODE_DUPLICATE, code); + } + } + + /** + * 格式化站内信内容 + * + * @param content 站内信模板的内容 + * @param params 站内信内容的参数 + * @return 格式化后的内容 + */ + @Override + public String formatNotifyTemplateContent(String content, Map params) { + return StrUtil.format(content, params); + } + +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/service/oauth2/OAuth2ApproveService.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/service/oauth2/OAuth2ApproveService.java new file mode 100644 index 0000000..682e006 --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/service/oauth2/OAuth2ApproveService.java @@ -0,0 +1,52 @@ +package com.tashow.cloud.system.service.oauth2; + +import com.tashow.cloud.system.dal.dataobject.oauth2.OAuth2ApproveDO; + +import java.util.Collection; +import java.util.List; +import java.util.Map; + +/** + * OAuth2 批准 Service 接口 + * + * 从功能上,和 Spring Security OAuth 的 ApprovalStoreUserApprovalHandler 的功能,记录用户针对指定客户端的授权,减少手动确定。 + * + * @author 芋道源码 + */ +public interface OAuth2ApproveService { + + /** + * 获得指定用户,针对指定客户端的指定授权,是否通过 + * + * 参考 ApprovalStoreUserApprovalHandler 的 checkForPreApproval 方法 + * + * @param userId 用户编号 + * @param userType 用户类型 + * @param clientId 客户端编号 + * @param requestedScopes 授权范围 + * @return 是否授权通过 + */ + boolean checkForPreApproval(Long userId, Integer userType, String clientId, Collection requestedScopes); + + /** + * 在用户发起批准时,基于 scopes 的选项,计算最终是否通过 + * + * @param userId 用户编号 + * @param userType 用户类型 + * @param clientId 客户端编号 + * @param requestedScopes 授权范围 + * @return 是否授权通过 + */ + boolean updateAfterApproval(Long userId, Integer userType, String clientId, Map requestedScopes); + + /** + * 获得用户的批准列表,排除已过期的 + * + * @param userId 用户编号 + * @param userType 用户类型 + * @param clientId 客户端编号 + * @return 是否授权通过 + */ + List getApproveList(Long userId, Integer userType, String clientId); + +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/service/oauth2/OAuth2ApproveServiceImpl.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/service/oauth2/OAuth2ApproveServiceImpl.java new file mode 100644 index 0000000..e392851 --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/service/oauth2/OAuth2ApproveServiceImpl.java @@ -0,0 +1,104 @@ +package com.tashow.cloud.system.service.oauth2; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.lang.Assert; +import com.tashow.cloud.common.util.date.DateUtils; +import com.tashow.cloud.system.dal.dataobject.oauth2.OAuth2ApproveDO; +import com.tashow.cloud.system.dal.dataobject.oauth2.OAuth2ClientDO; +import com.tashow.cloud.system.dal.mysql.oauth2.OAuth2ApproveMapper; +import com.google.common.annotations.VisibleForTesting; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.validation.annotation.Validated; + +import jakarta.annotation.Resource; +import java.time.LocalDateTime; +import java.util.*; + +import static com.tashow.cloud.common.util.collection.CollectionUtils.convertSet; + + +/** + * OAuth2 批准 Service 实现类 + * + * @author 芋道源码 + */ +@Service +@Validated +public class OAuth2ApproveServiceImpl implements OAuth2ApproveService { + + /** + * 批准的过期时间,默认 30 天 + */ + private static final Integer TIMEOUT = 30 * 24 * 60 * 60; // 单位:秒 + + @Resource + private OAuth2ClientService oauth2ClientService; + + @Resource + private OAuth2ApproveMapper oauth2ApproveMapper; + + @Override + @Transactional + public boolean checkForPreApproval(Long userId, Integer userType, String clientId, Collection requestedScopes) { + // 第一步,基于 Client 的自动授权计算,如果 scopes 都在自动授权中,则返回 true 通过 + OAuth2ClientDO clientDO = oauth2ClientService.validOAuthClientFromCache(clientId); + Assert.notNull(clientDO, "客户端不能为空"); // 防御性编程 + if (CollUtil.containsAll(clientDO.getAutoApproveScopes(), requestedScopes)) { + // gh-877 - if all scopes are auto approved, approvals still need to be added to the approval store. + LocalDateTime expireTime = LocalDateTime.now().plusSeconds(TIMEOUT); + for (String scope : requestedScopes) { + saveApprove(userId, userType, clientId, scope, true, expireTime); + } + return true; + } + + // 第二步,算上用户已经批准的授权。如果 scopes 都包含,则返回 true + List approveDOs = getApproveList(userId, userType, clientId); + Set scopes = convertSet(approveDOs, OAuth2ApproveDO::getScope, + OAuth2ApproveDO::getApproved); // 只保留未过期的 + 同意的 + return CollUtil.containsAll(scopes, requestedScopes); + } + + @Override + @Transactional + public boolean updateAfterApproval(Long userId, Integer userType, String clientId, Map requestedScopes) { + // 如果 requestedScopes 为空,说明没有要求,则返回 true 通过 + if (CollUtil.isEmpty(requestedScopes)) { + return true; + } + + // 更新批准的信息 + boolean success = false; // 需要至少有一个同意 + LocalDateTime expireTime = LocalDateTime.now().plusSeconds(TIMEOUT); + for (Map.Entry entry : requestedScopes.entrySet()) { + if (entry.getValue()) { + success = true; + } + saveApprove(userId, userType, clientId, entry.getKey(), entry.getValue(), expireTime); + } + return success; + } + + @Override + public List getApproveList(Long userId, Integer userType, String clientId) { + List approveDOs = oauth2ApproveMapper.selectListByUserIdAndUserTypeAndClientId( + userId, userType, clientId); + approveDOs.removeIf(o -> DateUtils.isExpired(o.getExpiresTime())); + return approveDOs; + } + + @VisibleForTesting + void saveApprove(Long userId, Integer userType, String clientId, + String scope, Boolean approved, LocalDateTime expireTime) { + // 先更新 + OAuth2ApproveDO approveDO = new OAuth2ApproveDO().setUserId(userId).setUserType(userType) + .setClientId(clientId).setScope(scope).setApproved(approved).setExpiresTime(expireTime); + if (oauth2ApproveMapper.update(approveDO) == 1) { + return; + } + // 失败,则说明不存在,进行更新 + oauth2ApproveMapper.insert(approveDO); + } + +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/service/oauth2/OAuth2ClientService.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/service/oauth2/OAuth2ClientService.java new file mode 100644 index 0000000..7bd944f --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/service/oauth2/OAuth2ClientService.java @@ -0,0 +1,92 @@ +package com.tashow.cloud.system.service.oauth2; + +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.system.controller.admin.oauth2.vo.client.OAuth2ClientPageReqVO; +import com.tashow.cloud.system.controller.admin.oauth2.vo.client.OAuth2ClientSaveReqVO; +import com.tashow.cloud.system.dal.dataobject.oauth2.OAuth2ClientDO; + +import com.tashow.cloud.system.controller.admin.oauth2.vo.client.OAuth2ClientPageReqVO; +import com.tashow.cloud.system.controller.admin.oauth2.vo.client.OAuth2ClientSaveReqVO; +import jakarta.validation.Valid; +import java.util.Collection; + +/** + * OAuth2.0 Client Service 接口 + * + * 从功能上,和 JdbcClientDetailsService 的功能,提供客户端的操作 + * + * @author 芋道源码 + */ +public interface OAuth2ClientService { + + /** + * 创建 OAuth2 客户端 + * + * @param createReqVO 创建信息 + * @return 编号 + */ + Long createOAuth2Client(@Valid OAuth2ClientSaveReqVO createReqVO); + + /** + * 更新 OAuth2 客户端 + * + * @param updateReqVO 更新信息 + */ + void updateOAuth2Client(@Valid OAuth2ClientSaveReqVO updateReqVO); + + /** + * 删除 OAuth2 客户端 + * + * @param id 编号 + */ + void deleteOAuth2Client(Long id); + + /** + * 获得 OAuth2 客户端 + * + * @param id 编号 + * @return OAuth2 客户端 + */ + OAuth2ClientDO getOAuth2Client(Long id); + + /** + * 获得 OAuth2 客户端,从缓存中 + * + * @param clientId 客户端编号 + * @return OAuth2 客户端 + */ + OAuth2ClientDO getOAuth2ClientFromCache(String clientId); + + /** + * 获得 OAuth2 客户端分页 + * + * @param pageReqVO 分页查询 + * @return OAuth2 客户端分页 + */ + PageResult getOAuth2ClientPage(OAuth2ClientPageReqVO pageReqVO); + + /** + * 从缓存中,校验客户端是否合法 + * + * @return 客户端 + */ + default OAuth2ClientDO validOAuthClientFromCache(String clientId) { + return validOAuthClientFromCache(clientId, null, null, null, null); + } + + /** + * 从缓存中,校验客户端是否合法 + * + * 非空时,进行校验 + * + * @param clientId 客户端编号 + * @param clientSecret 客户端密钥 + * @param authorizedGrantType 授权方式 + * @param scopes 授权范围 + * @param redirectUri 重定向地址 + * @return 客户端 + */ + OAuth2ClientDO validOAuthClientFromCache(String clientId, String clientSecret, String authorizedGrantType, + Collection scopes, String redirectUri); + +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/service/oauth2/OAuth2ClientServiceImpl.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/service/oauth2/OAuth2ClientServiceImpl.java new file mode 100644 index 0000000..66e02d6 --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/service/oauth2/OAuth2ClientServiceImpl.java @@ -0,0 +1,153 @@ +package com.tashow.cloud.system.service.oauth2; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.extra.spring.SpringUtil; +import com.tashow.cloud.common.enums.CommonStatusEnum; +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.common.util.object.BeanUtils; +import com.tashow.cloud.common.util.string.StrUtils; +import com.tashow.cloud.system.controller.admin.oauth2.vo.client.OAuth2ClientPageReqVO; +import com.tashow.cloud.system.controller.admin.oauth2.vo.client.OAuth2ClientSaveReqVO; +import com.tashow.cloud.system.dal.dataobject.oauth2.OAuth2ClientDO; +import com.tashow.cloud.system.dal.mysql.oauth2.OAuth2ClientMapper; +import com.tashow.cloud.system.dal.redis.RedisKeyConstants; +import com.google.common.annotations.VisibleForTesting; +import com.tashow.cloud.systemapi.enums.ErrorCodeConstants; +import lombok.extern.slf4j.Slf4j; +import org.springframework.cache.annotation.CacheEvict; +import org.springframework.cache.annotation.Cacheable; +import org.springframework.stereotype.Service; +import org.springframework.validation.annotation.Validated; + +import jakarta.annotation.Resource; +import java.util.Collection; + +import static com.tashow.cloud.common.exception.util.ServiceExceptionUtil.exception; + +/** + * OAuth2.0 Client Service 实现类 + * + * @author 芋道源码 + */ +@Service +@Validated +@Slf4j +public class OAuth2ClientServiceImpl implements OAuth2ClientService { + + @Resource + private OAuth2ClientMapper oauth2ClientMapper; + + @Override + public Long createOAuth2Client(OAuth2ClientSaveReqVO createReqVO) { + validateClientIdExists(null, createReqVO.getClientId()); + // 插入 + OAuth2ClientDO client = BeanUtils.toBean(createReqVO, OAuth2ClientDO.class); + oauth2ClientMapper.insert(client); + return client.getId(); + } + + @Override + @CacheEvict(cacheNames = RedisKeyConstants.OAUTH_CLIENT, + allEntries = true) // allEntries 清空所有缓存,因为可能修改到 clientId 字段,不好清理 + public void updateOAuth2Client(OAuth2ClientSaveReqVO updateReqVO) { + // 校验存在 + validateOAuth2ClientExists(updateReqVO.getId()); + // 校验 Client 未被占用 + validateClientIdExists(updateReqVO.getId(), updateReqVO.getClientId()); + + // 更新 + OAuth2ClientDO updateObj = BeanUtils.toBean(updateReqVO, OAuth2ClientDO.class); + oauth2ClientMapper.updateById(updateObj); + } + + @Override + @CacheEvict(cacheNames = RedisKeyConstants.OAUTH_CLIENT, + allEntries = true) // allEntries 清空所有缓存,因为 id 不是直接的缓存 key,不好清理 + public void deleteOAuth2Client(Long id) { + // 校验存在 + validateOAuth2ClientExists(id); + // 删除 + oauth2ClientMapper.deleteById(id); + } + + private void validateOAuth2ClientExists(Long id) { + if (oauth2ClientMapper.selectById(id) == null) { + throw exception(ErrorCodeConstants.OAUTH2_CLIENT_NOT_EXISTS); + } + } + + @VisibleForTesting + void validateClientIdExists(Long id, String clientId) { + OAuth2ClientDO client = oauth2ClientMapper.selectByClientId(clientId); + if (client == null) { + return; + } + // 如果 id 为空,说明不用比较是否为相同 id 的客户端 + if (id == null) { + throw exception(ErrorCodeConstants.OAUTH2_CLIENT_EXISTS); + } + if (!client.getId().equals(id)) { + throw exception(ErrorCodeConstants.OAUTH2_CLIENT_EXISTS); + } + } + + @Override + public OAuth2ClientDO getOAuth2Client(Long id) { + return oauth2ClientMapper.selectById(id); + } + + @Override + @Cacheable(cacheNames = RedisKeyConstants.OAUTH_CLIENT, key = "#clientId", + unless = "#result == null") + public OAuth2ClientDO getOAuth2ClientFromCache(String clientId) { + return oauth2ClientMapper.selectByClientId(clientId); + } + + @Override + public PageResult getOAuth2ClientPage(OAuth2ClientPageReqVO pageReqVO) { + return oauth2ClientMapper.selectPage(pageReqVO); + } + + @Override + public OAuth2ClientDO validOAuthClientFromCache(String clientId, String clientSecret, String authorizedGrantType, + Collection scopes, String redirectUri) { + // 校验客户端存在、且开启 + OAuth2ClientDO client = getSelf().getOAuth2ClientFromCache(clientId); + if (client == null) { + throw exception(ErrorCodeConstants.OAUTH2_CLIENT_NOT_EXISTS); + } + if (CommonStatusEnum.isDisable(client.getStatus())) { + throw exception(ErrorCodeConstants.OAUTH2_CLIENT_DISABLE); + } + + // 校验客户端密钥 + if (StrUtil.isNotEmpty(clientSecret) && ObjectUtil.notEqual(client.getSecret(), clientSecret)) { + throw exception(ErrorCodeConstants.OAUTH2_CLIENT_CLIENT_SECRET_ERROR); + } + // 校验授权方式 + if (StrUtil.isNotEmpty(authorizedGrantType) && !CollUtil.contains(client.getAuthorizedGrantTypes(), authorizedGrantType)) { + throw exception(ErrorCodeConstants.OAUTH2_CLIENT_AUTHORIZED_GRANT_TYPE_NOT_EXISTS); + } + // 校验授权范围 + if (CollUtil.isNotEmpty(scopes) && !CollUtil.containsAll(client.getScopes(), scopes)) { + throw exception(ErrorCodeConstants.OAUTH2_CLIENT_SCOPE_OVER); + } + // 校验回调地址 + if (StrUtil.isNotEmpty(redirectUri) && !StrUtils.startWithAny(redirectUri, client.getRedirectUris())) { + throw exception(ErrorCodeConstants.OAUTH2_CLIENT_REDIRECT_URI_NOT_MATCH, redirectUri); + } + return client; + } + + /** + * 获得自身的代理对象,解决 AOP 生效问题 + * + * @return 自己 + */ + private OAuth2ClientServiceImpl getSelf() { + return SpringUtil.getBean(getClass()); + } + +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/service/oauth2/OAuth2CodeService.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/service/oauth2/OAuth2CodeService.java new file mode 100644 index 0000000..694f2c9 --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/service/oauth2/OAuth2CodeService.java @@ -0,0 +1,39 @@ +package com.tashow.cloud.system.service.oauth2; + +import com.tashow.cloud.system.dal.dataobject.oauth2.OAuth2CodeDO; + +import java.util.List; + +/** + * OAuth2.0 授权码 Service 接口 + * + * 从功能上,和 Spring Security OAuth 的 JdbcAuthorizationCodeServices 的功能,提供授权码的操作 + * + * @author 芋道源码 + */ +public interface OAuth2CodeService { + + /** + * 创建授权码 + * + * 参考 JdbcAuthorizationCodeServices 的 createAuthorizationCode 方法 + * + * @param userId 用户编号 + * @param userType 用户类型 + * @param clientId 客户端编号 + * @param scopes 授权范围 + * @param redirectUri 重定向 URI + * @param state 状态 + * @return 授权码的信息 + */ + OAuth2CodeDO createAuthorizationCode(Long userId, Integer userType, String clientId, + List scopes, String redirectUri, String state); + + /** + * 使用授权码 + * + * @param code 授权码 + */ + OAuth2CodeDO consumeAuthorizationCode(String code); + +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/service/oauth2/OAuth2CodeServiceImpl.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/service/oauth2/OAuth2CodeServiceImpl.java new file mode 100644 index 0000000..dc57fbc --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/service/oauth2/OAuth2CodeServiceImpl.java @@ -0,0 +1,65 @@ +package com.tashow.cloud.system.service.oauth2; + +import cn.hutool.core.util.IdUtil; +import com.tashow.cloud.common.util.date.DateUtils; +import com.tashow.cloud.system.dal.dataobject.oauth2.OAuth2CodeDO; +import com.tashow.cloud.system.dal.mysql.oauth2.OAuth2CodeMapper; +import com.tashow.cloud.systemapi.enums.ErrorCodeConstants; +import org.springframework.stereotype.Service; +import org.springframework.validation.annotation.Validated; + +import jakarta.annotation.Resource; +import java.time.LocalDateTime; +import java.util.List; + +import static com.tashow.cloud.common.exception.util.ServiceExceptionUtil.exception; +import static com.tashow.cloud.systemapi.enums.ErrorCodeConstants.OAUTH2_CODE_EXPIRE; +import static com.tashow.cloud.systemapi.enums.ErrorCodeConstants.OAUTH2_CODE_NOT_EXISTS; + +/** + * OAuth2.0 授权码 Service 实现类 + * + * @author 芋道源码 + */ +@Service +@Validated +public class OAuth2CodeServiceImpl implements OAuth2CodeService { + + /** + * 授权码的过期时间,默认 5 分钟 + */ + private static final Integer TIMEOUT = 5 * 60; + + @Resource + private OAuth2CodeMapper oauth2CodeMapper; + + @Override + public OAuth2CodeDO createAuthorizationCode(Long userId, Integer userType, String clientId, + List scopes, String redirectUri, String state) { + OAuth2CodeDO codeDO = new OAuth2CodeDO().setCode(generateCode()) + .setUserId(userId).setUserType(userType) + .setClientId(clientId).setScopes(scopes) + .setExpiresTime(LocalDateTime.now().plusSeconds(TIMEOUT)) + .setRedirectUri(redirectUri).setState(state); + oauth2CodeMapper.insert(codeDO); + return codeDO; + } + + @Override + public OAuth2CodeDO consumeAuthorizationCode(String code) { + OAuth2CodeDO codeDO = oauth2CodeMapper.selectByCode(code); + if (codeDO == null) { + throw exception(ErrorCodeConstants.OAUTH2_CODE_NOT_EXISTS); + } + if (DateUtils.isExpired(codeDO.getExpiresTime())) { + throw exception(ErrorCodeConstants.OAUTH2_CODE_EXPIRE); + } + oauth2CodeMapper.deleteById(codeDO.getId()); + return codeDO; + } + + private static String generateCode() { + return IdUtil.fastSimpleUUID(); + } + +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/service/oauth2/OAuth2GrantService.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/service/oauth2/OAuth2GrantService.java new file mode 100644 index 0000000..38a6d5b --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/service/oauth2/OAuth2GrantService.java @@ -0,0 +1,113 @@ +package com.tashow.cloud.system.service.oauth2; + +import com.tashow.cloud.system.dal.dataobject.oauth2.OAuth2AccessTokenDO; + +import java.util.List; + +/** + * OAuth2 授予 Service 接口 + * + * 从功能上,和 Spring Security OAuth 的 TokenGranter 的功能,提供访问令牌、刷新令牌的操作 + * + * 将自身的 AdminUser 用户,授权给第三方应用,采用 OAuth2.0 的协议。 + * + * 问题:为什么自身也作为一个第三方应用,也走这套流程呢? + * 回复:当然可以这么做,采用 password 模式。考虑到大多数开发者使用不到这个特性,OAuth2.0 毕竟有一定学习成本,所以暂时没有采取这种方式。 + * + * @author 芋道源码 + */ +public interface OAuth2GrantService { + + /** + * 简化模式 + * + * 对应 Spring Security OAuth2 的 ImplicitTokenGranter 功能 + * + * @param userId 用户编号 + * @param userType 用户类型 + * @param clientId 客户端编号 + * @param scopes 授权范围 + * @return 访问令牌 + */ + OAuth2AccessTokenDO grantImplicit(Long userId, Integer userType, + String clientId, List scopes); + + /** + * 授权码模式,第一阶段,获得 code 授权码 + * + * 对应 Spring Security OAuth2 的 AuthorizationEndpoint 的 generateCode 方法 + * + * @param userId 用户编号 + * @param userType 用户类型 + * @param clientId 客户端编号 + * @param scopes 授权范围 + * @param redirectUri 重定向 URI + * @param state 状态 + * @return 授权码 + */ + String grantAuthorizationCodeForCode(Long userId, Integer userType, + String clientId, List scopes, + String redirectUri, String state); + + /** + * 授权码模式,第二阶段,获得 accessToken 访问令牌 + * + * 对应 Spring Security OAuth2 的 AuthorizationCodeTokenGranter 功能 + * + * @param clientId 客户端编号 + * @param code 授权码 + * @param redirectUri 重定向 URI + * @param state 状态 + * @return 访问令牌 + */ + OAuth2AccessTokenDO grantAuthorizationCodeForAccessToken(String clientId, String code, + String redirectUri, String state); + + /** + * 密码模式 + * + * 对应 Spring Security OAuth2 的 ResourceOwnerPasswordTokenGranter 功能 + * + * @param username 账号 + * @param password 密码 + * @param clientId 客户端编号 + * @param scopes 授权范围 + * @return 访问令牌 + */ + OAuth2AccessTokenDO grantPassword(String username, String password, + String clientId, List scopes); + + /** + * 刷新模式 + * + * 对应 Spring Security OAuth2 的 ResourceOwnerPasswordTokenGranter 功能 + * + * @param refreshToken 刷新令牌 + * @param clientId 客户端编号 + * @return 访问令牌 + */ + OAuth2AccessTokenDO grantRefreshToken(String refreshToken, String clientId); + + /** + * 客户端模式 + * + * 对应 Spring Security OAuth2 的 ClientCredentialsTokenGranter 功能 + * + * @param clientId 客户端编号 + * @param scopes 授权范围 + * @return 访问令牌 + */ + OAuth2AccessTokenDO grantClientCredentials(String clientId, List scopes); + + /** + * 移除访问令牌 + * + * 对应 Spring Security OAuth2 的 ConsumerTokenServices 的 revokeToken 方法 + * + * @param accessToken 访问令牌 + * @param clientId 客户端编号 + * @return 是否移除到 + */ + boolean revokeToken(String clientId, String accessToken); + +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/service/oauth2/OAuth2GrantServiceImpl.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/service/oauth2/OAuth2GrantServiceImpl.java new file mode 100644 index 0000000..aa0f2be --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/service/oauth2/OAuth2GrantServiceImpl.java @@ -0,0 +1,105 @@ +package com.tashow.cloud.system.service.oauth2; + +import cn.hutool.core.lang.Assert; +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.StrUtil; +import com.tashow.cloud.common.enums.UserTypeEnum; +import com.tashow.cloud.system.dal.dataobject.oauth2.OAuth2AccessTokenDO; +import com.tashow.cloud.system.dal.dataobject.oauth2.OAuth2CodeDO; +import com.tashow.cloud.system.dal.dataobject.user.AdminUserDO; +import com.tashow.cloud.systemapi.enums.ErrorCodeConstants; +import com.tashow.cloud.system.service.auth.AdminAuthService; +import com.tashow.cloud.systemapi.enums.ErrorCodeConstants; +import org.springframework.stereotype.Service; + +import jakarta.annotation.Resource; +import java.util.List; + +import static com.tashow.cloud.common.exception.util.ServiceExceptionUtil.exception; + +/** + * OAuth2 授予 Service 实现类 + * + * @author 芋道源码 + */ +@Service +public class OAuth2GrantServiceImpl implements OAuth2GrantService { + + @Resource + private OAuth2TokenService oauth2TokenService; + @Resource + private OAuth2CodeService oauth2CodeService; + @Resource + private AdminAuthService adminAuthService; + + @Override + public OAuth2AccessTokenDO grantImplicit(Long userId, Integer userType, + String clientId, List scopes) { + return oauth2TokenService.createAccessToken(userId, userType, clientId, scopes); + } + + @Override + public String grantAuthorizationCodeForCode(Long userId, Integer userType, + String clientId, List scopes, + String redirectUri, String state) { + return oauth2CodeService.createAuthorizationCode(userId, userType, clientId, scopes, + redirectUri, state).getCode(); + } + + @Override + public OAuth2AccessTokenDO grantAuthorizationCodeForAccessToken(String clientId, String code, + String redirectUri, String state) { + OAuth2CodeDO codeDO = oauth2CodeService.consumeAuthorizationCode(code); + Assert.notNull(codeDO, "授权码不能为空"); // 防御性编程 + // 校验 clientId 是否匹配 + if (!StrUtil.equals(clientId, codeDO.getClientId())) { + throw exception(ErrorCodeConstants.OAUTH2_GRANT_CLIENT_ID_MISMATCH); + } + // 校验 redirectUri 是否匹配 + if (!StrUtil.equals(redirectUri, codeDO.getRedirectUri())) { + throw exception(ErrorCodeConstants.OAUTH2_GRANT_REDIRECT_URI_MISMATCH); + } + // 校验 state 是否匹配 + state = StrUtil.nullToDefault(state, ""); // 数据库 state 为 null 时,会设置为 "" 空串 + if (!StrUtil.equals(state, codeDO.getState())) { + throw exception(ErrorCodeConstants.OAUTH2_GRANT_STATE_MISMATCH); + } + + // 创建访问令牌 + return oauth2TokenService.createAccessToken(codeDO.getUserId(), codeDO.getUserType(), + codeDO.getClientId(), codeDO.getScopes()); + } + + @Override + public OAuth2AccessTokenDO grantPassword(String username, String password, String clientId, List scopes) { + // 使用账号 + 密码进行登录 + AdminUserDO user = adminAuthService.authenticate(username, password); + Assert.notNull(user, "用户不能为空!"); // 防御性编程 + + // 创建访问令牌 + return oauth2TokenService.createAccessToken(user.getId(), UserTypeEnum.ADMIN.getValue(), clientId, scopes); + } + + @Override + public OAuth2AccessTokenDO grantRefreshToken(String refreshToken, String clientId) { + return oauth2TokenService.refreshAccessToken(refreshToken, clientId); + } + + @Override + public OAuth2AccessTokenDO grantClientCredentials(String clientId, List scopes) { + // TODO 芋艿:项目中使用 OAuth2 解决的是三方应用的授权,内部的 SSO 等问题,所以暂时不考虑 client_credentials 这个场景 + throw new UnsupportedOperationException("暂时不支持 client_credentials 授权模式"); + } + + @Override + public boolean revokeToken(String clientId, String accessToken) { + // 先查询,保证 clientId 时匹配的 + OAuth2AccessTokenDO accessTokenDO = oauth2TokenService.getAccessToken(accessToken); + if (accessTokenDO == null || ObjectUtil.notEqual(clientId, accessTokenDO.getClientId())) { + return false; + } + // 再删除 + return oauth2TokenService.removeAccessToken(accessToken) != null; + } + +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/service/oauth2/OAuth2TokenService.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/service/oauth2/OAuth2TokenService.java new file mode 100644 index 0000000..e141690 --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/service/oauth2/OAuth2TokenService.java @@ -0,0 +1,81 @@ +package com.tashow.cloud.system.service.oauth2; + +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.system.controller.admin.oauth2.vo.token.OAuth2AccessTokenPageReqVO; +import com.tashow.cloud.system.dal.dataobject.oauth2.OAuth2AccessTokenDO; +import com.tashow.cloud.system.controller.admin.oauth2.vo.token.OAuth2AccessTokenPageReqVO; + +import java.util.List; + +/** + * OAuth2.0 Token Service 接口 + * + * 从功能上,和 Spring Security OAuth 的 DefaultTokenServices + JdbcTokenStore 的功能,提供访问令牌、刷新令牌的操作 + * + * @author 芋道源码 + */ +public interface OAuth2TokenService { + + /** + * 创建访问令牌 + * 注意:该流程中,会包含创建刷新令牌的创建 + * + * 参考 DefaultTokenServices 的 createAccessToken 方法 + * + * @param userId 用户编号 + * @param userType 用户类型 + * @param clientId 客户端编号 + * @param scopes 授权范围 + * @return 访问令牌的信息 + */ + OAuth2AccessTokenDO createAccessToken(Long userId, Integer userType, String clientId, List scopes); + + /** + * 刷新访问令牌 + * + * 参考 DefaultTokenServices 的 refreshAccessToken 方法 + * + * @param refreshToken 刷新令牌 + * @param clientId 客户端编号 + * @return 访问令牌的信息 + */ + OAuth2AccessTokenDO refreshAccessToken(String refreshToken, String clientId); + + /** + * 获得访问令牌 + * + * 参考 DefaultTokenServices 的 getAccessToken 方法 + * + * @param accessToken 访问令牌 + * @return 访问令牌的信息 + */ + OAuth2AccessTokenDO getAccessToken(String accessToken); + + /** + * 校验访问令牌 + * + * @param accessToken 访问令牌 + * @return 访问令牌的信息 + */ + OAuth2AccessTokenDO checkAccessToken(String accessToken); + + /** + * 移除访问令牌 + * 注意:该流程中,会移除相关的刷新令牌 + * + * 参考 DefaultTokenServices 的 revokeToken 方法 + * + * @param accessToken 刷新令牌 + * @return 访问令牌的信息 + */ + OAuth2AccessTokenDO removeAccessToken(String accessToken); + + /** + * 获得访问令牌分页 + * + * @param reqVO 请求 + * @return 访问令牌分页 + */ + PageResult getAccessTokenPage(OAuth2AccessTokenPageReqVO reqVO); + +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/service/oauth2/OAuth2TokenServiceImpl.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/service/oauth2/OAuth2TokenServiceImpl.java new file mode 100644 index 0000000..2d6bdbe --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/service/oauth2/OAuth2TokenServiceImpl.java @@ -0,0 +1,220 @@ +package com.tashow.cloud.system.service.oauth2; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.map.MapUtil; +import cn.hutool.core.util.IdUtil; +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.StrUtil; +import com.tashow.cloud.common.enums.UserTypeEnum; +import com.tashow.cloud.common.exception.enums.GlobalErrorCodeConstants; +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.common.util.date.DateUtils; +import com.tashow.cloud.common.util.object.BeanUtils; +import com.tashow.cloud.security.security.core.LoginUser; +import com.tashow.cloud.system.controller.admin.oauth2.vo.token.OAuth2AccessTokenPageReqVO; +import com.tashow.cloud.system.dal.dataobject.oauth2.OAuth2AccessTokenDO; +import com.tashow.cloud.system.dal.dataobject.oauth2.OAuth2ClientDO; +import com.tashow.cloud.system.dal.dataobject.oauth2.OAuth2RefreshTokenDO; +import com.tashow.cloud.system.dal.dataobject.user.AdminUserDO; +import com.tashow.cloud.system.dal.mysql.oauth2.OAuth2AccessTokenMapper; +import com.tashow.cloud.system.dal.mysql.oauth2.OAuth2RefreshTokenMapper; +import com.tashow.cloud.system.dal.redis.oauth2.OAuth2AccessTokenRedisDAO; +import com.tashow.cloud.system.service.user.AdminUserService; +import com.tashow.cloud.tenant.core.context.TenantContextHolder; +import com.tashow.cloud.tenant.core.util.TenantUtils; +import jakarta.annotation.Resource; +import org.springframework.context.annotation.Lazy; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.time.LocalDateTime; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +import static com.tashow.cloud.common.exception.util.ServiceExceptionUtil.exception0; +import static com.tashow.cloud.common.util.collection.CollectionUtils.convertSet; + + +/** + * OAuth2.0 Token Service 实现类 + * + * @author 芋道源码 + */ +@Service +public class OAuth2TokenServiceImpl implements OAuth2TokenService { + + @Resource + private OAuth2AccessTokenMapper oauth2AccessTokenMapper; + @Resource + private OAuth2RefreshTokenMapper oauth2RefreshTokenMapper; + + @Resource + private OAuth2AccessTokenRedisDAO oauth2AccessTokenRedisDAO; + + @Resource + private OAuth2ClientService oauth2ClientService; + @Resource + @Lazy // 懒加载,避免循环依赖 + private AdminUserService adminUserService; + + @Override + @Transactional(rollbackFor = Exception.class) + public OAuth2AccessTokenDO createAccessToken(Long userId, Integer userType, String clientId, List scopes) { + OAuth2ClientDO clientDO = oauth2ClientService.validOAuthClientFromCache(clientId); + // 创建刷新令牌 + OAuth2RefreshTokenDO refreshTokenDO = createOAuth2RefreshToken(userId, userType, clientDO, scopes); + // 创建访问令牌 + return createOAuth2AccessToken(refreshTokenDO, clientDO); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public OAuth2AccessTokenDO refreshAccessToken(String refreshToken, String clientId) { + // 查询访问令牌 + OAuth2RefreshTokenDO refreshTokenDO = oauth2RefreshTokenMapper.selectByRefreshToken(refreshToken); + if (refreshTokenDO == null) { + throw exception0(GlobalErrorCodeConstants.BAD_REQUEST.getCode(), "无效的刷新令牌"); + } + + // 校验 Client 匹配 + OAuth2ClientDO clientDO = oauth2ClientService.validOAuthClientFromCache(clientId); + if (ObjectUtil.notEqual(clientId, refreshTokenDO.getClientId())) { + throw exception0(GlobalErrorCodeConstants.BAD_REQUEST.getCode(), "刷新令牌的客户端编号不正确"); + } + + // 移除相关的访问令牌 + List accessTokenDOs = oauth2AccessTokenMapper.selectListByRefreshToken(refreshToken); + if (CollUtil.isNotEmpty(accessTokenDOs)) { + oauth2AccessTokenMapper.deleteByIds(convertSet(accessTokenDOs, OAuth2AccessTokenDO::getId)); + oauth2AccessTokenRedisDAO.deleteList(convertSet(accessTokenDOs, OAuth2AccessTokenDO::getAccessToken)); + } + + // 已过期的情况下,删除刷新令牌 + if (DateUtils.isExpired(refreshTokenDO.getExpiresTime())) { + oauth2RefreshTokenMapper.deleteById(refreshTokenDO.getId()); + throw exception0(GlobalErrorCodeConstants.UNAUTHORIZED.getCode(), "刷新令牌已过期"); + } + + // 创建访问令牌 + return createOAuth2AccessToken(refreshTokenDO, clientDO); + } + + @Override + public OAuth2AccessTokenDO getAccessToken(String accessToken) { + // 优先从 Redis 中获取 + OAuth2AccessTokenDO accessTokenDO = oauth2AccessTokenRedisDAO.get(accessToken); + if (accessTokenDO != null) { + return accessTokenDO; + } + + // 获取不到,从 MySQL 中获取访问令牌 + accessTokenDO = oauth2AccessTokenMapper.selectByAccessToken(accessToken); + if (accessTokenDO == null) { + // 特殊:从 MySQL 中获取刷新令牌。原因:解决部分场景不方便刷新访问令牌场景 + // 例如说,积木报表只允许传递 token,不允许传递 refresh_token,导致无法刷新访问令牌 + // 再例如说,前端 WebSocket 的 token 直接跟在 url 上,无法传递 refresh_token + OAuth2RefreshTokenDO refreshTokenDO = oauth2RefreshTokenMapper.selectByRefreshToken(accessToken); + if (refreshTokenDO != null && !DateUtils.isExpired(refreshTokenDO.getExpiresTime())) { + accessTokenDO = convertToAccessToken(refreshTokenDO); + } + } + + // 如果在 MySQL 存在,则往 Redis 中写入 + if (accessTokenDO != null && !DateUtils.isExpired(accessTokenDO.getExpiresTime())) { + oauth2AccessTokenRedisDAO.set(accessTokenDO); + } + return accessTokenDO; + } + + @Override + public OAuth2AccessTokenDO checkAccessToken(String accessToken) { + OAuth2AccessTokenDO accessTokenDO = getAccessToken(accessToken); + if (accessTokenDO == null) { + throw exception0(GlobalErrorCodeConstants.UNAUTHORIZED.getCode(), "访问令牌不存在"); + } + if (DateUtils.isExpired(accessTokenDO.getExpiresTime())) { + throw exception0(GlobalErrorCodeConstants.UNAUTHORIZED.getCode(), "访问令牌已过期"); + } + return accessTokenDO; + } + + @Override + @Transactional(rollbackFor = Exception.class) + public OAuth2AccessTokenDO removeAccessToken(String accessToken) { + // 删除访问令牌 + OAuth2AccessTokenDO accessTokenDO = oauth2AccessTokenMapper.selectByAccessToken(accessToken); + if (accessTokenDO == null) { + return null; + } + oauth2AccessTokenMapper.deleteById(accessTokenDO.getId()); + oauth2AccessTokenRedisDAO.delete(accessToken); + // 删除刷新令牌 + oauth2RefreshTokenMapper.deleteByRefreshToken(accessTokenDO.getRefreshToken()); + return accessTokenDO; + } + + @Override + public PageResult getAccessTokenPage(OAuth2AccessTokenPageReqVO reqVO) { + return oauth2AccessTokenMapper.selectPage(reqVO); + } + + private OAuth2AccessTokenDO createOAuth2AccessToken(OAuth2RefreshTokenDO refreshTokenDO, OAuth2ClientDO clientDO) { + OAuth2AccessTokenDO accessTokenDO = new OAuth2AccessTokenDO().setAccessToken(generateAccessToken()) + .setUserId(refreshTokenDO.getUserId()).setUserType(refreshTokenDO.getUserType()) + .setUserInfo(buildUserInfo(refreshTokenDO.getUserId(), refreshTokenDO.getUserType())) + .setClientId(clientDO.getClientId()).setScopes(refreshTokenDO.getScopes()) + .setRefreshToken(refreshTokenDO.getRefreshToken()) + .setExpiresTime(LocalDateTime.now().plusSeconds(clientDO.getAccessTokenValiditySeconds())); + accessTokenDO.setTenantId(TenantContextHolder.getTenantId()); // 手动设置租户编号,避免缓存到 Redis 的时候,无对应的租户编号 + oauth2AccessTokenMapper.insert(accessTokenDO); + // 记录到 Redis 中 + oauth2AccessTokenRedisDAO.set(accessTokenDO); + return accessTokenDO; + } + + private OAuth2RefreshTokenDO createOAuth2RefreshToken(Long userId, Integer userType, OAuth2ClientDO clientDO, List scopes) { + OAuth2RefreshTokenDO refreshToken = new OAuth2RefreshTokenDO().setRefreshToken(generateRefreshToken()) + .setUserId(userId).setUserType(userType) + .setClientId(clientDO.getClientId()).setScopes(scopes) + .setExpiresTime(LocalDateTime.now().plusSeconds(clientDO.getRefreshTokenValiditySeconds())); + oauth2RefreshTokenMapper.insert(refreshToken); + return refreshToken; + } + + private OAuth2AccessTokenDO convertToAccessToken(OAuth2RefreshTokenDO refreshTokenDO) { + OAuth2AccessTokenDO accessTokenDO = BeanUtils.toBean(refreshTokenDO, OAuth2AccessTokenDO.class) + .setAccessToken(refreshTokenDO.getRefreshToken()); + TenantUtils.execute(refreshTokenDO.getTenantId(), + () -> accessTokenDO.setUserInfo(buildUserInfo(refreshTokenDO.getUserId(), refreshTokenDO.getUserType()))); + return accessTokenDO; + } + + /** + * 加载用户信息,方便 {@link LoginUser} 获取到昵称、部门等信息 + * + * @param userId 用户编号 + * @param userType 用户类型 + * @return 用户信息 + */ + private Map buildUserInfo(Long userId, Integer userType) { + if (userType.equals(UserTypeEnum.ADMIN.getValue())) { + AdminUserDO user = adminUserService.getUser(userId); + return MapUtil.builder(LoginUser.INFO_KEY_NICKNAME, user.getNickname()) + .put(LoginUser.INFO_KEY_DEPT_ID, StrUtil.toStringOrNull(user.getDeptId())).build(); + } else if (userType.equals(UserTypeEnum.MEMBER.getValue())) { + // 注意:目前 Member 暂时不读取,可以按需实现 + return Collections.emptyMap(); + } + return null; + } + + private static String generateAccessToken() { + return IdUtil.fastSimpleUUID(); + } + + private static String generateRefreshToken() { + return IdUtil.fastSimpleUUID(); + } + +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/service/permission/MenuService.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/service/permission/MenuService.java new file mode 100644 index 0000000..1a6e7ba --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/service/permission/MenuService.java @@ -0,0 +1,97 @@ +package com.tashow.cloud.system.service.permission; + +import com.tashow.cloud.system.controller.admin.permission.vo.menu.MenuListReqVO; +import com.tashow.cloud.system.controller.admin.permission.vo.menu.MenuSaveVO; +import com.tashow.cloud.system.dal.dataobject.permission.MenuDO; +import com.tashow.cloud.system.controller.admin.permission.vo.menu.MenuListReqVO; +import com.tashow.cloud.system.controller.admin.permission.vo.menu.MenuSaveVO; + +import java.util.Collection; +import java.util.List; + +/** + * 菜单 Service 接口 + * + * @author 芋道源码 + */ +public interface MenuService { + + /** + * 创建菜单 + * + * @param createReqVO 菜单信息 + * @return 创建出来的菜单编号 + */ + Long createMenu(MenuSaveVO createReqVO); + + /** + * 更新菜单 + * + * @param updateReqVO 菜单信息 + */ + void updateMenu(MenuSaveVO updateReqVO); + + /** + * 删除菜单 + * + * @param id 菜单编号 + */ + void deleteMenu(Long id); + + /** + * 获得所有菜单列表 + * + * @return 菜单列表 + */ + List getMenuList(); + + /** + * 基于租户,筛选菜单列表 + * 注意,如果是系统租户,返回的还是全菜单 + * + * @param reqVO 筛选条件请求 VO + * @return 菜单列表 + */ + List getMenuListByTenant(MenuListReqVO reqVO); + + /** + * 过滤掉关闭的菜单及其子菜单 + * + * @param list 菜单列表 + * @return 过滤后的菜单列表 + */ + List filterDisableMenus(List list); + + /** + * 筛选菜单列表 + * + * @param reqVO 筛选条件请求 VO + * @return 菜单列表 + */ + List getMenuList(MenuListReqVO reqVO); + + /** + * 获得权限对应的菜单编号数组 + * + * @param permission 权限标识 + * @return 数组 + */ + List getMenuIdListByPermissionFromCache(String permission); + + /** + * 获得菜单 + * + * @param id 菜单编号 + * @return 菜单 + */ + MenuDO getMenu(Long id); + + /** + * 获得菜单数组 + * + * @param ids 菜单编号数组 + * @return 菜单数组 + */ + List getMenuList(Collection ids); + +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/service/permission/MenuServiceImpl.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/service/permission/MenuServiceImpl.java new file mode 100644 index 0000000..e0ce4b8 --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/service/permission/MenuServiceImpl.java @@ -0,0 +1,262 @@ +package com.tashow.cloud.system.service.permission; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.util.ObjUtil; +import com.tashow.cloud.common.enums.CommonStatusEnum; +import com.tashow.cloud.common.util.object.BeanUtils; +import com.tashow.cloud.system.controller.admin.permission.vo.menu.MenuListReqVO; +import com.tashow.cloud.system.controller.admin.permission.vo.menu.MenuSaveVO; +import com.tashow.cloud.system.dal.dataobject.permission.MenuDO; +import com.tashow.cloud.system.dal.mysql.permission.MenuMapper; +import com.tashow.cloud.system.dal.redis.RedisKeyConstants; +import com.tashow.cloud.systemapi.enums.permission.MenuTypeEnum; +import com.tashow.cloud.system.service.tenant.TenantService; +import com.google.common.annotations.VisibleForTesting; +import com.google.common.collect.Lists; +import com.tashow.cloud.systemapi.enums.ErrorCodeConstants; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import org.springframework.cache.annotation.CacheEvict; +import org.springframework.cache.annotation.Cacheable; +import org.springframework.context.annotation.Lazy; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.*; + +import static com.tashow.cloud.common.exception.util.ServiceExceptionUtil.exception; +import static com.tashow.cloud.common.util.collection.CollectionUtils.convertList; +import static com.tashow.cloud.common.util.collection.CollectionUtils.convertMap; +import static com.tashow.cloud.system.dal.dataobject.permission.MenuDO.ID_ROOT; + +/** + * 菜单 Service 实现 + * + * @author 芋道源码 + */ +@Service +@Slf4j +public class MenuServiceImpl implements MenuService { + + @Resource + private MenuMapper menuMapper; + @Resource + private PermissionService permissionService; + @Resource + @Lazy // 延迟,避免循环依赖报错 + private TenantService tenantService; + + @Override + @CacheEvict(value = RedisKeyConstants.PERMISSION_MENU_ID_LIST, key = "#createReqVO.permission", + condition = "#createReqVO.permission != null") + public Long createMenu(MenuSaveVO createReqVO) { + // 校验父菜单存在 + validateParentMenu(createReqVO.getParentId(), null); + // 校验菜单(自己) + validateMenu(createReqVO.getParentId(), createReqVO.getName(), null); + + // 插入数据库 + MenuDO menu = BeanUtils.toBean(createReqVO, MenuDO.class); + initMenuProperty(menu); + menuMapper.insert(menu); + // 返回 + return menu.getId(); + } + + @Override + @CacheEvict(value = RedisKeyConstants.PERMISSION_MENU_ID_LIST, + allEntries = true) // allEntries 清空所有缓存,因为 permission 如果变更,涉及到新老两个 permission。直接清理,简单有效 + public void updateMenu(MenuSaveVO updateReqVO) { + // 校验更新的菜单是否存在 + if (menuMapper.selectById(updateReqVO.getId()) == null) { + throw exception(ErrorCodeConstants.MENU_NOT_EXISTS); + } + // 校验父菜单存在 + validateParentMenu(updateReqVO.getParentId(), updateReqVO.getId()); + // 校验菜单(自己) + validateMenu(updateReqVO.getParentId(), updateReqVO.getName(), updateReqVO.getId()); + + // 更新到数据库 + MenuDO updateObj = BeanUtils.toBean(updateReqVO, MenuDO.class); + initMenuProperty(updateObj); + menuMapper.updateById(updateObj); + } + + @Override + @Transactional(rollbackFor = Exception.class) + @CacheEvict(value = RedisKeyConstants.PERMISSION_MENU_ID_LIST, + allEntries = true) // allEntries 清空所有缓存,因为此时不知道 id 对应的 permission 是多少。直接清理,简单有效 + public void deleteMenu(Long id) { + // 校验是否还有子菜单 + if (menuMapper.selectCountByParentId(id) > 0) { + throw exception(ErrorCodeConstants.MENU_EXISTS_CHILDREN); + } + // 校验删除的菜单是否存在 + if (menuMapper.selectById(id) == null) { + throw exception(ErrorCodeConstants.MENU_NOT_EXISTS); + } + // 标记删除 + menuMapper.deleteById(id); + // 删除授予给角色的权限 + permissionService.processMenuDeleted(id); + } + + @Override + public List getMenuList() { + return menuMapper.selectList(); + } + + @Override + public List getMenuListByTenant(MenuListReqVO reqVO) { + // 查询所有菜单,并过滤掉关闭的节点 + List menus = getMenuList(reqVO); + // 开启多租户的情况下,需要过滤掉未开通的菜单 + tenantService.handleTenantMenu(menuIds -> menus.removeIf(menu -> !CollUtil.contains(menuIds, menu.getId()))); + return menus; + } + + @Override + public List filterDisableMenus(List menuList) { + if (CollUtil.isEmpty(menuList)){ + return Collections.emptyList(); + } + Map menuMap = convertMap(menuList, MenuDO::getId); + + // 遍历 menu 菜单,查找不是禁用的菜单,添加到 enabledMenus 结果 + List enabledMenus = new ArrayList<>(); + Set disabledMenuCache = new HashSet<>(); // 存下递归搜索过被禁用的菜单,防止重复的搜索 + for (MenuDO menu : menuList) { + if (isMenuDisabled(menu, menuMap, disabledMenuCache)) { + continue; + } + enabledMenus.add(menu); + } + return enabledMenus; + } + + private boolean isMenuDisabled(MenuDO node, Map menuMap, Set disabledMenuCache) { + // 如果已经判定是禁用的节点,直接结束 + if (disabledMenuCache.contains(node.getId())) { + return true; + } + + // 1. 先判断自身是否禁用 + if (CommonStatusEnum.isDisable(node.getStatus())) { + disabledMenuCache.add(node.getId()); + return true; + } + + // 2. 遍历到 parentId 为根节点,则无需判断 + Long parentId = node.getParentId(); + if (ObjUtil.equal(parentId, ID_ROOT)) { + return false; + } + + // 3. 继续遍历 parent 节点 + MenuDO parent = menuMap.get(parentId); + if (parent == null || isMenuDisabled(parent, menuMap, disabledMenuCache)) { + disabledMenuCache.add(node.getId()); + return true; + } + return false; + } + + @Override + public List getMenuList(MenuListReqVO reqVO) { + return menuMapper.selectList(reqVO); + } + + @Override + @Cacheable(value = RedisKeyConstants.PERMISSION_MENU_ID_LIST, key = "#permission") + public List getMenuIdListByPermissionFromCache(String permission) { + List menus = menuMapper.selectListByPermission(permission); + return convertList(menus, MenuDO::getId); + } + + @Override + public MenuDO getMenu(Long id) { + return menuMapper.selectById(id); + } + + @Override + public List getMenuList(Collection ids) { + // 当 ids 为空时,返回一个空的实例对象 + if (CollUtil.isEmpty(ids)) { + return Lists.newArrayList(); + } + return menuMapper.selectBatchIds(ids); + } + + /** + * 校验父菜单是否合法 + *

+ * 1. 不能设置自己为父菜单 + * 2. 父菜单不存在 + * 3. 父菜单必须是 {@link MenuTypeEnum#MENU} 菜单类型 + * + * @param parentId 父菜单编号 + * @param childId 当前菜单编号 + */ + @VisibleForTesting + void validateParentMenu(Long parentId, Long childId) { + if (parentId == null || ID_ROOT.equals(parentId)) { + return; + } + // 不能设置自己为父菜单 + if (parentId.equals(childId)) { + throw exception(ErrorCodeConstants.MENU_PARENT_ERROR); + } + MenuDO menu = menuMapper.selectById(parentId); + // 父菜单不存在 + if (menu == null) { + throw exception(ErrorCodeConstants.MENU_PARENT_NOT_EXISTS); + } + // 父菜单必须是目录或者菜单类型 + if (!MenuTypeEnum.DIR.getType().equals(menu.getType()) + && !MenuTypeEnum.MENU.getType().equals(menu.getType())) { + throw exception(ErrorCodeConstants.MENU_PARENT_NOT_DIR_OR_MENU); + } + } + + /** + * 校验菜单是否合法 + *

+ * 1. 校验相同父菜单编号下,是否存在相同的菜单名 + * + * @param name 菜单名字 + * @param parentId 父菜单编号 + * @param id 菜单编号 + */ + @VisibleForTesting + void validateMenu(Long parentId, String name, Long id) { + MenuDO menu = menuMapper.selectByParentIdAndName(parentId, name); + if (menu == null) { + return; + } + // 如果 id 为空,说明不用比较是否为相同 id 的菜单 + if (id == null) { + throw exception(ErrorCodeConstants.MENU_NAME_DUPLICATE); + } + if (!menu.getId().equals(id)) { + throw exception(ErrorCodeConstants.MENU_NAME_DUPLICATE); + } + } + + /** + * 初始化菜单的通用属性。 + *

+ * 例如说,只有目录或者菜单类型的菜单,才设置 icon + * + * @param menu 菜单 + */ + private void initMenuProperty(MenuDO menu) { + // 菜单为按钮类型时,无需 component、icon、path 属性,进行置空 + if (MenuTypeEnum.BUTTON.getType().equals(menu.getType())) { + menu.setComponent(""); + menu.setComponentName(""); + menu.setIcon(""); + menu.setPath(""); + } + } + +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/service/permission/PermissionService.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/service/permission/PermissionService.java new file mode 100644 index 0000000..ac6137c --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/service/permission/PermissionService.java @@ -0,0 +1,147 @@ +package com.tashow.cloud.system.service.permission; + +import com.tashow.cloud.systemapi.api.permission.dto.DeptDataPermissionRespDTO; +import com.tashow.cloud.systemapi.api.permission.dto.DeptDataPermissionRespDTO; + +import java.util.Collection; +import java.util.Set; + +import static java.util.Collections.singleton; + +/** + * 权限 Service 接口 + *

+ * 提供用户-角色、角色-菜单、角色-部门的关联权限处理 + * + * @author 芋道源码 + */ +public interface PermissionService { + + /** + * 判断是否有权限,任一一个即可 + * + * @param userId 用户编号 + * @param permissions 权限 + * @return 是否 + */ + boolean hasAnyPermissions(Long userId, String... permissions); + + /** + * 判断是否有角色,任一一个即可 + * + * @param roles 角色数组 + * @return 是否 + */ + boolean hasAnyRoles(Long userId, String... roles); + + // ========== 角色-菜单的相关方法 ========== + + /** + * 设置角色菜单 + * + * @param roleId 角色编号 + * @param menuIds 菜单编号集合 + */ + void assignRoleMenu(Long roleId, Set menuIds); + + /** + * 处理角色删除时,删除关联授权数据 + * + * @param roleId 角色编号 + */ + void processRoleDeleted(Long roleId); + + /** + * 处理菜单删除时,删除关联授权数据 + * + * @param menuId 菜单编号 + */ + void processMenuDeleted(Long menuId); + + /** + * 获得角色拥有的菜单编号集合 + * + * @param roleId 角色编号 + * @return 菜单编号集合 + */ + default Set getRoleMenuListByRoleId(Long roleId) { + return getRoleMenuListByRoleId(singleton(roleId)); + } + + /** + * 获得角色们拥有的菜单编号集合 + * + * @param roleIds 角色编号数组 + * @return 菜单编号集合 + */ + Set getRoleMenuListByRoleId(Collection roleIds); + + /** + * 获得拥有指定菜单的角色编号数组,从缓存中获取 + * + * @param menuId 菜单编号 + * @return 角色编号数组 + */ + Set getMenuRoleIdListByMenuIdFromCache(Long menuId); + + // ========== 用户-角色的相关方法 ========== + + /** + * 设置用户角色 + * + * @param userId 角色编号 + * @param roleIds 角色编号集合 + */ + void assignUserRole(Long userId, Set roleIds); + + /** + * 处理用户删除时,删除关联授权数据 + * + * @param userId 用户编号 + */ + void processUserDeleted(Long userId); + + /** + * 获得拥有多个角色的用户编号集合 + * + * @param roleIds 角色编号集合 + * @return 用户编号集合 + */ + Set getUserRoleIdListByRoleId(Collection roleIds); + + /** + * 获得用户拥有的角色编号集合 + * + * @param userId 用户编号 + * @return 角色编号集合 + */ + Set getUserRoleIdListByUserId(Long userId); + + /** + * 获得用户拥有的角色编号集合,从缓存中获取 + * + * @param userId 用户编号 + * @return 角色编号集合 + */ + Set getUserRoleIdListByUserIdFromCache(Long userId); + + // ========== 用户-部门的相关方法 ========== + + /** + * 设置角色的数据权限 + * + * @param roleId 角色编号 + * @param dataScope 数据范围 + * @param dataScopeDeptIds 部门编号数组 + */ + void assignRoleDataScope(Long roleId, Integer dataScope, Set dataScopeDeptIds); + + /** + * 获得登陆用户的部门数据权限 + * + * @param userId 用户编号 + * @return 部门数据权限 + */ + DeptDataPermissionRespDTO getDeptDataPermission(Long userId); + +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/service/permission/PermissionServiceImpl.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/service/permission/PermissionServiceImpl.java new file mode 100644 index 0000000..6a48c1d --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/service/permission/PermissionServiceImpl.java @@ -0,0 +1,342 @@ +package com.tashow.cloud.system.service.permission; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.collection.CollectionUtil; +import cn.hutool.core.util.ArrayUtil; +import cn.hutool.extra.spring.SpringUtil; +import com.tashow.cloud.common.enums.CommonStatusEnum; +import com.tashow.cloud.common.util.collection.CollectionUtils; +import com.tashow.cloud.permission.core.annotation.DataPermission; +import com.tashow.cloud.systemapi.api.permission.dto.DeptDataPermissionRespDTO; +import com.tashow.cloud.system.dal.dataobject.permission.MenuDO; +import com.tashow.cloud.system.dal.dataobject.permission.RoleDO; +import com.tashow.cloud.system.dal.dataobject.permission.RoleMenuDO; +import com.tashow.cloud.system.dal.dataobject.permission.UserRoleDO; +import com.tashow.cloud.system.dal.mysql.permission.RoleMenuMapper; +import com.tashow.cloud.system.dal.mysql.permission.UserRoleMapper; +import com.tashow.cloud.system.dal.redis.RedisKeyConstants; +import com.tashow.cloud.systemapi.enums.permission.DataScopeEnum; +import com.tashow.cloud.system.service.dept.DeptService; +import com.tashow.cloud.system.service.user.AdminUserService; +import com.baomidou.dynamic.datasource.annotation.DSTransactional; +import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.Suppliers; +import com.google.common.collect.Sets; +import lombok.extern.slf4j.Slf4j; +import org.springframework.cache.annotation.CacheEvict; +import org.springframework.cache.annotation.Cacheable; +import org.springframework.cache.annotation.Caching; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import jakarta.annotation.Resource; +import java.util.*; +import java.util.function.Supplier; + +import static com.tashow.cloud.common.util.collection.CollectionUtils.convertSet; +import static com.tashow.cloud.common.util.json.JsonUtils.toJsonString; + + +/** + * 权限 Service 实现类 + * + * @author 芋道源码 + */ +@Service +@Slf4j +public class PermissionServiceImpl implements PermissionService { + + @Resource + private RoleMenuMapper roleMenuMapper; + @Resource + private UserRoleMapper userRoleMapper; + + @Resource + private RoleService roleService; + @Resource + private MenuService menuService; + @Resource + private DeptService deptService; + @Resource + private AdminUserService userService; + + @Override + public boolean hasAnyPermissions(Long userId, String... permissions) { + // 如果为空,说明已经有权限 + if (ArrayUtil.isEmpty(permissions)) { + return true; + } + + // 获得当前登录的角色。如果为空,说明没有权限 + List roles = getEnableUserRoleListByUserIdFromCache(userId); + if (CollUtil.isEmpty(roles)) { + return false; + } + + // 情况一:遍历判断每个权限,如果有一满足,说明有权限 + for (String permission : permissions) { + if (hasAnyPermission(roles, permission)) { + return true; + } + } + + // 情况二:如果是超管,也说明有权限 + return roleService.hasAnySuperAdmin(convertSet(roles, RoleDO::getId)); + } + + /** + * 判断指定角色,是否拥有该 permission 权限 + * + * @param roles 指定角色数组 + * @param permission 权限标识 + * @return 是否拥有 + */ + private boolean hasAnyPermission(List roles, String permission) { + List menuIds = menuService.getMenuIdListByPermissionFromCache(permission); + // 采用严格模式,如果权限找不到对应的 Menu 的话,也认为没有权限 + if (CollUtil.isEmpty(menuIds)) { + return false; + } + + // 判断是否有权限 + Set roleIds = convertSet(roles, RoleDO::getId); + for (Long menuId : menuIds) { + // 获得拥有该菜单的角色编号集合 + Set menuRoleIds = getSelf().getMenuRoleIdListByMenuIdFromCache(menuId); + // 如果有交集,说明有权限 + if (CollUtil.containsAny(menuRoleIds, roleIds)) { + return true; + } + } + return false; + } + + @Override + public boolean hasAnyRoles(Long userId, String... roles) { + // 如果为空,说明已经有权限 + if (ArrayUtil.isEmpty(roles)) { + return true; + } + + // 获得当前登录的角色。如果为空,说明没有权限 + List roleList = getEnableUserRoleListByUserIdFromCache(userId); + if (CollUtil.isEmpty(roleList)) { + return false; + } + + // 判断是否有角色 + Set userRoles = convertSet(roleList, RoleDO::getCode); + return CollUtil.containsAny(userRoles, Sets.newHashSet(roles)); + } + + // ========== 角色-菜单的相关方法 ========== + + @Override + @DSTransactional // 多数据源,使用 @DSTransactional 保证本地事务,以及数据源的切换 + @Caching(evict = { + @CacheEvict(value = RedisKeyConstants.MENU_ROLE_ID_LIST, + allEntries = true), + @CacheEvict(value = RedisKeyConstants.PERMISSION_MENU_ID_LIST, + allEntries = true) // allEntries 清空所有缓存,主要一次更新涉及到的 menuIds 较多,反倒批量会更快 + }) + public void assignRoleMenu(Long roleId, Set menuIds) { + // 获得角色拥有菜单编号 + Set dbMenuIds = convertSet(roleMenuMapper.selectListByRoleId(roleId), RoleMenuDO::getMenuId); + // 计算新增和删除的菜单编号 + Set menuIdList = CollUtil.emptyIfNull(menuIds); + Collection createMenuIds = CollUtil.subtract(menuIdList, dbMenuIds); + Collection deleteMenuIds = CollUtil.subtract(dbMenuIds, menuIdList); + // 执行新增和删除。对于已经授权的菜单,不用做任何处理 + if (CollUtil.isNotEmpty(createMenuIds)) { + roleMenuMapper.insertBatch(CollectionUtils.convertList(createMenuIds, menuId -> { + RoleMenuDO entity = new RoleMenuDO(); + entity.setRoleId(roleId); + entity.setMenuId(menuId); + return entity; + })); + } + if (CollUtil.isNotEmpty(deleteMenuIds)) { + roleMenuMapper.deleteListByRoleIdAndMenuIds(roleId, deleteMenuIds); + } + } + + @Override + @Transactional(rollbackFor = Exception.class) + @Caching(evict = { + @CacheEvict(value = RedisKeyConstants.MENU_ROLE_ID_LIST, + allEntries = true), // allEntries 清空所有缓存,此处无法方便获得 roleId 对应的 menu 缓存们 + @CacheEvict(value = RedisKeyConstants.USER_ROLE_ID_LIST, + allEntries = true) // allEntries 清空所有缓存,此处无法方便获得 roleId 对应的 user 缓存们 + }) + public void processRoleDeleted(Long roleId) { + // 标记删除 UserRole + userRoleMapper.deleteListByRoleId(roleId); + // 标记删除 RoleMenu + roleMenuMapper.deleteListByRoleId(roleId); + } + + @Override + @CacheEvict(value = RedisKeyConstants.MENU_ROLE_ID_LIST, key = "#menuId") + public void processMenuDeleted(Long menuId) { + roleMenuMapper.deleteListByMenuId(menuId); + } + + @Override + public Set getRoleMenuListByRoleId(Collection roleIds) { + if (CollUtil.isEmpty(roleIds)) { + return Collections.emptySet(); + } + + // 如果是管理员的情况下,获取全部菜单编号 + if (roleService.hasAnySuperAdmin(roleIds)) { + return convertSet(menuService.getMenuList(), MenuDO::getId); + } + // 如果是非管理员的情况下,获得拥有的菜单编号 + return convertSet(roleMenuMapper.selectListByRoleId(roleIds), RoleMenuDO::getMenuId); + } + + @Override + @Cacheable(value = RedisKeyConstants.MENU_ROLE_ID_LIST, key = "#menuId") + public Set getMenuRoleIdListByMenuIdFromCache(Long menuId) { + return convertSet(roleMenuMapper.selectListByMenuId(menuId), RoleMenuDO::getRoleId); + } + + // ========== 用户-角色的相关方法 ========== + + @Override + @DSTransactional // 多数据源,使用 @DSTransactional 保证本地事务,以及数据源的切换 + @CacheEvict(value = RedisKeyConstants.USER_ROLE_ID_LIST, key = "#userId") + public void assignUserRole(Long userId, Set roleIds) { + // 获得角色拥有角色编号 + Set dbRoleIds = convertSet(userRoleMapper.selectListByUserId(userId), + UserRoleDO::getRoleId); + // 计算新增和删除的角色编号 + Set roleIdList = CollUtil.emptyIfNull(roleIds); + Collection createRoleIds = CollUtil.subtract(roleIdList, dbRoleIds); + Collection deleteMenuIds = CollUtil.subtract(dbRoleIds, roleIdList); + // 执行新增和删除。对于已经授权的角色,不用做任何处理 + if (!CollectionUtil.isEmpty(createRoleIds)) { + userRoleMapper.insertBatch(CollectionUtils.convertList(createRoleIds, roleId -> { + UserRoleDO entity = new UserRoleDO(); + entity.setUserId(userId); + entity.setRoleId(roleId); + return entity; + })); + } + if (!CollectionUtil.isEmpty(deleteMenuIds)) { + userRoleMapper.deleteListByUserIdAndRoleIdIds(userId, deleteMenuIds); + } + } + + @Override + @CacheEvict(value = RedisKeyConstants.USER_ROLE_ID_LIST, key = "#userId") + public void processUserDeleted(Long userId) { + userRoleMapper.deleteListByUserId(userId); + } + + @Override + public Set getUserRoleIdListByUserId(Long userId) { + return convertSet(userRoleMapper.selectListByUserId(userId), UserRoleDO::getRoleId); + } + + @Override + @Cacheable(value = RedisKeyConstants.USER_ROLE_ID_LIST, key = "#userId") + public Set getUserRoleIdListByUserIdFromCache(Long userId) { + return getUserRoleIdListByUserId(userId); + } + + @Override + public Set getUserRoleIdListByRoleId(Collection roleIds) { + return convertSet(userRoleMapper.selectListByRoleIds(roleIds), UserRoleDO::getUserId); + } + + /** + * 获得用户拥有的角色,并且这些角色是开启状态的 + * + * @param userId 用户编号 + * @return 用户拥有的角色 + */ + @VisibleForTesting + List getEnableUserRoleListByUserIdFromCache(Long userId) { + // 获得用户拥有的角色编号 + Set roleIds = getSelf().getUserRoleIdListByUserIdFromCache(userId); + // 获得角色数组,并移除被禁用的 + List roles = roleService.getRoleListFromCache(roleIds); + roles.removeIf(role -> !CommonStatusEnum.ENABLE.getStatus().equals(role.getStatus())); + return roles; + } + + // ========== 用户-部门的相关方法 ========== + + @Override + public void assignRoleDataScope(Long roleId, Integer dataScope, Set dataScopeDeptIds) { + roleService.updateRoleDataScope(roleId, dataScope, dataScopeDeptIds); + } + + @Override + @DataPermission(enable = false) // 关闭数据权限,不然就会出现递归获取数据权限的问题 + public DeptDataPermissionRespDTO getDeptDataPermission(Long userId) { + // 获得用户的角色 + List roles = getEnableUserRoleListByUserIdFromCache(userId); + + // 如果角色为空,则只能查看自己 + DeptDataPermissionRespDTO result = new DeptDataPermissionRespDTO(); + if (CollUtil.isEmpty(roles)) { + result.setSelf(true); + return result; + } + + // 获得用户的部门编号的缓存,通过 Guava 的 Suppliers 惰性求值,即有且仅有第一次发起 DB 的查询 + Supplier userDeptId = Suppliers.memoize(() -> userService.getUser(userId).getDeptId()); + // 遍历每个角色,计算 + for (RoleDO role : roles) { + // 为空时,跳过 + if (role.getDataScope() == null) { + continue; + } + // 情况一,ALL + if (Objects.equals(role.getDataScope(), DataScopeEnum.ALL.getScope())) { + result.setAll(true); + continue; + } + // 情况二,DEPT_CUSTOM + if (Objects.equals(role.getDataScope(), DataScopeEnum.DEPT_CUSTOM.getScope())) { + CollUtil.addAll(result.getDeptIds(), role.getDataScopeDeptIds()); + // 自定义可见部门时,保证可以看到自己所在的部门。否则,一些场景下可能会有问题。 + // 例如说,登录时,基于 t_user 的 username 查询会可能被 dept_id 过滤掉 + CollUtil.addAll(result.getDeptIds(), userDeptId.get()); + continue; + } + // 情况三,DEPT_ONLY + if (Objects.equals(role.getDataScope(), DataScopeEnum.DEPT_ONLY.getScope())) { + CollectionUtils.addIfNotNull(result.getDeptIds(), userDeptId.get()); + continue; + } + // 情况四,DEPT_DEPT_AND_CHILD + if (Objects.equals(role.getDataScope(), DataScopeEnum.DEPT_AND_CHILD.getScope())) { + CollUtil.addAll(result.getDeptIds(), deptService.getChildDeptIdListFromCache(userDeptId.get())); + // 添加本身部门编号 + CollUtil.addAll(result.getDeptIds(), userDeptId.get()); + continue; + } + // 情况五,SELF + if (Objects.equals(role.getDataScope(), DataScopeEnum.SELF.getScope())) { + result.setSelf(true); + continue; + } + // 未知情况,error log 即可 + log.error("[getDeptDataPermission][LoginUser({}) role({}) 无法处理]", userId, toJsonString(result)); + } + return result; + } + + /** + * 获得自身的代理对象,解决 AOP 生效问题 + * + * @return 自己 + */ + private PermissionServiceImpl getSelf() { + return SpringUtil.getBean(getClass()); + } + +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/service/permission/RoleService.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/service/permission/RoleService.java new file mode 100644 index 0000000..fa100dc --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/service/permission/RoleService.java @@ -0,0 +1,126 @@ +package com.tashow.cloud.system.service.permission; + +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.system.controller.admin.permission.vo.role.RolePageReqVO; +import com.tashow.cloud.system.controller.admin.permission.vo.role.RoleSaveReqVO; +import com.tashow.cloud.system.dal.dataobject.permission.RoleDO; + +import com.tashow.cloud.system.controller.admin.permission.vo.role.RolePageReqVO; +import com.tashow.cloud.system.controller.admin.permission.vo.role.RoleSaveReqVO; +import jakarta.validation.Valid; +import java.util.Collection; +import java.util.List; +import java.util.Set; + +/** + * 角色 Service 接口 + * + * @author 芋道源码 + */ +public interface RoleService { + + /** + * 创建角色 + * + * @param createReqVO 创建角色信息 + * @param type 角色类型 + * @return 角色编号 + */ + Long createRole(@Valid RoleSaveReqVO createReqVO, Integer type); + + /** + * 更新角色 + * + * @param updateReqVO 更新角色信息 + */ + void updateRole(@Valid RoleSaveReqVO updateReqVO); + + /** + * 删除角色 + * + * @param id 角色编号 + */ + void deleteRole(Long id); + + /** + * 设置角色的数据权限 + * + * @param id 角色编号 + * @param dataScope 数据范围 + * @param dataScopeDeptIds 部门编号数组 + */ + void updateRoleDataScope(Long id, Integer dataScope, Set dataScopeDeptIds); + + /** + * 获得角色 + * + * @param id 角色编号 + * @return 角色 + */ + RoleDO getRole(Long id); + + /** + * 获得角色,从缓存中 + * + * @param id 角色编号 + * @return 角色 + */ + RoleDO getRoleFromCache(Long id); + + /** + * 获得角色列表 + * + * @param ids 角色编号数组 + * @return 角色列表 + */ + List getRoleList(Collection ids); + + /** + * 获得角色数组,从缓存中 + * + * @param ids 角色编号数组 + * @return 角色数组 + */ + List getRoleListFromCache(Collection ids); + + /** + * 获得角色列表 + * + * @param statuses 筛选的状态 + * @return 角色列表 + */ + List getRoleListByStatus(Collection statuses); + + /** + * 获得所有角色列表 + * + * @return 角色列表 + */ + List getRoleList(); + + /** + * 获得角色分页 + * + * @param reqVO 角色分页查询 + * @return 角色分页结果 + */ + PageResult getRolePage(RolePageReqVO reqVO); + + /** + * 判断角色编号数组中,是否有管理员 + * + * @param ids 角色编号数组 + * @return 是否有管理员 + */ + boolean hasAnySuperAdmin(Collection ids); + + /** + * 校验角色们是否有效。如下情况,视为无效: + * 1. 角色编号不存在 + * 2. 角色被禁用 + * + * @param ids 角色编号数组 + */ + void validateRoleList(Collection ids); + +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/service/permission/RoleServiceImpl.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/service/permission/RoleServiceImpl.java new file mode 100644 index 0000000..f2f220f --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/service/permission/RoleServiceImpl.java @@ -0,0 +1,262 @@ +package com.tashow.cloud.system.service.permission; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.collection.CollectionUtil; +import cn.hutool.core.util.ObjUtil; +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.extra.spring.SpringUtil; +import com.tashow.cloud.common.enums.CommonStatusEnum; +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.common.util.collection.CollectionUtils; +import com.tashow.cloud.common.util.object.BeanUtils; +import com.tashow.cloud.system.controller.admin.permission.vo.role.RolePageReqVO; +import com.tashow.cloud.system.controller.admin.permission.vo.role.RoleSaveReqVO; +import com.tashow.cloud.system.dal.dataobject.permission.RoleDO; +import com.tashow.cloud.system.dal.mysql.permission.RoleMapper; +import com.tashow.cloud.system.dal.redis.RedisKeyConstants; +import com.tashow.cloud.systemapi.enums.permission.DataScopeEnum; +import com.tashow.cloud.systemapi.enums.permission.RoleCodeEnum; +import com.tashow.cloud.systemapi.enums.permission.RoleTypeEnum; +import com.google.common.annotations.VisibleForTesting; +import com.mzt.logapi.context.LogRecordContext; +import com.mzt.logapi.service.impl.DiffParseFunction; +import com.mzt.logapi.starter.annotation.LogRecord; +import com.tashow.cloud.systemapi.enums.ErrorCodeConstants; +import com.tashow.cloud.systemapi.enums.LogRecordConstants; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import org.springframework.cache.annotation.CacheEvict; +import org.springframework.cache.annotation.Cacheable; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.util.StringUtils; + +import java.util.*; + +import static com.tashow.cloud.common.exception.util.ServiceExceptionUtil.exception; +import static com.tashow.cloud.common.util.collection.CollectionUtils.convertMap; + +/** + * 角色 Service 实现类 + * + * @author 芋道源码 + */ +@Service +@Slf4j +public class RoleServiceImpl implements RoleService { + + @Resource + private PermissionService permissionService; + + @Resource + private RoleMapper roleMapper; + + @Override + @Transactional(rollbackFor = Exception.class) + @LogRecord(type = LogRecordConstants.SYSTEM_ROLE_TYPE, subType = LogRecordConstants.SYSTEM_ROLE_CREATE_SUB_TYPE, bizNo = "{{#role.id}}", + success = LogRecordConstants.SYSTEM_ROLE_CREATE_SUCCESS) + public Long createRole(RoleSaveReqVO createReqVO, Integer type) { + // 1. 校验角色 + validateRoleDuplicate(createReqVO.getName(), createReqVO.getCode(), null); + + // 2. 插入到数据库 + RoleDO role = BeanUtils.toBean(createReqVO, RoleDO.class) + .setType(ObjectUtil.defaultIfNull(type, RoleTypeEnum.CUSTOM.getType())) + .setStatus(ObjUtil.defaultIfNull(createReqVO.getStatus(), CommonStatusEnum.ENABLE.getStatus())) + .setDataScope(DataScopeEnum.ALL.getScope()); // 默认可查看所有数据。原因是,可能一些项目不需要项目权限 + roleMapper.insert(role); + + // 3. 记录操作日志上下文 + LogRecordContext.putVariable("role", role); + return role.getId(); + } + + @Override + @CacheEvict(value = RedisKeyConstants.ROLE, key = "#updateReqVO.id") + @LogRecord(type = LogRecordConstants.SYSTEM_ROLE_TYPE, subType = LogRecordConstants.SYSTEM_ROLE_UPDATE_SUB_TYPE, bizNo = "{{#updateReqVO.id}}", + success = LogRecordConstants.SYSTEM_ROLE_UPDATE_SUCCESS) + public void updateRole(RoleSaveReqVO updateReqVO) { + // 1.1 校验是否可以更新 + RoleDO role = validateRoleForUpdate(updateReqVO.getId()); + // 1.2 校验角色的唯一字段是否重复 + validateRoleDuplicate(updateReqVO.getName(), updateReqVO.getCode(), updateReqVO.getId()); + + // 2. 更新到数据库 + RoleDO updateObj = BeanUtils.toBean(updateReqVO, RoleDO.class); + roleMapper.updateById(updateObj); + + // 3. 记录操作日志上下文 + LogRecordContext.putVariable(DiffParseFunction.OLD_OBJECT, BeanUtils.toBean(role, RoleSaveReqVO.class)); + LogRecordContext.putVariable("role", role); + } + + @Override + @CacheEvict(value = RedisKeyConstants.ROLE, key = "#id") + public void updateRoleDataScope(Long id, Integer dataScope, Set dataScopeDeptIds) { + // 校验是否可以更新 + validateRoleForUpdate(id); + + // 更新数据范围 + RoleDO updateObject = new RoleDO(); + updateObject.setId(id); + updateObject.setDataScope(dataScope); + updateObject.setDataScopeDeptIds(dataScopeDeptIds); + roleMapper.updateById(updateObject); + } + + @Override + @Transactional(rollbackFor = Exception.class) + @CacheEvict(value = RedisKeyConstants.ROLE, key = "#id") + @LogRecord(type = LogRecordConstants.SYSTEM_ROLE_TYPE, subType = LogRecordConstants.SYSTEM_ROLE_DELETE_SUB_TYPE, bizNo = "{{#id}}", + success = LogRecordConstants.SYSTEM_ROLE_DELETE_SUCCESS) + public void deleteRole(Long id) { + // 1. 校验是否可以更新 + RoleDO role = validateRoleForUpdate(id); + + // 2.1 标记删除 + roleMapper.deleteById(id); + // 2.2 删除相关数据 + permissionService.processRoleDeleted(id); + + // 3. 记录操作日志上下文 + LogRecordContext.putVariable("role", role); + } + + /** + * 校验角色的唯一字段是否重复 + * + * 1. 是否存在相同名字的角色 + * 2. 是否存在相同编码的角色 + * + * @param name 角色名字 + * @param code 角色额编码 + * @param id 角色编号 + */ + @VisibleForTesting + void validateRoleDuplicate(String name, String code, Long id) { + // 0. 超级管理员,不允许创建 + if (RoleCodeEnum.isSuperAdmin(code)) { + throw exception(ErrorCodeConstants.ROLE_ADMIN_CODE_ERROR, code); + } + // 1. 该 name 名字被其它角色所使用 + RoleDO role = roleMapper.selectByName(name); + if (role != null && !role.getId().equals(id)) { + throw exception(ErrorCodeConstants.ROLE_NAME_DUPLICATE, name); + } + // 2. 是否存在相同编码的角色 + if (!StringUtils.hasText(code)) { + return; + } + // 该 code 编码被其它角色所使用 + role = roleMapper.selectByCode(code); + if (role != null && !role.getId().equals(id)) { + throw exception(ErrorCodeConstants.ROLE_CODE_DUPLICATE, code); + } + } + + /** + * 校验角色是否可以被更新 + * + * @param id 角色编号 + */ + @VisibleForTesting + RoleDO validateRoleForUpdate(Long id) { + RoleDO role = roleMapper.selectById(id); + if (role == null) { + throw exception(ErrorCodeConstants.ROLE_NOT_EXISTS); + } + // 内置角色,不允许删除 + if (RoleTypeEnum.SYSTEM.getType().equals(role.getType())) { + throw exception(ErrorCodeConstants.ROLE_CAN_NOT_UPDATE_SYSTEM_TYPE_ROLE); + } + return role; + } + + @Override + public RoleDO getRole(Long id) { + return roleMapper.selectById(id); + } + + @Override + @Cacheable(value = RedisKeyConstants.ROLE, key = "#id", + unless = "#result == null") + public RoleDO getRoleFromCache(Long id) { + return roleMapper.selectById(id); + } + + + @Override + public List getRoleListByStatus(Collection statuses) { + return roleMapper.selectListByStatus(statuses); + } + + @Override + public List getRoleList() { + return roleMapper.selectList(); + } + + @Override + public List getRoleList(Collection ids) { + if (CollectionUtil.isEmpty(ids)) { + return Collections.emptyList(); + } + return roleMapper.selectBatchIds(ids); + } + + @Override + public List getRoleListFromCache(Collection ids) { + if (CollectionUtil.isEmpty(ids)) { + return Collections.emptyList(); + } + // 这里采用 for 循环从缓存中获取,主要考虑 Spring CacheManager 无法批量操作的问题 + RoleServiceImpl self = getSelf(); + return CollectionUtils.convertList(ids, self::getRoleFromCache); + } + + @Override + public PageResult getRolePage(RolePageReqVO reqVO) { + return roleMapper.selectPage(reqVO); + } + + @Override + public boolean hasAnySuperAdmin(Collection ids) { + if (CollectionUtil.isEmpty(ids)) { + return false; + } + RoleServiceImpl self = getSelf(); + return ids.stream().anyMatch(id -> { + RoleDO role = self.getRoleFromCache(id); + return role != null && RoleCodeEnum.isSuperAdmin(role.getCode()); + }); + } + + @Override + public void validateRoleList(Collection ids) { + if (CollUtil.isEmpty(ids)) { + return; + } + // 获得角色信息 + List roles = roleMapper.selectBatchIds(ids); + Map roleMap = convertMap(roles, RoleDO::getId); + // 校验 + ids.forEach(id -> { + RoleDO role = roleMap.get(id); + if (role == null) { + throw exception(ErrorCodeConstants.ROLE_NOT_EXISTS); + } + if (!CommonStatusEnum.ENABLE.getStatus().equals(role.getStatus())) { + throw exception(ErrorCodeConstants.ROLE_IS_DISABLE, role.getName()); + } + }); + } + + /** + * 获得自身的代理对象,解决 AOP 生效问题 + * + * @return 自己 + */ + private RoleServiceImpl getSelf() { + return SpringUtil.getBean(getClass()); + } + +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/service/sms/SmsChannelService.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/service/sms/SmsChannelService.java new file mode 100644 index 0000000..caaf80f --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/service/sms/SmsChannelService.java @@ -0,0 +1,84 @@ +package com.tashow.cloud.system.service.sms; + +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.system.framework.sms.core.client.SmsClient; +import com.tashow.cloud.system.controller.admin.sms.vo.channel.SmsChannelPageReqVO; +import com.tashow.cloud.system.controller.admin.sms.vo.channel.SmsChannelSaveReqVO; +import com.tashow.cloud.system.dal.dataobject.sms.SmsChannelDO; + +import com.tashow.cloud.system.controller.admin.sms.vo.channel.SmsChannelPageReqVO; +import com.tashow.cloud.system.controller.admin.sms.vo.channel.SmsChannelSaveReqVO; +import com.tashow.cloud.system.framework.sms.core.client.SmsClient; +import jakarta.validation.Valid; +import java.util.List; + +/** + * 短信渠道 Service 接口 + * + * @author zzf + * @since 2021/1/25 9:24 + */ +public interface SmsChannelService { + + /** + * 创建短信渠道 + * + * @param createReqVO 创建信息 + * @return 编号 + */ + Long createSmsChannel(@Valid SmsChannelSaveReqVO createReqVO); + + /** + * 更新短信渠道 + * + * @param updateReqVO 更新信息 + */ + void updateSmsChannel(@Valid SmsChannelSaveReqVO updateReqVO); + + /** + * 删除短信渠道 + * + * @param id 编号 + */ + void deleteSmsChannel(Long id); + + /** + * 获得短信渠道 + * + * @param id 编号 + * @return 短信渠道 + */ + SmsChannelDO getSmsChannel(Long id); + + /** + * 获得所有短信渠道列表 + * + * @return 短信渠道列表 + */ + List getSmsChannelList(); + + /** + * 获得短信渠道分页 + * + * @param pageReqVO 分页查询 + * @return 短信渠道分页 + */ + PageResult getSmsChannelPage(SmsChannelPageReqVO pageReqVO); + + /** + * 获得短信客户端 + * + * @param id 编号 + * @return 短信客户端 + */ + SmsClient getSmsClient(Long id); + + /** + * 获得短信客户端 + * + * @param code 编码 + * @return 短信客户端 + */ + SmsClient getSmsClient(String code); + +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/service/sms/SmsChannelServiceImpl.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/service/sms/SmsChannelServiceImpl.java new file mode 100644 index 0000000..45c782a --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/service/sms/SmsChannelServiceImpl.java @@ -0,0 +1,109 @@ +package com.tashow.cloud.system.service.sms; + +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.common.util.object.BeanUtils; +import com.tashow.cloud.system.controller.admin.sms.vo.channel.SmsChannelPageReqVO; +import com.tashow.cloud.system.controller.admin.sms.vo.channel.SmsChannelSaveReqVO; +import com.tashow.cloud.system.dal.dataobject.sms.SmsChannelDO; +import com.tashow.cloud.system.dal.mysql.sms.SmsChannelMapper; +import com.tashow.cloud.system.framework.sms.core.client.SmsClient; +import com.tashow.cloud.system.framework.sms.core.client.SmsClientFactory; +import com.tashow.cloud.system.framework.sms.core.property.SmsChannelProperties; +import com.tashow.cloud.system.controller.admin.sms.vo.channel.SmsChannelPageReqVO; +import com.tashow.cloud.system.controller.admin.sms.vo.channel.SmsChannelSaveReqVO; +import com.tashow.cloud.systemapi.enums.ErrorCodeConstants; +import com.tashow.cloud.system.framework.sms.core.client.SmsClient; +import com.tashow.cloud.system.framework.sms.core.client.SmsClientFactory; +import com.tashow.cloud.system.framework.sms.core.property.SmsChannelProperties; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +import java.util.List; + +import static com.tashow.cloud.common.exception.util.ServiceExceptionUtil.exception; +import static com.tashow.cloud.systemapi.enums.ErrorCodeConstants.SMS_CHANNEL_HAS_CHILDREN; +import static com.tashow.cloud.systemapi.enums.ErrorCodeConstants.SMS_CHANNEL_NOT_EXISTS; + +/** + * 短信渠道 Service 实现类 + * + * @author zzf + */ +@Service +@Slf4j +public class SmsChannelServiceImpl implements SmsChannelService { + + @Resource + private SmsClientFactory smsClientFactory; + + @Resource + private SmsChannelMapper smsChannelMapper; + + @Resource + private SmsTemplateService smsTemplateService; + + @Override + public Long createSmsChannel(SmsChannelSaveReqVO createReqVO) { + SmsChannelDO channel = BeanUtils.toBean(createReqVO, SmsChannelDO.class); + smsChannelMapper.insert(channel); + return channel.getId(); + } + + @Override + public void updateSmsChannel(SmsChannelSaveReqVO updateReqVO) { + // 校验存在 + validateSmsChannelExists(updateReqVO.getId()); + // 更新 + SmsChannelDO updateObj = BeanUtils.toBean(updateReqVO, SmsChannelDO.class); + smsChannelMapper.updateById(updateObj); + } + + @Override + public void deleteSmsChannel(Long id) { + // 校验存在 + validateSmsChannelExists(id); + // 校验是否有在使用该账号的模版 + if (smsTemplateService.getSmsTemplateCountByChannelId(id) > 0) { + throw exception(ErrorCodeConstants.SMS_CHANNEL_HAS_CHILDREN); + } + // 删除 + smsChannelMapper.deleteById(id); + } + + private SmsChannelDO validateSmsChannelExists(Long id) { + SmsChannelDO channel = smsChannelMapper.selectById(id); + if (channel == null) { + throw exception(ErrorCodeConstants.SMS_CHANNEL_NOT_EXISTS); + } + return channel; + } + + @Override + public SmsChannelDO getSmsChannel(Long id) { + return smsChannelMapper.selectById(id); + } + + @Override + public List getSmsChannelList() { + return smsChannelMapper.selectList(); + } + + @Override + public PageResult getSmsChannelPage(SmsChannelPageReqVO pageReqVO) { + return smsChannelMapper.selectPage(pageReqVO); + } + + @Override + public SmsClient getSmsClient(Long id) { + SmsChannelDO channel = smsChannelMapper.selectById(id); + SmsChannelProperties properties = BeanUtils.toBean(channel, SmsChannelProperties.class); + return smsClientFactory.createOrUpdateSmsClient(properties); + } + + @Override + public SmsClient getSmsClient(String code) { + return smsClientFactory.getSmsClient(code); + } + +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/service/sms/SmsCodeService.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/service/sms/SmsCodeService.java new file mode 100644 index 0000000..3c23fad --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/service/sms/SmsCodeService.java @@ -0,0 +1,43 @@ +package com.tashow.cloud.system.service.sms; + +import com.tashow.cloud.common.exception.ServiceException; +import com.tashow.cloud.systemapi.api.sms.dto.code.SmsCodeSendReqDTO; +import com.tashow.cloud.systemapi.api.sms.dto.code.SmsCodeUseReqDTO; +import com.tashow.cloud.systemapi.api.sms.dto.code.SmsCodeValidateReqDTO; + +import com.tashow.cloud.systemapi.api.sms.dto.code.SmsCodeSendReqDTO; +import com.tashow.cloud.systemapi.api.sms.dto.code.SmsCodeUseReqDTO; +import com.tashow.cloud.systemapi.api.sms.dto.code.SmsCodeValidateReqDTO; +import jakarta.validation.Valid; + +/** + * 短信验证码 Service 接口 + * + * @author 芋道源码 + */ +public interface SmsCodeService { + + /** + * 创建短信验证码,并进行发送 + * + * @param reqDTO 发送请求 + */ + void sendSmsCode(@Valid SmsCodeSendReqDTO reqDTO); + + /** + * 验证短信验证码,并进行使用 + * 如果正确,则将验证码标记成已使用 + * 如果错误,则抛出 {@link ServiceException} 异常 + * + * @param reqDTO 使用请求 + */ + void useSmsCode(@Valid SmsCodeUseReqDTO reqDTO); + + /** + * 检查验证码是否有效 + * + * @param reqDTO 校验请求 + */ + void validateSmsCode(@Valid SmsCodeValidateReqDTO reqDTO); + +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/service/sms/SmsCodeServiceImpl.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/service/sms/SmsCodeServiceImpl.java new file mode 100644 index 0000000..9026ff7 --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/service/sms/SmsCodeServiceImpl.java @@ -0,0 +1,117 @@ +package com.tashow.cloud.system.service.sms; + +import cn.hutool.core.date.LocalDateTimeUtil; +import cn.hutool.core.lang.Assert; +import cn.hutool.core.map.MapUtil; +import com.tashow.cloud.systemapi.api.sms.dto.code.SmsCodeSendReqDTO; +import com.tashow.cloud.systemapi.api.sms.dto.code.SmsCodeUseReqDTO; +import com.tashow.cloud.systemapi.api.sms.dto.code.SmsCodeValidateReqDTO; +import com.tashow.cloud.system.dal.dataobject.sms.SmsCodeDO; +import com.tashow.cloud.system.dal.mysql.sms.SmsCodeMapper; +import com.tashow.cloud.systemapi.enums.sms.SmsSceneEnum; +import com.tashow.cloud.system.framework.sms.config.SmsCodeProperties; +import com.tashow.cloud.systemapi.api.sms.dto.code.SmsCodeSendReqDTO; +import com.tashow.cloud.systemapi.api.sms.dto.code.SmsCodeUseReqDTO; +import com.tashow.cloud.systemapi.api.sms.dto.code.SmsCodeValidateReqDTO; +import com.tashow.cloud.systemapi.enums.ErrorCodeConstants; +import com.tashow.cloud.systemapi.enums.sms.SmsSceneEnum; +import com.tashow.cloud.system.framework.sms.config.SmsCodeProperties; +import org.springframework.stereotype.Service; +import org.springframework.validation.annotation.Validated; + +import jakarta.annotation.Resource; +import java.time.LocalDateTime; + +import static cn.hutool.core.util.RandomUtil.randomInt; +import static com.tashow.cloud.common.exception.util.ServiceExceptionUtil.exception; +import static com.tashow.cloud.common.util.date.DateUtils.isToday; + +/** + * 短信验证码 Service 实现类 + * + * @author 芋道源码 + */ +@Service +@Validated +public class SmsCodeServiceImpl implements SmsCodeService { + + @Resource + private SmsCodeProperties smsCodeProperties; + + @Resource + private SmsCodeMapper smsCodeMapper; + + @Resource + private SmsSendService smsSendService; + + @Override + public void sendSmsCode(SmsCodeSendReqDTO reqDTO) { + SmsSceneEnum sceneEnum = SmsSceneEnum.getCodeByScene(reqDTO.getScene()); + Assert.notNull(sceneEnum, "验证码场景({}) 查找不到配置", reqDTO.getScene()); + // 创建验证码 + String code = createSmsCode(reqDTO.getMobile(), reqDTO.getScene(), reqDTO.getCreateIp()); + // 发送验证码 + smsSendService.sendSingleSms(reqDTO.getMobile(), null, null, + sceneEnum.getTemplateCode(), MapUtil.of("code", code)); + } + + private String createSmsCode(String mobile, Integer scene, String ip) { + // 校验是否可以发送验证码,不用筛选场景 + SmsCodeDO lastSmsCode = smsCodeMapper.selectLastByMobile(mobile, null, null); + if (lastSmsCode != null) { + if (LocalDateTimeUtil.between(lastSmsCode.getCreateTime(), LocalDateTime.now()).toMillis() + < smsCodeProperties.getSendFrequency().toMillis()) { // 发送过于频繁 + throw exception(ErrorCodeConstants.SMS_CODE_SEND_TOO_FAST); + } + if (isToday(lastSmsCode.getCreateTime()) && // 必须是今天,才能计算超过当天的上限 + lastSmsCode.getTodayIndex() >= smsCodeProperties.getSendMaximumQuantityPerDay()) { // 超过当天发送的上限。 + throw exception(ErrorCodeConstants.SMS_CODE_EXCEED_SEND_MAXIMUM_QUANTITY_PER_DAY); + } + // TODO 芋艿:提升,每个 IP 每天可发送数量 + // TODO 芋艿:提升,每个 IP 每小时可发送数量 + } + + // 创建验证码记录 + String code = String.format("%0" + smsCodeProperties.getEndCode().toString().length() + "d", + randomInt(smsCodeProperties.getBeginCode(), smsCodeProperties.getEndCode() + 1)); + SmsCodeDO newSmsCode = SmsCodeDO.builder().mobile(mobile).code(code).scene(scene) + .todayIndex(lastSmsCode != null && isToday(lastSmsCode.getCreateTime()) ? lastSmsCode.getTodayIndex() + 1 : 1) + .createIp(ip).used(false).build(); + smsCodeMapper.insert(newSmsCode); + return code; + } + + @Override + public void useSmsCode(SmsCodeUseReqDTO reqDTO) { + // 检测验证码是否有效 + SmsCodeDO lastSmsCode = validateSmsCode0(reqDTO.getMobile(), reqDTO.getCode(), reqDTO.getScene()); + // 使用验证码 + smsCodeMapper.updateById(SmsCodeDO.builder().id(lastSmsCode.getId()) + .used(true).usedTime(LocalDateTime.now()).usedIp(reqDTO.getUsedIp()).build()); + } + + @Override + public void validateSmsCode(SmsCodeValidateReqDTO reqDTO) { + validateSmsCode0(reqDTO.getMobile(), reqDTO.getCode(), reqDTO.getScene()); + } + + private SmsCodeDO validateSmsCode0(String mobile, String code, Integer scene) { + // 校验验证码 + SmsCodeDO lastSmsCode = smsCodeMapper.selectLastByMobile(mobile, code, scene); + // 若验证码不存在,抛出异常 + if (lastSmsCode == null) { + throw exception(ErrorCodeConstants.SMS_CODE_NOT_FOUND); + } + // 超过时间 + if (LocalDateTimeUtil.between(lastSmsCode.getCreateTime(), LocalDateTime.now()).toMillis() + >= smsCodeProperties.getExpireTimes().toMillis()) { // 验证码已过期 + throw exception(ErrorCodeConstants.SMS_CODE_EXPIRED); + } + // 判断验证码是否已被使用 + if (Boolean.TRUE.equals(lastSmsCode.getUsed())) { + throw exception(ErrorCodeConstants.SMS_CODE_USED); + } + return lastSmsCode; + } + +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/service/sms/SmsLogService.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/service/sms/SmsLogService.java new file mode 100644 index 0000000..5b39dae --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/service/sms/SmsLogService.java @@ -0,0 +1,69 @@ +package com.tashow.cloud.system.service.sms; + +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.system.controller.admin.sms.vo.log.SmsLogPageReqVO; +import com.tashow.cloud.system.dal.dataobject.sms.SmsLogDO; +import com.tashow.cloud.system.dal.dataobject.sms.SmsTemplateDO; +import com.tashow.cloud.system.controller.admin.sms.vo.log.SmsLogPageReqVO; + +import java.time.LocalDateTime; +import java.util.Map; + +/** + * 短信日志 Service 接口 + * + * @author zzf + * @date 13:48 2021/3/2 + */ +public interface SmsLogService { + + /** + * 创建短信日志 + * + * @param mobile 手机号 + * @param userId 用户编号 + * @param userType 用户类型 + * @param isSend 是否发送 + * @param template 短信模板 + * @param templateContent 短信内容 + * @param templateParams 短信参数 + * @return 发送日志编号 + */ + Long createSmsLog(String mobile, Long userId, Integer userType, Boolean isSend, + SmsTemplateDO template, String templateContent, Map templateParams); + + /** + * 更新日志的发送结果 + * + * @param id 日志编号 + * @param success 发送是否成功 + * @param apiSendCode 短信 API 发送结果的编码 + * @param apiSendMsg 短信 API 发送失败的提示 + * @param apiRequestId 短信 API 发送返回的唯一请求 ID + * @param apiSerialNo 短信 API 发送返回的序号 + */ + void updateSmsSendResult(Long id, Boolean success, + String apiSendCode, String apiSendMsg, + String apiRequestId, String apiSerialNo); + + /** + * 更新日志的接收结果 + * + * @param id 日志编号 + * @param success 是否接收成功 + * @param receiveTime 用户接收时间 + * @param apiReceiveCode API 接收结果的编码 + * @param apiReceiveMsg API 接收结果的说明 + */ + void updateSmsReceiveResult(Long id, Boolean success, + LocalDateTime receiveTime, String apiReceiveCode, String apiReceiveMsg); + + /** + * 获得短信日志分页 + * + * @param pageReqVO 分页查询 + * @return 短信日志分页 + */ + PageResult getSmsLogPage(SmsLogPageReqVO pageReqVO); + +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/service/sms/SmsLogServiceImpl.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/service/sms/SmsLogServiceImpl.java new file mode 100644 index 0000000..6ddff26 --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/service/sms/SmsLogServiceImpl.java @@ -0,0 +1,82 @@ +package com.tashow.cloud.system.service.sms; + +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.system.controller.admin.sms.vo.log.SmsLogPageReqVO; +import com.tashow.cloud.system.dal.dataobject.sms.SmsLogDO; +import com.tashow.cloud.system.dal.dataobject.sms.SmsTemplateDO; +import com.tashow.cloud.system.dal.mysql.sms.SmsLogMapper; +import com.tashow.cloud.systemapi.enums.sms.SmsReceiveStatusEnum; +import com.tashow.cloud.systemapi.enums.sms.SmsSendStatusEnum; +import com.tashow.cloud.system.controller.admin.sms.vo.log.SmsLogPageReqVO; +import com.tashow.cloud.systemapi.enums.sms.SmsReceiveStatusEnum; +import com.tashow.cloud.systemapi.enums.sms.SmsSendStatusEnum; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +import jakarta.annotation.Resource; +import java.time.LocalDateTime; +import java.util.Map; +import java.util.Objects; + +/** + * 短信日志 Service 实现类 + * + * @author zzf + */ +@Slf4j +@Service +public class SmsLogServiceImpl implements SmsLogService { + + @Resource + private SmsLogMapper smsLogMapper; + + @Override + public Long createSmsLog(String mobile, Long userId, Integer userType, Boolean isSend, + SmsTemplateDO template, String templateContent, Map templateParams) { + SmsLogDO.SmsLogDOBuilder logBuilder = SmsLogDO.builder(); + // 根据是否要发送,设置状态 + logBuilder.sendStatus(Objects.equals(isSend, true) ? SmsSendStatusEnum.INIT.getStatus() + : SmsSendStatusEnum.IGNORE.getStatus()); + // 设置手机相关字段 + logBuilder.mobile(mobile).userId(userId).userType(userType); + // 设置模板相关字段 + logBuilder.templateId(template.getId()).templateCode(template.getCode()).templateType(template.getType()); + logBuilder.templateContent(templateContent).templateParams(templateParams) + .apiTemplateId(template.getApiTemplateId()); + // 设置渠道相关字段 + logBuilder.channelId(template.getChannelId()).channelCode(template.getChannelCode()); + // 设置接收相关字段 + logBuilder.receiveStatus(SmsReceiveStatusEnum.INIT.getStatus()); + + // 插入数据库 + SmsLogDO logDO = logBuilder.build(); + smsLogMapper.insert(logDO); + return logDO.getId(); + } + + @Override + public void updateSmsSendResult(Long id, Boolean success, + String apiSendCode, String apiSendMsg, + String apiRequestId, String apiSerialNo) { + SmsSendStatusEnum sendStatus = success ? SmsSendStatusEnum.SUCCESS : SmsSendStatusEnum.FAILURE; + smsLogMapper.updateById(SmsLogDO.builder().id(id) + .sendStatus(sendStatus.getStatus()).sendTime(LocalDateTime.now()) + .apiSendCode(apiSendCode).apiSendMsg(apiSendMsg) + .apiRequestId(apiRequestId).apiSerialNo(apiSerialNo).build()); + } + + @Override + public void updateSmsReceiveResult(Long id, Boolean success, LocalDateTime receiveTime, + String apiReceiveCode, String apiReceiveMsg) { + SmsReceiveStatusEnum receiveStatus = Objects.equals(success, true) ? + SmsReceiveStatusEnum.SUCCESS : SmsReceiveStatusEnum.FAILURE; + smsLogMapper.updateById(SmsLogDO.builder().id(id).receiveStatus(receiveStatus.getStatus()) + .receiveTime(receiveTime).apiReceiveCode(apiReceiveCode).apiReceiveMsg(apiReceiveMsg).build()); + } + + @Override + public PageResult getSmsLogPage(SmsLogPageReqVO pageReqVO) { + return smsLogMapper.selectPage(pageReqVO); + } + +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/service/sms/SmsSendService.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/service/sms/SmsSendService.java new file mode 100644 index 0000000..dadd3a8 --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/service/sms/SmsSendService.java @@ -0,0 +1,78 @@ +package com.tashow.cloud.system.service.sms; + +import com.tashow.cloud.system.mq.message.sms.SmsSendMessage; + +import java.util.List; +import java.util.Map; + +/** + * 短信发送 Service 接口 + * + * @author 芋道源码 + */ +public interface SmsSendService { + + /** + * 发送单条短信给管理后台的用户 + * + * 在 mobile 为空时,使用 userId 加载对应管理员的手机号 + * + * @param mobile 手机号 + * @param userId 用户编号 + * @param templateCode 短信模板编号 + * @param templateParams 短信模板参数 + * @return 发送日志编号 + */ + Long sendSingleSmsToAdmin(String mobile, Long userId, + String templateCode, Map templateParams); + + /** + * 发送单条短信给用户 APP 的用户 + * + * 在 mobile 为空时,使用 userId 加载对应会员的手机号 + * + * @param mobile 手机号 + * @param userId 用户编号 + * @param templateCode 短信模板编号 + * @param templateParams 短信模板参数 + * @return 发送日志编号 + */ + Long sendSingleSmsToMember(String mobile, Long userId, + String templateCode, Map templateParams); + + /** + * 发送单条短信给用户 + * + * @param mobile 手机号 + * @param userId 用户编号 + * @param userType 用户类型 + * @param templateCode 短信模板编号 + * @param templateParams 短信模板参数 + * @return 发送日志编号 + */ + Long sendSingleSms(String mobile, Long userId, Integer userType, + String templateCode, Map templateParams); + + default void sendBatchSms(List mobiles, List userIds, Integer userType, + String templateCode, Map templateParams) { + throw new UnsupportedOperationException("暂时不支持该操作,感兴趣可以实现该功能哟!"); + } + + /** + * 执行真正的短信发送 + * 注意,该方法仅仅提供给 MQ Consumer 使用 + * + * @param message 短信 + */ + void doSendSms(SmsSendMessage message); + + /** + * 接收短信的接收结果 + * + * @param channelCode 渠道编码 + * @param text 结果内容 + * @throws Throwable 处理失败时,抛出异常 + */ + void receiveSmsStatus(String channelCode, String text) throws Throwable; + +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/service/sms/SmsSendServiceImpl.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/service/sms/SmsSendServiceImpl.java new file mode 100644 index 0000000..072d975 --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/service/sms/SmsSendServiceImpl.java @@ -0,0 +1,191 @@ +package com.tashow.cloud.system.service.sms; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.exceptions.ExceptionUtil; +import cn.hutool.core.lang.Assert; +import cn.hutool.core.util.StrUtil; +import com.tashow.cloud.common.core.KeyValue; +import com.tashow.cloud.common.enums.CommonStatusEnum; +import com.tashow.cloud.common.enums.UserTypeEnum; +import com.tashow.cloud.permission.core.annotation.DataPermission; +import com.tashow.cloud.system.framework.sms.core.client.SmsClient; +import com.tashow.cloud.system.framework.sms.core.client.dto.SmsReceiveRespDTO; +import com.tashow.cloud.system.framework.sms.core.client.dto.SmsSendRespDTO; +import com.tashow.cloud.system.dal.dataobject.sms.SmsChannelDO; +import com.tashow.cloud.system.dal.dataobject.sms.SmsTemplateDO; +import com.tashow.cloud.system.dal.dataobject.user.AdminUserDO; +import com.tashow.cloud.system.mq.message.sms.SmsSendMessage; +import com.tashow.cloud.system.mq.producer.sms.SmsProducer; +import com.tashow.cloud.system.service.member.MemberService; +import com.tashow.cloud.system.service.user.AdminUserService; +import com.google.common.annotations.VisibleForTesting; +import com.tashow.cloud.systemapi.enums.ErrorCodeConstants; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +import jakarta.annotation.Resource; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import static com.tashow.cloud.common.exception.util.ServiceExceptionUtil.exception; + +/** + * 短信发送 Service 发送的实现 + * + * @author 芋道源码 + */ +@Service +@Slf4j +public class SmsSendServiceImpl implements SmsSendService { + + @Resource + private AdminUserService adminUserService; + @Resource + private MemberService memberService; + @Resource + private SmsChannelService smsChannelService; + @Resource + private SmsTemplateService smsTemplateService; + @Resource + private SmsLogService smsLogService; + + @Resource + private SmsProducer smsProducer; + + @Override + @DataPermission(enable = false) // 发送短信时,无需考虑数据权限 + public Long sendSingleSmsToAdmin(String mobile, Long userId, String templateCode, Map templateParams) { + // 如果 mobile 为空,则加载用户编号对应的手机号 + if (StrUtil.isEmpty(mobile)) { + AdminUserDO user = adminUserService.getUser(userId); + if (user != null) { + mobile = user.getMobile(); + } + } + // 执行发送 + return sendSingleSms(mobile, userId, UserTypeEnum.ADMIN.getValue(), templateCode, templateParams); + } + + @Override + public Long sendSingleSmsToMember(String mobile, Long userId, String templateCode, Map templateParams) { + // 如果 mobile 为空,则加载用户编号对应的手机号 + if (StrUtil.isEmpty(mobile)) { + mobile = memberService.getMemberUserMobile(userId); + } + // 执行发送 + return sendSingleSms(mobile, userId, UserTypeEnum.MEMBER.getValue(), templateCode, templateParams); + } + + @Override + public Long sendSingleSms(String mobile, Long userId, Integer userType, + String templateCode, Map templateParams) { + // 校验短信模板是否合法 + SmsTemplateDO template = validateSmsTemplate(templateCode); + // 校验短信渠道是否合法 + SmsChannelDO smsChannel = validateSmsChannel(template.getChannelId()); + + // 校验手机号码是否存在 + mobile = validateMobile(mobile); + // 构建有序的模板参数。为什么放在这个位置,是提前保证模板参数的正确性,而不是到了插入发送日志 + List> newTemplateParams = buildTemplateParams(template, templateParams); + + // 创建发送日志。如果模板被禁用,则不发送短信,只记录日志 + Boolean isSend = CommonStatusEnum.ENABLE.getStatus().equals(template.getStatus()) + && CommonStatusEnum.ENABLE.getStatus().equals(smsChannel.getStatus()); + String content = smsTemplateService.formatSmsTemplateContent(template.getContent(), templateParams); + Long sendLogId = smsLogService.createSmsLog(mobile, userId, userType, isSend, template, content, templateParams); + + // 发送 MQ 消息,异步执行发送短信 + if (isSend) { + smsProducer.sendSmsSendMessage(sendLogId, mobile, template.getChannelId(), + template.getApiTemplateId(), newTemplateParams); + } + return sendLogId; + } + + @VisibleForTesting + SmsChannelDO validateSmsChannel(Long channelId) { + // 获得短信模板。考虑到效率,从缓存中获取 + SmsChannelDO channelDO = smsChannelService.getSmsChannel(channelId); + // 短信模板不存在 + if (channelDO == null) { + throw exception(ErrorCodeConstants.SMS_CHANNEL_NOT_EXISTS); + } + return channelDO; + } + + @VisibleForTesting + SmsTemplateDO validateSmsTemplate(String templateCode) { + // 获得短信模板。考虑到效率,从缓存中获取 + SmsTemplateDO template = smsTemplateService.getSmsTemplateByCodeFromCache(templateCode); + // 短信模板不存在 + if (template == null) { + throw exception(ErrorCodeConstants.SMS_SEND_TEMPLATE_NOT_EXISTS); + } + return template; + } + + /** + * 将参数模板,处理成有序的 KeyValue 数组 + *

+ * 原因是,部分短信平台并不是使用 key 作为参数,而是数组下标,例如说 腾讯云 + * + * @param template 短信模板 + * @param templateParams 原始参数 + * @return 处理后的参数 + */ + @VisibleForTesting + List> buildTemplateParams(SmsTemplateDO template, Map templateParams) { + return template.getParams().stream().map(key -> { + Object value = templateParams.get(key); + if (value == null) { + throw exception(ErrorCodeConstants.SMS_SEND_MOBILE_TEMPLATE_PARAM_MISS, key); + } + return new KeyValue<>(key, value); + }).collect(Collectors.toList()); + } + + @VisibleForTesting + public String validateMobile(String mobile) { + if (StrUtil.isEmpty(mobile)) { + throw exception(ErrorCodeConstants.SMS_SEND_MOBILE_NOT_EXISTS); + } + return mobile; + } + + @Override + public void doSendSms(SmsSendMessage message) { + // 获得渠道对应的 SmsClient 客户端 + SmsClient smsClient = smsChannelService.getSmsClient(message.getChannelId()); + Assert.notNull(smsClient, "短信客户端({}) 不存在", message.getChannelId()); + // 发送短信 + try { + SmsSendRespDTO sendResponse = smsClient.sendSms(message.getLogId(), message.getMobile(), + message.getApiTemplateId(), message.getTemplateParams()); + smsLogService.updateSmsSendResult(message.getLogId(), sendResponse.getSuccess(), + sendResponse.getApiCode(), sendResponse.getApiMsg(), + sendResponse.getApiRequestId(), sendResponse.getSerialNo()); + } catch (Throwable ex) { + log.error("[doSendSms][发送短信异常,日志编号({})]", message.getLogId(), ex); + smsLogService.updateSmsSendResult(message.getLogId(), false, + "EXCEPTION", ExceptionUtil.getRootCauseMessage(ex), null, null); + } + } + + @Override + public void receiveSmsStatus(String channelCode, String text) throws Throwable { + // 获得渠道对应的 SmsClient 客户端 + SmsClient smsClient = smsChannelService.getSmsClient(channelCode); + Assert.notNull(smsClient, "短信客户端({}) 不存在", channelCode); + // 解析内容 + List receiveResults = smsClient.parseSmsReceiveStatus(text); + if (CollUtil.isEmpty(receiveResults)) { + return; + } + // 更新短信日志的接收结果. 因为量一般不大,所以先使用 for 循环更新 + receiveResults.forEach(result -> smsLogService.updateSmsReceiveResult(result.getLogId(), + result.getSuccess(), result.getReceiveTime(), result.getErrorCode(), result.getErrorMsg())); + } + +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/service/sms/SmsTemplateService.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/service/sms/SmsTemplateService.java new file mode 100644 index 0000000..35d9013 --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/service/sms/SmsTemplateService.java @@ -0,0 +1,84 @@ +package com.tashow.cloud.system.service.sms; + +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.system.controller.admin.sms.vo.template.SmsTemplatePageReqVO; +import com.tashow.cloud.system.controller.admin.sms.vo.template.SmsTemplateSaveReqVO; +import com.tashow.cloud.system.dal.dataobject.sms.SmsTemplateDO; + +import com.tashow.cloud.system.controller.admin.sms.vo.template.SmsTemplatePageReqVO; +import com.tashow.cloud.system.controller.admin.sms.vo.template.SmsTemplateSaveReqVO; +import jakarta.validation.Valid; +import java.util.Map; + +/** + * 短信模板 Service 接口 + * + * @author zzf + * @since 2021/1/25 9:24 + */ +public interface SmsTemplateService { + + /** + * 创建短信模板 + * + * @param createReqVO 创建信息 + * @return 编号 + */ + Long createSmsTemplate(@Valid SmsTemplateSaveReqVO createReqVO); + + /** + * 更新短信模板 + * + * @param updateReqVO 更新信息 + */ + void updateSmsTemplate(@Valid SmsTemplateSaveReqVO updateReqVO); + + /** + * 删除短信模板 + * + * @param id 编号 + */ + void deleteSmsTemplate(Long id); + + /** + * 获得短信模板 + * + * @param id 编号 + * @return 短信模板 + */ + SmsTemplateDO getSmsTemplate(Long id); + + /** + * 获得短信模板,从缓存中 + * + * @param code 模板编码 + * @return 短信模板 + */ + SmsTemplateDO getSmsTemplateByCodeFromCache(String code); + + /** + * 获得短信模板分页 + * + * @param pageReqVO 分页查询 + * @return 短信模板分页 + */ + PageResult getSmsTemplatePage(SmsTemplatePageReqVO pageReqVO); + + /** + * 获得指定短信渠道下的短信模板数量 + * + * @param channelId 短信渠道编号 + * @return 数量 + */ + Long getSmsTemplateCountByChannelId(Long channelId); + + /** + * 格式化短信内容 + * + * @param content 短信模板的内容 + * @param params 内容的参数 + * @return 格式化后的内容 + */ + String formatSmsTemplateContent(String content, Map params); + +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/service/sms/SmsTemplateServiceImpl.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/service/sms/SmsTemplateServiceImpl.java new file mode 100644 index 0000000..3a9ca9f --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/service/sms/SmsTemplateServiceImpl.java @@ -0,0 +1,204 @@ +package com.tashow.cloud.system.service.sms; + +import cn.hutool.core.exceptions.ExceptionUtil; +import cn.hutool.core.lang.Assert; +import cn.hutool.core.util.ReUtil; +import cn.hutool.core.util.StrUtil; +import com.tashow.cloud.common.enums.CommonStatusEnum; +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.common.util.object.BeanUtils; +import com.tashow.cloud.system.framework.sms.core.client.SmsClient; +import com.tashow.cloud.system.framework.sms.core.client.dto.SmsTemplateRespDTO; +import com.tashow.cloud.system.framework.sms.core.enums.SmsTemplateAuditStatusEnum; +import com.tashow.cloud.system.controller.admin.sms.vo.template.SmsTemplatePageReqVO; +import com.tashow.cloud.system.controller.admin.sms.vo.template.SmsTemplateSaveReqVO; +import com.tashow.cloud.system.dal.dataobject.sms.SmsChannelDO; +import com.tashow.cloud.system.dal.dataobject.sms.SmsTemplateDO; +import com.tashow.cloud.system.dal.mysql.sms.SmsTemplateMapper; +import com.tashow.cloud.system.dal.redis.RedisKeyConstants; +import com.google.common.annotations.VisibleForTesting; +import com.tashow.cloud.system.controller.admin.sms.vo.template.SmsTemplatePageReqVO; +import com.tashow.cloud.system.controller.admin.sms.vo.template.SmsTemplateSaveReqVO; +import com.tashow.cloud.systemapi.enums.ErrorCodeConstants; +import com.tashow.cloud.system.framework.sms.core.client.SmsClient; +import com.tashow.cloud.system.framework.sms.core.client.dto.SmsTemplateRespDTO; +import com.tashow.cloud.system.framework.sms.core.enums.SmsTemplateAuditStatusEnum; +import lombok.extern.slf4j.Slf4j; +import org.springframework.cache.annotation.CacheEvict; +import org.springframework.cache.annotation.Cacheable; +import org.springframework.stereotype.Service; + +import jakarta.annotation.Resource; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.regex.Pattern; + +import static com.tashow.cloud.common.exception.util.ServiceExceptionUtil.exception; + +/** + * 短信模板 Service 实现类 + * + * @author zzf + * @since 2021/1/25 9:25 + */ +@Service +@Slf4j +public class SmsTemplateServiceImpl implements SmsTemplateService { + + /** + * 正则表达式,匹配 {} 中的变量 + */ + private static final Pattern PATTERN_PARAMS = Pattern.compile("\\{(.*?)}"); + + @Resource + private SmsTemplateMapper smsTemplateMapper; + + @Resource + private SmsChannelService smsChannelService; + + @Override + public Long createSmsTemplate(SmsTemplateSaveReqVO createReqVO) { + // 校验短信渠道 + SmsChannelDO channelDO = validateSmsChannel(createReqVO.getChannelId()); + // 校验短信编码是否重复 + validateSmsTemplateCodeDuplicate(null, createReqVO.getCode()); + // 校验短信模板 + validateApiTemplate(createReqVO.getChannelId(), createReqVO.getApiTemplateId()); + + // 插入 + SmsTemplateDO template = BeanUtils.toBean(createReqVO, SmsTemplateDO.class); + template.setParams(parseTemplateContentParams(template.getContent())); + template.setChannelCode(channelDO.getCode()); + smsTemplateMapper.insert(template); + // 返回 + return template.getId(); + } + + @Override + @CacheEvict(cacheNames = RedisKeyConstants.SMS_TEMPLATE, + allEntries = true) // allEntries 清空所有缓存,因为可能修改到 code 字段,不好清理 + public void updateSmsTemplate(SmsTemplateSaveReqVO updateReqVO) { + // 校验存在 + validateSmsTemplateExists(updateReqVO.getId()); + // 校验短信渠道 + SmsChannelDO channelDO = validateSmsChannel(updateReqVO.getChannelId()); + // 校验短信编码是否重复 + validateSmsTemplateCodeDuplicate(updateReqVO.getId(), updateReqVO.getCode()); + // 校验短信模板 + validateApiTemplate(updateReqVO.getChannelId(), updateReqVO.getApiTemplateId()); + + // 更新 + SmsTemplateDO updateObj = BeanUtils.toBean(updateReqVO, SmsTemplateDO.class); + updateObj.setParams(parseTemplateContentParams(updateObj.getContent())); + updateObj.setChannelCode(channelDO.getCode()); + smsTemplateMapper.updateById(updateObj); + } + + @Override + @CacheEvict(cacheNames = RedisKeyConstants.SMS_TEMPLATE, + allEntries = true) // allEntries 清空所有缓存,因为 id 不是直接的缓存 code,不好清理 + public void deleteSmsTemplate(Long id) { + // 校验存在 + validateSmsTemplateExists(id); + // 更新 + smsTemplateMapper.deleteById(id); + } + + private void validateSmsTemplateExists(Long id) { + if (smsTemplateMapper.selectById(id) == null) { + throw exception(ErrorCodeConstants.SMS_TEMPLATE_NOT_EXISTS); + } + } + + @Override + public SmsTemplateDO getSmsTemplate(Long id) { + return smsTemplateMapper.selectById(id); + } + + @Override + @Cacheable(cacheNames = RedisKeyConstants.SMS_TEMPLATE, key = "#code", + unless = "#result == null") + public SmsTemplateDO getSmsTemplateByCodeFromCache(String code) { + return smsTemplateMapper.selectByCode(code); + } + + @Override + public PageResult getSmsTemplatePage(SmsTemplatePageReqVO pageReqVO) { + return smsTemplateMapper.selectPage(pageReqVO); + } + + @Override + public Long getSmsTemplateCountByChannelId(Long channelId) { + return smsTemplateMapper.selectCountByChannelId(channelId); + } + + @VisibleForTesting + public SmsChannelDO validateSmsChannel(Long channelId) { + SmsChannelDO channelDO = smsChannelService.getSmsChannel(channelId); + if (channelDO == null) { + throw exception(ErrorCodeConstants.SMS_CHANNEL_NOT_EXISTS); + } + if (CommonStatusEnum.isDisable(channelDO.getStatus())) { + throw exception(ErrorCodeConstants.SMS_CHANNEL_DISABLE); + } + return channelDO; + } + + @VisibleForTesting + public void validateSmsTemplateCodeDuplicate(Long id, String code) { + SmsTemplateDO template = smsTemplateMapper.selectByCode(code); + if (template == null) { + return; + } + // 如果 id 为空,说明不用比较是否为相同 id 的字典类型 + if (id == null) { + throw exception(ErrorCodeConstants.SMS_TEMPLATE_CODE_DUPLICATE, code); + } + if (!template.getId().equals(id)) { + throw exception(ErrorCodeConstants.SMS_TEMPLATE_CODE_DUPLICATE, code); + } + } + + /** + * 校验 API 短信平台的模板是否有效 + * + * @param channelId 渠道编号 + * @param apiTemplateId API 模板编号 + */ + @VisibleForTesting + void validateApiTemplate(Long channelId, String apiTemplateId) { + // 获得短信模板 + SmsClient smsClient = smsChannelService.getSmsClient(channelId); + Assert.notNull(smsClient, String.format("短信客户端(%d) 不存在", channelId)); + SmsTemplateRespDTO template; + try { + template = smsClient.getSmsTemplate(apiTemplateId); + } catch (Throwable ex) { + throw exception(ErrorCodeConstants.SMS_TEMPLATE_API_ERROR, ExceptionUtil.getRootCauseMessage(ex)); + } + // 校验短信模版 + if (template == null) { + throw exception(ErrorCodeConstants.SMS_TEMPLATE_API_NOT_FOUND); + } + if (Objects.equals(template.getAuditStatus(), SmsTemplateAuditStatusEnum.CHECKING.getStatus())) { + throw exception(ErrorCodeConstants.SMS_TEMPLATE_API_AUDIT_CHECKING); + } + if (Objects.equals(template.getAuditStatus(), SmsTemplateAuditStatusEnum.FAIL.getStatus())) { + throw exception(ErrorCodeConstants.SMS_TEMPLATE_API_AUDIT_FAIL, template.getAuditReason()); + } + Assert.equals(template.getAuditStatus(), SmsTemplateAuditStatusEnum.SUCCESS.getStatus(), + String.format("短信模板(%s) 审核状态(%d) 不正确", apiTemplateId, template.getAuditStatus())); + } + + @Override + public String formatSmsTemplateContent(String content, Map params) { + return StrUtil.format(content, params); + } + + @VisibleForTesting + List parseTemplateContentParams(String content) { + return ReUtil.findAllGroup1(PATTERN_PARAMS, content); + } + +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/service/social/SocialClientService.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/service/social/SocialClientService.java new file mode 100644 index 0000000..a82c463 --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/service/social/SocialClientService.java @@ -0,0 +1,140 @@ +package com.tashow.cloud.system.service.social; + +import cn.binarywang.wx.miniapp.bean.WxMaPhoneNumberInfo; +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.systemapi.api.social.dto.SocialWxQrcodeReqDTO; +import com.tashow.cloud.systemapi.api.social.dto.SocialWxaSubscribeMessageSendReqDTO; +import com.tashow.cloud.system.controller.admin.socail.vo.client.SocialClientPageReqVO; +import com.tashow.cloud.system.controller.admin.socail.vo.client.SocialClientSaveReqVO; +import com.tashow.cloud.system.dal.dataobject.social.SocialClientDO; +import com.tashow.cloud.systemapi.enums.social.SocialTypeEnum; +import com.tashow.cloud.systemapi.api.social.dto.SocialWxQrcodeReqDTO; +import com.tashow.cloud.systemapi.api.social.dto.SocialWxaSubscribeMessageSendReqDTO; +import com.tashow.cloud.system.controller.admin.socail.vo.client.SocialClientPageReqVO; +import com.tashow.cloud.system.controller.admin.socail.vo.client.SocialClientSaveReqVO; +import com.tashow.cloud.systemapi.enums.social.SocialTypeEnum; +import com.xingyuv.jushauth.model.AuthUser; +import jakarta.validation.Valid; +import me.chanjar.weixin.common.bean.WxJsapiSignature; +import me.chanjar.weixin.common.bean.subscribemsg.TemplateInfo; + +import java.util.List; + +/** + * 社交应用 Service 接口 + * + * @author 芋道源码 + */ +public interface SocialClientService { + + /** + * 获得社交平台的授权 URL + * + * @param socialType 社交平台的类型 {@link SocialTypeEnum} + * @param userType 用户类型 + * @param redirectUri 重定向 URL + * @return 社交平台的授权 URL + */ + String getAuthorizeUrl(Integer socialType, Integer userType, String redirectUri); + + /** + * 请求社交平台,获得授权的用户 + * + * @param socialType 社交平台的类型 + * @param userType 用户类型 + * @param code 授权码 + * @param state 授权 state + * @return 授权的用户 + */ + AuthUser getAuthUser(Integer socialType, Integer userType, String code, String state); + + // =================== 微信公众号独有 =================== + + /** + * 创建微信公众号的 JS SDK 初始化所需的签名 + * + * @param userType 用户类型 + * @param url 访问的 URL 地址 + * @return 签名 + */ + WxJsapiSignature createWxMpJsapiSignature(Integer userType, String url); + + // =================== 微信小程序独有 =================== + + /** + * 获得微信小程序的手机信息 + * + * @param userType 用户类型 + * @param phoneCode 手机授权码 + * @return 手机信息 + */ + WxMaPhoneNumberInfo getWxMaPhoneNumberInfo(Integer userType, String phoneCode); + + /** + * 获得小程序二维码 + * + * @param reqVO 请求信息 + * @return 小程序二维码 + */ + byte[] getWxaQrcode(SocialWxQrcodeReqDTO reqVO); + + /** + * 获得微信小程订阅模板 + * + * 缓存的目的:考虑到微信小程序订阅消息选择好模版后几乎不会变动,缓存增加查询效率 + * + * @param userType 用户类型 + * @return 微信小程订阅模板 + */ + List getSubscribeTemplateList(Integer userType); + + /** + * 发送微信小程序订阅消息 + * + * @param reqDTO 请求 + * @param templateId 模版编号 + * @param openId 会员 openId + */ + void sendSubscribeMessage(SocialWxaSubscribeMessageSendReqDTO reqDTO, String templateId, String openId); + + // =================== 客户端管理 =================== + + /** + * 创建社交客户端 + * + * @param createReqVO 创建信息 + * @return 编号 + */ + Long createSocialClient(@Valid SocialClientSaveReqVO createReqVO); + + /** + * 更新社交客户端 + * + * @param updateReqVO 更新信息 + */ + void updateSocialClient(@Valid SocialClientSaveReqVO updateReqVO); + + /** + * 删除社交客户端 + * + * @param id 编号 + */ + void deleteSocialClient(Long id); + + /** + * 获得社交客户端 + * + * @param id 编号 + * @return 社交客户端 + */ + SocialClientDO getSocialClient(Long id); + + /** + * 获得社交客户端分页 + * + * @param pageReqVO 分页查询 + * @return 社交客户端分页 + */ + PageResult getSocialClientPage(SocialClientPageReqVO pageReqVO); + +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/service/social/SocialClientServiceImpl.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/service/social/SocialClientServiceImpl.java new file mode 100644 index 0000000..dba09cb --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/service/social/SocialClientServiceImpl.java @@ -0,0 +1,439 @@ +package com.tashow.cloud.system.service.social; + +import cn.binarywang.wx.miniapp.api.WxMaService; +import cn.binarywang.wx.miniapp.api.WxMaSubscribeService; +import cn.binarywang.wx.miniapp.api.impl.WxMaServiceImpl; +import cn.binarywang.wx.miniapp.bean.WxMaPhoneNumberInfo; +import cn.binarywang.wx.miniapp.bean.WxMaSubscribeMessage; +import cn.binarywang.wx.miniapp.config.impl.WxMaRedisBetterConfigImpl; +import cn.binarywang.wx.miniapp.constant.WxMaConstants; +import cn.hutool.core.bean.BeanUtil; +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.lang.Assert; +import cn.hutool.core.util.ObjUtil; +import cn.hutool.core.util.ReflectUtil; +import com.tashow.cloud.common.enums.CommonStatusEnum; +import com.tashow.cloud.common.enums.UserTypeEnum; +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.common.util.cache.CacheUtils; +import com.tashow.cloud.common.util.http.HttpUtils; +import com.tashow.cloud.common.util.object.BeanUtils; +import com.tashow.cloud.systemapi.api.social.dto.SocialWxQrcodeReqDTO; +import com.tashow.cloud.systemapi.api.social.dto.SocialWxaSubscribeMessageSendReqDTO; +import com.tashow.cloud.system.controller.admin.socail.vo.client.SocialClientPageReqVO; +import com.tashow.cloud.system.controller.admin.socail.vo.client.SocialClientSaveReqVO; +import com.tashow.cloud.system.dal.dataobject.social.SocialClientDO; +import com.tashow.cloud.system.dal.mysql.social.SocialClientMapper; +import com.tashow.cloud.system.dal.redis.RedisKeyConstants; +import com.tashow.cloud.systemapi.enums.social.SocialTypeEnum; +import com.binarywang.spring.starter.wxjava.miniapp.properties.WxMaProperties; +import com.binarywang.spring.starter.wxjava.mp.properties.WxMpProperties; +import com.google.common.annotations.VisibleForTesting; +import com.google.common.cache.CacheLoader; +import com.google.common.cache.LoadingCache; +import com.tashow.cloud.systemapi.enums.ErrorCodeConstants; +import com.xingyuv.jushauth.config.AuthConfig; +import com.xingyuv.jushauth.model.AuthCallback; +import com.xingyuv.jushauth.model.AuthResponse; +import com.xingyuv.jushauth.model.AuthUser; +import com.xingyuv.jushauth.request.AuthRequest; +import com.xingyuv.jushauth.utils.AuthStateUtils; +import com.xingyuv.justauth.AuthRequestFactory; +import jakarta.annotation.Resource; +import lombok.SneakyThrows; +import lombok.extern.slf4j.Slf4j; +import me.chanjar.weixin.common.bean.WxJsapiSignature; +import me.chanjar.weixin.common.bean.subscribemsg.TemplateInfo; +import me.chanjar.weixin.common.error.WxErrorException; +import me.chanjar.weixin.common.redis.RedisTemplateWxRedisOps; +import me.chanjar.weixin.mp.api.WxMpService; +import me.chanjar.weixin.mp.api.impl.WxMpServiceImpl; +import me.chanjar.weixin.mp.config.impl.WxMpRedisConfigImpl; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.cache.annotation.Cacheable; +import org.springframework.data.redis.core.StringRedisTemplate; +import org.springframework.stereotype.Service; + +import java.time.Duration; +import java.util.List; +import java.util.Map; +import java.util.Objects; + +import static com.tashow.cloud.common.exception.util.ServiceExceptionUtil.exception; +import static com.tashow.cloud.common.util.collection.MapUtils.findAndThen; +import static com.tashow.cloud.common.util.json.JsonUtils.toJsonString; + +/** + * 社交应用 Service 实现类 + * + * @author 芋道源码 + */ +@Service +@Slf4j +public class SocialClientServiceImpl implements SocialClientService { + + /** + * 小程序码要打开的小程序版本 + * + * 1. release:正式版 + * 2. trial:体验版 + * 3. developer:开发版 + */ + @Value("${yudao.wxa-code.env-version:release}") + public String envVersion; + /** + * 订阅消息跳转小程序类型 + * + * 1. developer:开发版 + * 2. trial:体验版 + * 3. formal:正式版 + */ + @Value("${yudao.wxa-subscribe-message.miniprogram-state:formal}") + public String miniprogramState; + + @Resource + private AuthRequestFactory authRequestFactory; + + @Resource + private WxMpService wxMpService; + @Resource + private WxMpProperties wxMpProperties; + @Resource + private StringRedisTemplate stringRedisTemplate; // WxMpService 需要使用到,所以在 Service 注入了它 + /** + * 缓存 WxMpService 对象 + * + * key:使用微信公众号的 appId + secret 拼接,即 {@link SocialClientDO} 的 clientId 和 clientSecret 属性。 + * 为什么 key 使用这种格式?因为 {@link SocialClientDO} 在管理后台可以变更,通过这个 key 存储它的单例。 + * + * 为什么要做 WxMpService 缓存?因为 WxMpService 构建成本比较大,所以尽量保证它是单例。 + */ + private final LoadingCache wxMpServiceCache = CacheUtils.buildAsyncReloadingCache( + Duration.ofSeconds(10L), + new CacheLoader() { + + @Override + public WxMpService load(String key) { + String[] keys = key.split(":"); + return buildWxMpService(keys[0], keys[1]); + } + + }); + + @Resource + private WxMaService wxMaService; + @Resource + private WxMaProperties wxMaProperties; + /** + * 缓存 WxMaService 对象 + * + * 说明同 {@link #wxMpServiceCache} 变量 + */ + private final LoadingCache wxMaServiceCache = CacheUtils.buildAsyncReloadingCache( + Duration.ofSeconds(10L), + new CacheLoader() { + + @Override + public WxMaService load(String key) { + String[] keys = key.split(":"); + return buildWxMaService(keys[0], keys[1]); + } + + }); + + @Resource + private SocialClientMapper socialClientMapper; + + @Override + public String getAuthorizeUrl(Integer socialType, Integer userType, String redirectUri) { + // 获得对应的 AuthRequest 实现 + AuthRequest authRequest = buildAuthRequest(socialType, userType); + // 生成跳转地址 + String authorizeUri = authRequest.authorize(AuthStateUtils.createState()); + return HttpUtils.replaceUrlQuery(authorizeUri, "redirect_uri", redirectUri); + } + + @Override + public AuthUser getAuthUser(Integer socialType, Integer userType, String code, String state) { + // 构建请求 + AuthRequest authRequest = buildAuthRequest(socialType, userType); + AuthCallback authCallback = AuthCallback.builder().code(code).state(state).build(); + // 执行请求 + AuthResponse authResponse = authRequest.login(authCallback); + log.info("[getAuthUser][请求社交平台 type({}) request({}) response({})]", socialType, + toJsonString(authCallback), toJsonString(authResponse)); + if (!authResponse.ok()) { + throw exception(ErrorCodeConstants.SOCIAL_USER_AUTH_FAILURE, authResponse.getMsg()); + } + return (AuthUser) authResponse.getData(); + } + + /** + * 构建 AuthRequest 对象,支持多租户配置 + * + * @param socialType 社交类型 + * @param userType 用户类型 + * @return AuthRequest 对象 + */ + @VisibleForTesting + AuthRequest buildAuthRequest(Integer socialType, Integer userType) { + // 1. 先查找默认的配置项,从 application-*.yaml 中读取 + AuthRequest request = authRequestFactory.get(SocialTypeEnum.valueOfType(socialType).getSource()); + Assert.notNull(request, String.format("社交平台(%d) 不存在", socialType)); + // 2. 查询 DB 的配置项,如果存在则进行覆盖 + SocialClientDO client = socialClientMapper.selectBySocialTypeAndUserType(socialType, userType); + if (client != null && Objects.equals(client.getStatus(), CommonStatusEnum.ENABLE.getStatus())) { + // 2.1 构造新的 AuthConfig 对象 + AuthConfig authConfig = (AuthConfig) ReflectUtil.getFieldValue(request, "config"); + AuthConfig newAuthConfig = ReflectUtil.newInstance(authConfig.getClass()); + BeanUtil.copyProperties(authConfig, newAuthConfig); + // 2.2 修改对应的 clientId + clientSecret 密钥 + newAuthConfig.setClientId(client.getClientId()); + newAuthConfig.setClientSecret(client.getClientSecret()); + if (client.getAgentId() != null) { // 如果有 agentId 则修改 agentId + newAuthConfig.setAgentId(client.getAgentId()); + } + // 2.3 设置会 request 里,进行后续使用 + ReflectUtil.setFieldValue(request, "config", newAuthConfig); + } + return request; + } + + // =================== 微信公众号独有 =================== + + @Override + @SneakyThrows + public WxJsapiSignature createWxMpJsapiSignature(Integer userType, String url) { + WxMpService service = getWxMpService(userType); + return service.createJsapiSignature(url); + } + + /** + * 获得 clientId + clientSecret 对应的 WxMpService 对象 + * + * @param userType 用户类型 + * @return WxMpService 对象 + */ + @VisibleForTesting + WxMpService getWxMpService(Integer userType) { + // 第一步,查询 DB 的配置项,获得对应的 WxMpService 对象 + SocialClientDO client = socialClientMapper.selectBySocialTypeAndUserType( + SocialTypeEnum.WECHAT_MP.getType(), userType); + if (client != null && Objects.equals(client.getStatus(), CommonStatusEnum.ENABLE.getStatus())) { + return wxMpServiceCache.getUnchecked(client.getClientId() + ":" + client.getClientSecret()); + } + // 第二步,不存在 DB 配置项,则使用 application-*.yaml 对应的 WxMpService 对象 + return wxMpService; + } + + /** + * 创建 clientId + clientSecret 对应的 WxMpService 对象 + * + * @param clientId 微信公众号 appId + * @param clientSecret 微信公众号 secret + * @return WxMpService 对象 + */ + public WxMpService buildWxMpService(String clientId, String clientSecret) { + // 第一步,创建 WxMpRedisConfigImpl 对象 + WxMpRedisConfigImpl configStorage = new WxMpRedisConfigImpl( + new RedisTemplateWxRedisOps(stringRedisTemplate), + wxMpProperties.getConfigStorage().getKeyPrefix()); + configStorage.setAppId(clientId); + configStorage.setSecret(clientSecret); + + // 第二步,创建 WxMpService 对象 + WxMpService service = new WxMpServiceImpl(); + service.setWxMpConfigStorage(configStorage); + return service; + } + + // =================== 微信小程序独有 =================== + + @Override + public WxMaPhoneNumberInfo getWxMaPhoneNumberInfo(Integer userType, String phoneCode) { + WxMaService service = getWxMaService(userType); + try { + return service.getUserService().getPhoneNoInfo(phoneCode); + } catch (WxErrorException e) { + log.error("[getPhoneNoInfo][userType({}) phoneCode({}) 获得手机号失败]", userType, phoneCode, e); + throw exception(ErrorCodeConstants.SOCIAL_CLIENT_WEIXIN_MINI_APP_PHONE_CODE_ERROR); + } + } + + @Override + public byte[] getWxaQrcode(SocialWxQrcodeReqDTO reqVO) { + WxMaService service = getWxMaService(UserTypeEnum.MEMBER.getValue()); + try { + return service.getQrcodeService().createWxaCodeUnlimitBytes( + ObjUtil.defaultIfEmpty(reqVO.getScene(), SocialWxQrcodeReqDTO.SCENE), + reqVO.getPath(), + ObjUtil.defaultIfNull(reqVO.getCheckPath(), SocialWxQrcodeReqDTO.CHECK_PATH), + envVersion, + ObjUtil.defaultIfNull(reqVO.getWidth(), SocialWxQrcodeReqDTO.WIDTH), + ObjUtil.defaultIfNull(reqVO.getAutoColor(), SocialWxQrcodeReqDTO.AUTO_COLOR), + null, + ObjUtil.defaultIfNull(reqVO.getHyaline(), SocialWxQrcodeReqDTO.HYALINE)); + } catch (WxErrorException e) { + log.error("[getWxQrcode][reqVO({}) 获得小程序码失败]", reqVO, e); + throw exception(ErrorCodeConstants.SOCIAL_CLIENT_WEIXIN_MINI_APP_QRCODE_ERROR); + } + } + + @Override + @Cacheable(cacheNames = RedisKeyConstants.WXA_SUBSCRIBE_TEMPLATE, key = "#userType", + unless = "#result == null") + public List getSubscribeTemplateList(Integer userType) { + WxMaService service = getWxMaService(userType); + try { + WxMaSubscribeService subscribeService = service.getSubscribeService(); + return subscribeService.getTemplateList(); + } catch (WxErrorException e) { + log.error("[getSubscribeTemplate][userType({}) 获得小程序订阅消息模版]", userType, e); + throw exception(ErrorCodeConstants.SOCIAL_CLIENT_WEIXIN_MINI_APP_SUBSCRIBE_TEMPLATE_ERROR); + } + } + + @Override + public void sendSubscribeMessage(SocialWxaSubscribeMessageSendReqDTO reqDTO, String templateId, String openId) { + WxMaService service = getWxMaService(reqDTO.getUserType()); + try { + WxMaSubscribeService subscribeService = service.getSubscribeService(); + subscribeService.sendSubscribeMsg(buildMessageSendReqDTO(reqDTO, templateId, openId)); + } catch (WxErrorException e) { + log.error("[sendSubscribeMessage][reqVO({}) templateId({}) openId({}) 发送小程序订阅消息失败]", reqDTO, templateId, openId, e); + throw exception(ErrorCodeConstants.SOCIAL_CLIENT_WEIXIN_MINI_APP_SUBSCRIBE_MESSAGE_ERROR); + } + } + + /** + * 构建发送消息请求参数 + * + * @param reqDTO 请求 + * @param templateId 模版编号 + * @param openId 会员 openId + * @return 微信小程序订阅消息请求参数 + */ + private WxMaSubscribeMessage buildMessageSendReqDTO(SocialWxaSubscribeMessageSendReqDTO reqDTO, + String templateId, String openId) { + // 设置订阅消息基本参数 + WxMaSubscribeMessage subscribeMessage = new WxMaSubscribeMessage().setLang(WxMaConstants.MiniProgramLang.ZH_CN) + .setMiniprogramState(miniprogramState).setTemplateId(templateId).setToUser(openId).setPage(reqDTO.getPage()); + // 设置具体消息参数 + Map messages = reqDTO.getMessages(); + if (CollUtil.isNotEmpty(messages)) { + reqDTO.getMessages().keySet().forEach(key -> findAndThen(messages, key, value -> + subscribeMessage.addData(new WxMaSubscribeMessage.MsgData(key, value)))); + } + return subscribeMessage; + } + + /** + * 获得 clientId + clientSecret 对应的 WxMpService 对象 + * + * @param userType 用户类型 + * @return WxMpService 对象 + */ + @VisibleForTesting + WxMaService getWxMaService(Integer userType) { + // 第一步,查询 DB 的配置项,获得对应的 WxMaService 对象 + SocialClientDO client = socialClientMapper.selectBySocialTypeAndUserType( + SocialTypeEnum.WECHAT_MINI_APP.getType(), userType); + if (client != null && Objects.equals(client.getStatus(), CommonStatusEnum.ENABLE.getStatus())) { + return wxMaServiceCache.getUnchecked(client.getClientId() + ":" + client.getClientSecret()); + } + // 第二步,不存在 DB 配置项,则使用 application-*.yaml 对应的 WxMaService 对象 + return wxMaService; + } + + /** + * 创建 clientId + clientSecret 对应的 WxMaService 对象 + * + * @param clientId 微信小程序 appId + * @param clientSecret 微信小程序 secret + * @return WxMaService 对象 + */ + private WxMaService buildWxMaService(String clientId, String clientSecret) { + // 第一步,创建 WxMaRedisBetterConfigImpl 对象 + WxMaRedisBetterConfigImpl configStorage = new WxMaRedisBetterConfigImpl( + new RedisTemplateWxRedisOps(stringRedisTemplate), + wxMaProperties.getConfigStorage().getKeyPrefix()); + configStorage.setAppid(clientId); + configStorage.setSecret(clientSecret); + + // 第二步,创建 WxMpService 对象 + WxMaService service = new WxMaServiceImpl(); + service.setWxMaConfig(configStorage); + return service; + } + + // =================== 客户端管理 =================== + + @Override + public Long createSocialClient(SocialClientSaveReqVO createReqVO) { + // 校验重复 + validateSocialClientUnique(null, createReqVO.getUserType(), createReqVO.getSocialType()); + + // 插入 + SocialClientDO client = BeanUtils.toBean(createReqVO, SocialClientDO.class); + socialClientMapper.insert(client); + return client.getId(); + } + + @Override + public void updateSocialClient(SocialClientSaveReqVO updateReqVO) { + // 校验存在 + validateSocialClientExists(updateReqVO.getId()); + // 校验重复 + validateSocialClientUnique(updateReqVO.getId(), updateReqVO.getUserType(), updateReqVO.getSocialType()); + + // 更新 + SocialClientDO updateObj = BeanUtils.toBean(updateReqVO, SocialClientDO.class); + socialClientMapper.updateById(updateObj); + } + + @Override + public void deleteSocialClient(Long id) { + // 校验存在 + validateSocialClientExists(id); + // 删除 + socialClientMapper.deleteById(id); + } + + private void validateSocialClientExists(Long id) { + if (socialClientMapper.selectById(id) == null) { + throw exception(ErrorCodeConstants.SOCIAL_CLIENT_NOT_EXISTS); + } + } + + /** + * 校验社交应用是否重复,需要保证 userType + socialType 唯一 + * + * 原因是,不同端(userType)选择某个社交登录(socialType)时,需要通过 {@link #buildAuthRequest(Integer, Integer)} 构建对应的请求 + * + * @param id 编号 + * @param userType 用户类型 + * @param socialType 社交类型 + */ + private void validateSocialClientUnique(Long id, Integer userType, Integer socialType) { + SocialClientDO client = socialClientMapper.selectBySocialTypeAndUserType( + socialType, userType); + if (client == null) { + return; + } + if (id == null // 新增时,说明重复 + || ObjUtil.notEqual(id, client.getId())) { // 更新时,如果 id 不一致,说明重复 + throw exception(ErrorCodeConstants.SOCIAL_CLIENT_UNIQUE); + } + } + + @Override + public SocialClientDO getSocialClient(Long id) { + return socialClientMapper.selectById(id); + } + + @Override + public PageResult getSocialClientPage(SocialClientPageReqVO pageReqVO) { + return socialClientMapper.selectPage(pageReqVO); + } + +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/service/social/SocialUserService.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/service/social/SocialUserService.java new file mode 100644 index 0000000..bdd372b --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/service/social/SocialUserService.java @@ -0,0 +1,93 @@ +package com.tashow.cloud.system.service.social; + +import com.tashow.cloud.common.exception.ServiceException; +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.systemapi.api.social.dto.SocialUserBindReqDTO; +import com.tashow.cloud.systemapi.api.social.dto.SocialUserRespDTO; +import com.tashow.cloud.system.controller.admin.socail.vo.user.SocialUserPageReqVO; +import com.tashow.cloud.system.dal.dataobject.social.SocialUserDO; +import com.tashow.cloud.systemapi.enums.social.SocialTypeEnum; +import com.tashow.cloud.systemapi.api.social.dto.SocialUserBindReqDTO; +import com.tashow.cloud.systemapi.api.social.dto.SocialUserRespDTO; +import com.tashow.cloud.system.controller.admin.socail.vo.user.SocialUserPageReqVO; +import com.tashow.cloud.systemapi.enums.social.SocialTypeEnum; +import jakarta.validation.Valid; + +import java.util.List; + +/** + * 社交用户 Service 接口,例如说社交平台的授权登录 + * + * @author 芋道源码 + */ +public interface SocialUserService { + + /** + * 获得指定用户的社交用户列表 + * + * @param userId 用户编号 + * @param userType 用户类型 + * @return 社交用户列表 + */ + List getSocialUserList(Long userId, Integer userType); + + /** + * 绑定社交用户 + * + * @param reqDTO 绑定信息 + * @return 社交用户 openid + */ + String bindSocialUser(@Valid SocialUserBindReqDTO reqDTO); + + /** + * 取消绑定社交用户 + * + * @param userId 用户编号 + * @param userType 全局用户类型 + * @param socialType 社交平台的类型 {@link SocialTypeEnum} + * @param openid 社交平台的 openid + */ + void unbindSocialUser(Long userId, Integer userType, Integer socialType, String openid); + + /** + * 获得社交用户,基于 userId + * + * @param userType 用户类型 + * @param userId 用户编号 + * @param socialType 社交平台的类型 + * @return 社交用户 + */ + SocialUserRespDTO getSocialUserByUserId(Integer userType, Long userId, Integer socialType); + + /** + * 获得社交用户 + * + * 在认证信息不正确的情况下,也会抛出 {@link ServiceException} 业务异常 + * + * @param userType 用户类型 + * @param socialType 社交平台的类型 + * @param code 授权码 + * @param state state + * @return 社交用户 + */ + SocialUserRespDTO getSocialUserByCode(Integer userType, Integer socialType, String code, String state); + + // ==================== 社交用户 CRUD ==================== + + /** + * 获得社交用户 + * + * @param id 编号 + * @return 社交用户 + */ + SocialUserDO getSocialUser(Long id); + + /** + * 获得社交用户分页 + * + * @param pageReqVO 分页查询 + * @return 社交用户分页 + */ + PageResult getSocialUserPage(SocialUserPageReqVO pageReqVO); + +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/service/social/SocialUserServiceImpl.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/service/social/SocialUserServiceImpl.java new file mode 100644 index 0000000..0a6b468 --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/service/social/SocialUserServiceImpl.java @@ -0,0 +1,177 @@ +package com.tashow.cloud.system.service.social; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.lang.Assert; +import com.tashow.cloud.common.exception.ServiceException; +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.systemapi.api.social.dto.SocialUserBindReqDTO; +import com.tashow.cloud.systemapi.api.social.dto.SocialUserRespDTO; +import com.tashow.cloud.system.controller.admin.socail.vo.user.SocialUserPageReqVO; +import com.tashow.cloud.system.dal.dataobject.social.SocialUserBindDO; +import com.tashow.cloud.system.dal.dataobject.social.SocialUserDO; +import com.tashow.cloud.system.dal.mysql.social.SocialUserBindMapper; +import com.tashow.cloud.system.dal.mysql.social.SocialUserMapper; +import com.tashow.cloud.systemapi.enums.social.SocialTypeEnum; +import com.tashow.cloud.systemapi.api.social.dto.SocialUserBindReqDTO; +import com.tashow.cloud.systemapi.api.social.dto.SocialUserRespDTO; +import com.tashow.cloud.system.controller.admin.socail.vo.user.SocialUserPageReqVO; +import com.tashow.cloud.systemapi.enums.ErrorCodeConstants; +import com.tashow.cloud.systemapi.enums.social.SocialTypeEnum; +import com.xingyuv.jushauth.model.AuthUser; +import jakarta.annotation.Resource; +import jakarta.validation.constraints.NotNull; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.validation.annotation.Validated; + +import java.util.Collections; +import java.util.List; + +import static com.tashow.cloud.common.exception.util.ServiceExceptionUtil.exception; +import static com.tashow.cloud.common.util.collection.CollectionUtils.convertSet; +import static com.tashow.cloud.common.util.json.JsonUtils.toJsonString; + +/** + * 社交用户 Service 实现类 + * + * @author 芋道源码 + */ +@Service +@Validated +@Slf4j +public class SocialUserServiceImpl implements SocialUserService { + + @Resource + private SocialUserBindMapper socialUserBindMapper; + @Resource + private SocialUserMapper socialUserMapper; + + @Resource + private SocialClientService socialClientService; + + @Override + public List getSocialUserList(Long userId, Integer userType) { + // 获得绑定 + List socialUserBinds = socialUserBindMapper.selectListByUserIdAndUserType(userId, userType); + if (CollUtil.isEmpty(socialUserBinds)) { + return Collections.emptyList(); + } + // 获得社交用户 + return socialUserMapper.selectBatchIds(convertSet(socialUserBinds, SocialUserBindDO::getSocialUserId)); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public String bindSocialUser(SocialUserBindReqDTO reqDTO) { + // 获得社交用户 + SocialUserDO socialUser = authSocialUser(reqDTO.getSocialType(), reqDTO.getUserType(), + reqDTO.getCode(), reqDTO.getState()); + Assert.notNull(socialUser, "社交用户不能为空"); + + // 社交用户可能之前绑定过别的用户,需要进行解绑 + socialUserBindMapper.deleteByUserTypeAndSocialUserId(reqDTO.getUserType(), socialUser.getId()); + + // 用户可能之前已经绑定过该社交类型,需要进行解绑 + socialUserBindMapper.deleteByUserTypeAndUserIdAndSocialType(reqDTO.getUserType(), reqDTO.getUserId(), + socialUser.getType()); + + // 绑定当前登录的社交用户 + SocialUserBindDO socialUserBind = SocialUserBindDO.builder() + .userId(reqDTO.getUserId()).userType(reqDTO.getUserType()) + .socialUserId(socialUser.getId()).socialType(socialUser.getType()).build(); + socialUserBindMapper.insert(socialUserBind); + return socialUser.getOpenid(); + } + + @Override + public void unbindSocialUser(Long userId, Integer userType, Integer socialType, String openid) { + // 获得 openid 对应的 SocialUserDO 社交用户 + SocialUserDO socialUser = socialUserMapper.selectByTypeAndOpenid(socialType, openid); + if (socialUser == null) { + throw exception(ErrorCodeConstants.SOCIAL_USER_NOT_FOUND); + } + + // 获得对应的社交绑定关系 + socialUserBindMapper.deleteByUserTypeAndUserIdAndSocialType(userType, userId, socialUser.getType()); + } + + @Override + public SocialUserRespDTO getSocialUserByUserId(Integer userType, Long userId, Integer socialType) { + // 获得绑定用户 + SocialUserBindDO socialUserBind = socialUserBindMapper.selectByUserIdAndUserTypeAndSocialType(userId, userType, socialType); + if (socialUserBind == null) { + return null; + } + // 获得社交用户 + SocialUserDO socialUser = socialUserMapper.selectById(socialUserBind.getSocialUserId()); + Assert.notNull(socialUser, "社交用户不能为空"); + return new SocialUserRespDTO(socialUser.getOpenid(), socialUser.getNickname(), socialUser.getAvatar(), + socialUserBind.getUserId()); + } + + @Override + public SocialUserRespDTO getSocialUserByCode(Integer userType, Integer socialType, String code, String state) { + // 获得社交用户 + SocialUserDO socialUser = authSocialUser(socialType, userType, code, state); + Assert.notNull(socialUser, "社交用户不能为空"); + + // 获得绑定用户 + SocialUserBindDO socialUserBind = socialUserBindMapper.selectByUserTypeAndSocialUserId(userType, + socialUser.getId()); + return new SocialUserRespDTO(socialUser.getOpenid(), socialUser.getNickname(), socialUser.getAvatar(), + socialUserBind != null ? socialUserBind.getUserId() : null); + } + + /** + * 授权获得对应的社交用户 + * 如果授权失败,则会抛出 {@link ServiceException} 异常 + * + * @param socialType 社交平台的类型 {@link SocialTypeEnum} + * @param userType 用户类型 + * @param code 授权码 + * @param state state + * @return 授权用户 + */ + @NotNull + public SocialUserDO authSocialUser(Integer socialType, Integer userType, String code, String state) { + // 优先从 DB 中获取,因为 code 有且可以使用一次。 + // 在社交登录时,当未绑定 User 时,需要绑定登录,此时需要 code 使用两次 + SocialUserDO socialUser = socialUserMapper.selectByTypeAndCodeAnState(socialType, code, state); + if (socialUser != null) { + return socialUser; + } + + // 请求获取 + AuthUser authUser = socialClientService.getAuthUser(socialType, userType, code, state); + Assert.notNull(authUser, "三方用户不能为空"); + + // 保存到 DB 中 + socialUser = socialUserMapper.selectByTypeAndOpenid(socialType, authUser.getUuid()); + if (socialUser == null) { + socialUser = new SocialUserDO(); + } + socialUser.setType(socialType).setCode(code).setState(state) // 需要保存 code + state 字段,保证后续可查询 + .setOpenid(authUser.getUuid()).setToken(authUser.getToken().getAccessToken()).setRawTokenInfo((toJsonString(authUser.getToken()))) + .setNickname(authUser.getNickname()).setAvatar(authUser.getAvatar()).setRawUserInfo(toJsonString(authUser.getRawUserInfo())); + if (socialUser.getId() == null) { + socialUserMapper.insert(socialUser); + } else { + socialUserMapper.updateById(socialUser); + } + return socialUser; + } + + // ==================== 社交用户 CRUD ==================== + + @Override + public SocialUserDO getSocialUser(Long id) { + return socialUserMapper.selectById(id); + } + + @Override + public PageResult getSocialUserPage(SocialUserPageReqVO pageReqVO) { + return socialUserMapper.selectPage(pageReqVO); + } + +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/service/tenant/TenantPackageService.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/service/tenant/TenantPackageService.java new file mode 100644 index 0000000..32e5855 --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/service/tenant/TenantPackageService.java @@ -0,0 +1,74 @@ +package com.tashow.cloud.system.service.tenant; + +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.system.controller.admin.tenant.vo.packages.TenantPackagePageReqVO; +import com.tashow.cloud.system.controller.admin.tenant.vo.packages.TenantPackageSaveReqVO; +import com.tashow.cloud.system.dal.dataobject.tenant.TenantPackageDO; + +import com.tashow.cloud.system.controller.admin.tenant.vo.packages.TenantPackagePageReqVO; +import com.tashow.cloud.system.controller.admin.tenant.vo.packages.TenantPackageSaveReqVO; +import jakarta.validation.Valid; +import java.util.List; + +/** + * 租户套餐 Service 接口 + * + * @author 芋道源码 + */ +public interface TenantPackageService { + + /** + * 创建租户套餐 + * + * @param createReqVO 创建信息 + * @return 编号 + */ + Long createTenantPackage(@Valid TenantPackageSaveReqVO createReqVO); + + /** + * 更新租户套餐 + * + * @param updateReqVO 更新信息 + */ + void updateTenantPackage(@Valid TenantPackageSaveReqVO updateReqVO); + + /** + * 删除租户套餐 + * + * @param id 编号 + */ + void deleteTenantPackage(Long id); + + /** + * 获得租户套餐 + * + * @param id 编号 + * @return 租户套餐 + */ + TenantPackageDO getTenantPackage(Long id); + + /** + * 获得租户套餐分页 + * + * @param pageReqVO 分页查询 + * @return 租户套餐分页 + */ + PageResult getTenantPackagePage(TenantPackagePageReqVO pageReqVO); + + /** + * 校验租户套餐 + * + * @param id 编号 + * @return 租户套餐 + */ + TenantPackageDO validTenantPackage(Long id); + + /** + * 获得指定状态的租户套餐列表 + * + * @param status 状态 + * @return 租户套餐 + */ + List getTenantPackageListByStatus(Integer status); + +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/service/tenant/TenantPackageServiceImpl.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/service/tenant/TenantPackageServiceImpl.java new file mode 100644 index 0000000..227c65c --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/service/tenant/TenantPackageServiceImpl.java @@ -0,0 +1,142 @@ +package com.tashow.cloud.system.service.tenant; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.util.StrUtil; +import com.tashow.cloud.common.enums.CommonStatusEnum; +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.common.util.object.BeanUtils; +import com.tashow.cloud.system.controller.admin.tenant.vo.packages.TenantPackagePageReqVO; +import com.tashow.cloud.system.controller.admin.tenant.vo.packages.TenantPackageSaveReqVO; +import com.tashow.cloud.system.dal.dataobject.tenant.TenantDO; +import com.tashow.cloud.system.dal.dataobject.tenant.TenantPackageDO; +import com.tashow.cloud.system.dal.mysql.tenant.TenantPackageMapper; +import com.baomidou.dynamic.datasource.annotation.DSTransactional; +import com.google.common.annotations.VisibleForTesting; +import com.tashow.cloud.system.controller.admin.tenant.vo.packages.TenantPackagePageReqVO; +import com.tashow.cloud.system.controller.admin.tenant.vo.packages.TenantPackageSaveReqVO; +import com.tashow.cloud.systemapi.enums.ErrorCodeConstants; +import org.springframework.context.annotation.Lazy; +import org.springframework.stereotype.Service; +import org.springframework.validation.annotation.Validated; + +import jakarta.annotation.Resource; +import java.util.List; + +import static com.tashow.cloud.common.exception.util.ServiceExceptionUtil.exception; +import static com.tashow.cloud.systemapi.enums.ErrorCodeConstants.TENANT_PACKAGE_NAME_DUPLICATE; + +/** + * 租户套餐 Service 实现类 + * + * @author 芋道源码 + */ +@Service +@Validated +public class TenantPackageServiceImpl implements TenantPackageService { + + @Resource + private TenantPackageMapper tenantPackageMapper; + + @Resource + @Lazy // 避免循环依赖的报错 + private TenantService tenantService; + + @Override + public Long createTenantPackage(TenantPackageSaveReqVO createReqVO) { + // 校验套餐名是否重复 + validateTenantPackageNameUnique(null, createReqVO.getName()); + // 插入 + TenantPackageDO tenantPackage = BeanUtils.toBean(createReqVO, TenantPackageDO.class); + tenantPackageMapper.insert(tenantPackage); + // 返回 + return tenantPackage.getId(); + } + + @Override + @DSTransactional // 多数据源,使用 @DSTransactional 保证本地事务,以及数据源的切换 + public void updateTenantPackage(TenantPackageSaveReqVO updateReqVO) { + // 校验存在 + TenantPackageDO tenantPackage = validateTenantPackageExists(updateReqVO.getId()); + // 校验套餐名是否重复 + validateTenantPackageNameUnique(updateReqVO.getId(), updateReqVO.getName()); + // 更新 + TenantPackageDO updateObj = BeanUtils.toBean(updateReqVO, TenantPackageDO.class); + tenantPackageMapper.updateById(updateObj); + // 如果菜单发生变化,则修改每个租户的菜单 + if (!CollUtil.isEqualList(tenantPackage.getMenuIds(), updateReqVO.getMenuIds())) { + List tenants = tenantService.getTenantListByPackageId(tenantPackage.getId()); + tenants.forEach(tenant -> tenantService.updateTenantRoleMenu(tenant.getId(), updateReqVO.getMenuIds())); + } + } + + @Override + public void deleteTenantPackage(Long id) { + // 校验存在 + validateTenantPackageExists(id); + // 校验正在使用 + validateTenantUsed(id); + // 删除 + tenantPackageMapper.deleteById(id); + } + + private TenantPackageDO validateTenantPackageExists(Long id) { + TenantPackageDO tenantPackage = tenantPackageMapper.selectById(id); + if (tenantPackage == null) { + throw exception(ErrorCodeConstants.TENANT_PACKAGE_NOT_EXISTS); + } + return tenantPackage; + } + + private void validateTenantUsed(Long id) { + if (tenantService.getTenantCountByPackageId(id) > 0) { + throw exception(ErrorCodeConstants.TENANT_PACKAGE_USED); + } + } + + @Override + public TenantPackageDO getTenantPackage(Long id) { + return tenantPackageMapper.selectById(id); + } + + @Override + public PageResult getTenantPackagePage(TenantPackagePageReqVO pageReqVO) { + return tenantPackageMapper.selectPage(pageReqVO); + } + + @Override + public TenantPackageDO validTenantPackage(Long id) { + TenantPackageDO tenantPackage = tenantPackageMapper.selectById(id); + if (tenantPackage == null) { + throw exception(ErrorCodeConstants.TENANT_PACKAGE_NOT_EXISTS); + } + if (tenantPackage.getStatus().equals(CommonStatusEnum.DISABLE.getStatus())) { + throw exception(ErrorCodeConstants.TENANT_PACKAGE_DISABLE, tenantPackage.getName()); + } + return tenantPackage; + } + + @Override + public List getTenantPackageListByStatus(Integer status) { + return tenantPackageMapper.selectListByStatus(status); + } + + + @VisibleForTesting + void validateTenantPackageNameUnique(Long id, String name) { + if (StrUtil.isBlank(name)) { + return; + } + TenantPackageDO tenantPackage = tenantPackageMapper.selectByName(name); + if (tenantPackage == null) { + return; + } + // 如果 id 为空,说明不用比较是否为相同 id 的用户 + if (id == null) { + throw exception(ErrorCodeConstants.TENANT_PACKAGE_NAME_DUPLICATE); + } + if (!tenantPackage.getId().equals(id)) { + throw exception(ErrorCodeConstants.TENANT_PACKAGE_NAME_DUPLICATE); + } + } + +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/service/tenant/TenantService.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/service/tenant/TenantService.java new file mode 100644 index 0000000..353f988 --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/service/tenant/TenantService.java @@ -0,0 +1,130 @@ +package com.tashow.cloud.system.service.tenant; + +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.system.controller.admin.tenant.vo.tenant.TenantPageReqVO; +import com.tashow.cloud.system.controller.admin.tenant.vo.tenant.TenantSaveReqVO; +import com.tashow.cloud.system.dal.dataobject.tenant.TenantDO; +import com.tashow.cloud.system.service.tenant.handler.TenantInfoHandler; +import com.tashow.cloud.system.service.tenant.handler.TenantMenuHandler; + +import com.tashow.cloud.tenant.core.context.TenantContextHolder; +import jakarta.validation.Valid; +import java.util.List; +import java.util.Set; + +/** + * 租户 Service 接口 + * + * @author 芋道源码 + */ +public interface TenantService { + + /** + * 创建租户 + * + * @param createReqVO 创建信息 + * @return 编号 + */ + Long createTenant(@Valid TenantSaveReqVO createReqVO); + + /** + * 更新租户 + * + * @param updateReqVO 更新信息 + */ + void updateTenant(@Valid TenantSaveReqVO updateReqVO); + + /** + * 更新租户的角色菜单 + * + * @param tenantId 租户编号 + * @param menuIds 菜单编号数组 + */ + void updateTenantRoleMenu(Long tenantId, Set menuIds); + + /** + * 删除租户 + * + * @param id 编号 + */ + void deleteTenant(Long id); + + /** + * 获得租户 + * + * @param id 编号 + * @return 租户 + */ + TenantDO getTenant(Long id); + + /** + * 获得租户分页 + * + * @param pageReqVO 分页查询 + * @return 租户分页 + */ + PageResult getTenantPage(TenantPageReqVO pageReqVO); + + /** + * 获得名字对应的租户 + * + * @param name 租户名 + * @return 租户 + */ + TenantDO getTenantByName(String name); + + /** + * 获得域名对应的租户 + * + * @param website 域名 + * @return 租户 + */ + TenantDO getTenantByWebsite(String website); + + /** + * 获得使用指定套餐的租户数量 + * + * @param packageId 租户套餐编号 + * @return 租户数量 + */ + Long getTenantCountByPackageId(Long packageId); + + /** + * 获得使用指定套餐的租户数组 + * + * @param packageId 租户套餐编号 + * @return 租户数组 + */ + List getTenantListByPackageId(Long packageId); + + /** + * 进行租户的信息处理逻辑 + * 其中,租户编号从 {@link TenantContextHolder} 上下文中获取 + * + * @param handler 处理器 + */ + void handleTenantInfo(TenantInfoHandler handler); + + /** + * 进行租户的菜单处理逻辑 + * 其中,租户编号从 {@link TenantContextHolder} 上下文中获取 + * + * @param handler 处理器 + */ + void handleTenantMenu(TenantMenuHandler handler); + + /** + * 获得所有租户 + * + * @return 租户编号数组 + */ + List getTenantIdList(); + + /** + * 校验租户是否合法 + * + * @param id 租户编号 + */ + void validTenant(Long id); + +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/service/tenant/TenantServiceImpl.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/service/tenant/TenantServiceImpl.java new file mode 100644 index 0000000..5e76ff7 --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/service/tenant/TenantServiceImpl.java @@ -0,0 +1,306 @@ +package com.tashow.cloud.system.service.tenant; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.lang.Assert; +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.StrUtil; +import com.tashow.cloud.common.enums.CommonStatusEnum; +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.common.util.collection.CollectionUtils; +import com.tashow.cloud.common.util.date.DateUtils; +import com.tashow.cloud.common.util.object.BeanUtils; +import com.tashow.cloud.system.controller.admin.permission.vo.role.RoleSaveReqVO; +import com.tashow.cloud.system.controller.admin.tenant.vo.tenant.TenantPageReqVO; +import com.tashow.cloud.system.controller.admin.tenant.vo.tenant.TenantSaveReqVO; +import com.tashow.cloud.system.convert.tenant.TenantConvert; +import com.tashow.cloud.system.dal.dataobject.permission.MenuDO; +import com.tashow.cloud.system.dal.dataobject.permission.RoleDO; +import com.tashow.cloud.system.dal.dataobject.tenant.TenantDO; +import com.tashow.cloud.system.dal.dataobject.tenant.TenantPackageDO; +import com.tashow.cloud.system.dal.mysql.tenant.TenantMapper; +import com.tashow.cloud.systemapi.enums.permission.RoleCodeEnum; +import com.tashow.cloud.systemapi.enums.permission.RoleTypeEnum; +import com.tashow.cloud.system.service.permission.MenuService; +import com.tashow.cloud.system.service.permission.PermissionService; +import com.tashow.cloud.system.service.permission.RoleService; +import com.tashow.cloud.system.service.tenant.handler.TenantInfoHandler; +import com.tashow.cloud.system.service.tenant.handler.TenantMenuHandler; +import com.tashow.cloud.system.service.user.AdminUserService; +import com.baomidou.dynamic.datasource.annotation.DSTransactional; +import com.tashow.cloud.systemapi.enums.ErrorCodeConstants; +import com.tashow.cloud.tenant.config.TenantProperties; +import com.tashow.cloud.tenant.core.context.TenantContextHolder; +import com.tashow.cloud.tenant.core.util.TenantUtils; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Lazy; +import org.springframework.stereotype.Service; +import org.springframework.validation.annotation.Validated; + +import jakarta.annotation.Resource; +import java.util.List; +import java.util.Objects; +import java.util.Set; + +import static com.tashow.cloud.common.exception.util.ServiceExceptionUtil.exception; +import static java.util.Collections.singleton; + +/** + * 租户 Service 实现类 + * + * @author 芋道源码 + */ +@Service +@Validated +@Slf4j +public class TenantServiceImpl implements TenantService { + + @SuppressWarnings("SpringJavaAutowiredFieldsWarningInspection") + @Autowired(required = false) // 由于 yudao.tenant.enable 配置项,可以关闭多租户的功能,所以这里只能不强制注入 + private TenantProperties tenantProperties; + + @Resource + private TenantMapper tenantMapper; + + @Resource + private TenantPackageService tenantPackageService; + @Resource + @Lazy // 延迟,避免循环依赖报错 + private AdminUserService userService; + @Resource + private RoleService roleService; + @Resource + private MenuService menuService; + @Resource + private PermissionService permissionService; + + @Override + public List getTenantIdList() { + List tenants = tenantMapper.selectList(); + return CollectionUtils.convertList(tenants, TenantDO::getId); + } + + @Override + public void validTenant(Long id) { + TenantDO tenant = getTenant(id); + if (tenant == null) { + throw exception(ErrorCodeConstants.TENANT_NOT_EXISTS); + } + if (tenant.getStatus().equals(CommonStatusEnum.DISABLE.getStatus())) { + throw exception(ErrorCodeConstants.TENANT_DISABLE, tenant.getName()); + } + if (DateUtils.isExpired(tenant.getExpireTime())) { + throw exception(ErrorCodeConstants.TENANT_EXPIRE, tenant.getName()); + } + } + + @Override + @DSTransactional // 多数据源,使用 @DSTransactional 保证本地事务,以及数据源的切换 + public Long createTenant(TenantSaveReqVO createReqVO) { + // 校验租户名称是否重复 + validTenantNameDuplicate(createReqVO.getName(), null); + // 校验租户域名是否重复 + validTenantWebsiteDuplicate(createReqVO.getWebsite(), null); + // 校验套餐被禁用 + TenantPackageDO tenantPackage = tenantPackageService.validTenantPackage(createReqVO.getPackageId()); + + // 创建租户 + TenantDO tenant = BeanUtils.toBean(createReqVO, TenantDO.class); + tenantMapper.insert(tenant); + // 创建租户的管理员 + TenantUtils.execute(tenant.getId(), () -> { + // 创建角色 + Long roleId = createRole(tenantPackage); + // 创建用户,并分配角色 + Long userId = createUser(roleId, createReqVO); + // 修改租户的管理员 + tenantMapper.updateById(new TenantDO().setId(tenant.getId()).setContactUserId(userId)); + }); + return tenant.getId(); + } + + private Long createUser(Long roleId, TenantSaveReqVO createReqVO) { + // 创建用户 + Long userId = userService.createUser(TenantConvert.INSTANCE.convert02(createReqVO)); + // 分配角色 + permissionService.assignUserRole(userId, singleton(roleId)); + return userId; + } + + private Long createRole(TenantPackageDO tenantPackage) { + // 创建角色 + RoleSaveReqVO reqVO = new RoleSaveReqVO(); + reqVO.setName(RoleCodeEnum.TENANT_ADMIN.getName()).setCode(RoleCodeEnum.TENANT_ADMIN.getCode()) + .setSort(0).setRemark("系统自动生成"); + Long roleId = roleService.createRole(reqVO, RoleTypeEnum.SYSTEM.getType()); + // 分配权限 + permissionService.assignRoleMenu(roleId, tenantPackage.getMenuIds()); + return roleId; + } + + @Override + @DSTransactional // 多数据源,使用 @DSTransactional 保证本地事务,以及数据源的切换 + public void updateTenant(TenantSaveReqVO updateReqVO) { + // 校验存在 + TenantDO tenant = validateUpdateTenant(updateReqVO.getId()); + // 校验租户名称是否重复 + validTenantNameDuplicate(updateReqVO.getName(), updateReqVO.getId()); + // 校验租户域名是否重复 + validTenantWebsiteDuplicate(updateReqVO.getWebsite(), updateReqVO.getId()); + // 校验套餐被禁用 + TenantPackageDO tenantPackage = tenantPackageService.validTenantPackage(updateReqVO.getPackageId()); + + // 更新租户 + TenantDO updateObj = BeanUtils.toBean(updateReqVO, TenantDO.class); + tenantMapper.updateById(updateObj); + // 如果套餐发生变化,则修改其角色的权限 + if (ObjectUtil.notEqual(tenant.getPackageId(), updateReqVO.getPackageId())) { + updateTenantRoleMenu(tenant.getId(), tenantPackage.getMenuIds()); + } + } + + private void validTenantNameDuplicate(String name, Long id) { + TenantDO tenant = tenantMapper.selectByName(name); + if (tenant == null) { + return; + } + // 如果 id 为空,说明不用比较是否为相同名字的租户 + if (id == null) { + throw exception(ErrorCodeConstants.TENANT_NAME_DUPLICATE, name); + } + if (!tenant.getId().equals(id)) { + throw exception(ErrorCodeConstants.TENANT_NAME_DUPLICATE, name); + } + } + + private void validTenantWebsiteDuplicate(String website, Long id) { + if (StrUtil.isEmpty(website)) { + return; + } + TenantDO tenant = tenantMapper.selectByWebsite(website); + if (tenant == null) { + return; + } + // 如果 id 为空,说明不用比较是否为相同名字的租户 + if (id == null) { + throw exception(ErrorCodeConstants.TENANT_WEBSITE_DUPLICATE, website); + } + if (!tenant.getId().equals(id)) { + throw exception(ErrorCodeConstants.TENANT_WEBSITE_DUPLICATE, website); + } + } + + @Override + @DSTransactional + public void updateTenantRoleMenu(Long tenantId, Set menuIds) { + TenantUtils.execute(tenantId, () -> { + // 获得所有角色 + List roles = roleService.getRoleList(); + roles.forEach(role -> Assert.isTrue(tenantId.equals(role.getTenantId()), "角色({}/{}) 租户不匹配", + role.getId(), role.getTenantId(), tenantId)); // 兜底校验 + // 重新分配每个角色的权限 + roles.forEach(role -> { + // 如果是租户管理员,重新分配其权限为租户套餐的权限 + if (Objects.equals(role.getCode(), RoleCodeEnum.TENANT_ADMIN.getCode())) { + permissionService.assignRoleMenu(role.getId(), menuIds); + log.info("[updateTenantRoleMenu][租户管理员({}/{}) 的权限修改为({})]", role.getId(), role.getTenantId(), menuIds); + return; + } + // 如果是其他角色,则去掉超过套餐的权限 + Set roleMenuIds = permissionService.getRoleMenuListByRoleId(role.getId()); + roleMenuIds = CollUtil.intersectionDistinct(roleMenuIds, menuIds); + permissionService.assignRoleMenu(role.getId(), roleMenuIds); + log.info("[updateTenantRoleMenu][角色({}/{}) 的权限修改为({})]", role.getId(), role.getTenantId(), roleMenuIds); + }); + }); + } + + @Override + public void deleteTenant(Long id) { + // 校验存在 + validateUpdateTenant(id); + // 删除 + tenantMapper.deleteById(id); + } + + private TenantDO validateUpdateTenant(Long id) { + TenantDO tenant = tenantMapper.selectById(id); + if (tenant == null) { + throw exception(ErrorCodeConstants.TENANT_NOT_EXISTS); + } + // 内置租户,不允许删除 + if (isSystemTenant(tenant)) { + throw exception(ErrorCodeConstants.TENANT_CAN_NOT_UPDATE_SYSTEM); + } + return tenant; + } + + @Override + public TenantDO getTenant(Long id) { + return tenantMapper.selectById(id); + } + + @Override + public PageResult getTenantPage(TenantPageReqVO pageReqVO) { + return tenantMapper.selectPage(pageReqVO); + } + + @Override + public TenantDO getTenantByName(String name) { + return tenantMapper.selectByName(name); + } + + @Override + public TenantDO getTenantByWebsite(String website) { + return tenantMapper.selectByWebsite(website); + } + + @Override + public Long getTenantCountByPackageId(Long packageId) { + return tenantMapper.selectCountByPackageId(packageId); + } + + @Override + public List getTenantListByPackageId(Long packageId) { + return tenantMapper.selectListByPackageId(packageId); + } + + @Override + public void handleTenantInfo(TenantInfoHandler handler) { + // 如果禁用,则不执行逻辑 + if (isTenantDisable()) { + return; + } + // 获得租户 + TenantDO tenant = getTenant(TenantContextHolder.getRequiredTenantId()); + // 执行处理器 + handler.handle(tenant); + } + + @Override + public void handleTenantMenu(TenantMenuHandler handler) { + // 如果禁用,则不执行逻辑 + if (isTenantDisable()) { + return; + } + // 获得租户,然后获得菜单 + TenantDO tenant = getTenant(TenantContextHolder.getRequiredTenantId()); + Set menuIds; + if (isSystemTenant(tenant)) { // 系统租户,菜单是全量的 + menuIds = CollectionUtils.convertSet(menuService.getMenuList(), MenuDO::getId); + } else { + menuIds = tenantPackageService.getTenantPackage(tenant.getPackageId()).getMenuIds(); + } + // 执行处理器 + handler.handle(menuIds); + } + + private static boolean isSystemTenant(TenantDO tenant) { + return Objects.equals(tenant.getPackageId(), TenantDO.PACKAGE_ID_SYSTEM); + } + + private boolean isTenantDisable() { + return tenantProperties == null || Boolean.FALSE.equals(tenantProperties.getEnable()); + } + +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/service/tenant/handler/TenantInfoHandler.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/service/tenant/handler/TenantInfoHandler.java new file mode 100644 index 0000000..4d6c689 --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/service/tenant/handler/TenantInfoHandler.java @@ -0,0 +1,21 @@ +package com.tashow.cloud.system.service.tenant.handler; + +import com.tashow.cloud.system.dal.dataobject.tenant.TenantDO; + +/** + * 租户信息处理 + * 目的:尽量减少租户逻辑耦合到系统中 + * + * @author 芋道源码 + */ +public interface TenantInfoHandler { + + /** + * 基于传入的租户信息,进行相关逻辑的执行 + * 例如说,创建用户时,超过最大账户配额 + * + * @param tenant 租户信息 + */ + void handle(TenantDO tenant); + +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/service/tenant/handler/TenantMenuHandler.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/service/tenant/handler/TenantMenuHandler.java new file mode 100644 index 0000000..e06aad9 --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/service/tenant/handler/TenantMenuHandler.java @@ -0,0 +1,21 @@ +package com.tashow.cloud.system.service.tenant.handler; + +import java.util.Set; + +/** + * 租户菜单处理 + * 目的:尽量减少租户逻辑耦合到系统中 + * + * @author 芋道源码 + */ +public interface TenantMenuHandler { + + /** + * 基于传入的租户菜单【全】列表,进行相关逻辑的执行 + * 例如说,返回可分配菜单的时候,可以移除多余的 + * + * @param menuIds 菜单列表 + */ + void handle(Set menuIds); + +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/service/user/AdminUserService.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/service/user/AdminUserService.java new file mode 100644 index 0000000..3797715 --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/service/user/AdminUserService.java @@ -0,0 +1,226 @@ +package com.tashow.cloud.system.service.user; + +import cn.hutool.core.collection.CollUtil; +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.common.util.collection.CollectionUtils; +import com.tashow.cloud.system.controller.admin.auth.vo.AuthRegisterReqVO; +import com.tashow.cloud.system.controller.admin.user.vo.profile.UserProfileUpdatePasswordReqVO; +import com.tashow.cloud.system.controller.admin.user.vo.profile.UserProfileUpdateReqVO; +import com.tashow.cloud.system.controller.admin.user.vo.user.UserImportExcelVO; +import com.tashow.cloud.system.controller.admin.user.vo.user.UserImportRespVO; +import com.tashow.cloud.system.controller.admin.user.vo.user.UserPageReqVO; +import com.tashow.cloud.system.controller.admin.user.vo.user.UserSaveReqVO; +import com.tashow.cloud.system.dal.dataobject.user.AdminUserDO; +import com.tashow.cloud.system.controller.admin.auth.vo.AuthRegisterReqVO; +import com.tashow.cloud.system.controller.admin.user.vo.profile.UserProfileUpdatePasswordReqVO; +import com.tashow.cloud.system.controller.admin.user.vo.profile.UserProfileUpdateReqVO; +import com.tashow.cloud.system.controller.admin.user.vo.user.UserImportExcelVO; +import com.tashow.cloud.system.controller.admin.user.vo.user.UserImportRespVO; +import com.tashow.cloud.system.controller.admin.user.vo.user.UserPageReqVO; +import com.tashow.cloud.system.controller.admin.user.vo.user.UserSaveReqVO; +import jakarta.validation.Valid; + +import java.io.InputStream; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * 后台用户 Service 接口 + * + * @author 芋道源码 + */ +public interface AdminUserService { + + /** + * 创建用户 + * + * @param createReqVO 用户信息 + * @return 用户编号 + */ + Long createUser(@Valid UserSaveReqVO createReqVO); + + /** + * 注册用户 + * + * @param registerReqVO 用户信息 + * @return 用户编号 + */ + Long registerUser(@Valid AuthRegisterReqVO registerReqVO); + + /** + * 修改用户 + * + * @param updateReqVO 用户信息 + */ + void updateUser(@Valid UserSaveReqVO updateReqVO); + + /** + * 更新用户的最后登陆信息 + * + * @param id 用户编号 + * @param loginIp 登陆 IP + */ + void updateUserLogin(Long id, String loginIp); + + /** + * 修改用户个人信息 + * + * @param id 用户编号 + * @param reqVO 用户个人信息 + */ + void updateUserProfile(Long id, @Valid UserProfileUpdateReqVO reqVO); + + /** + * 修改用户个人密码 + * + * @param id 用户编号 + * @param reqVO 更新用户个人密码 + */ + void updateUserPassword(Long id, @Valid UserProfileUpdatePasswordReqVO reqVO); + + /** + * 更新用户头像 + * + * @param id 用户 id + * @param avatarFile 头像文件 + */ + String updateUserAvatar(Long id, InputStream avatarFile) throws Exception; + + /** + * 修改密码 + * + * @param id 用户编号 + * @param password 密码 + */ + void updateUserPassword(Long id, String password); + + /** + * 修改状态 + * + * @param id 用户编号 + * @param status 状态 + */ + void updateUserStatus(Long id, Integer status); + + /** + * 删除用户 + * + * @param id 用户编号 + */ + void deleteUser(Long id); + + /** + * 通过用户名查询用户 + * + * @param username 用户名 + * @return 用户对象信息 + */ + AdminUserDO getUserByUsername(String username); + + /** + * 通过手机号获取用户 + * + * @param mobile 手机号 + * @return 用户对象信息 + */ + AdminUserDO getUserByMobile(String mobile); + + /** + * 获得用户分页列表 + * + * @param reqVO 分页条件 + * @return 分页列表 + */ + PageResult getUserPage(UserPageReqVO reqVO); + + /** + * 通过用户 ID 查询用户 + * + * @param id 用户ID + * @return 用户对象信息 + */ + AdminUserDO getUser(Long id); + + /** + * 获得指定部门的用户数组 + * + * @param deptIds 部门数组 + * @return 用户数组 + */ + List getUserListByDeptIds(Collection deptIds); + + /** + * 获得指定岗位的用户数组 + * + * @param postIds 岗位数组 + * @return 用户数组 + */ + List getUserListByPostIds(Collection postIds); + + /** + * 获得用户列表 + * + * @param ids 用户编号数组 + * @return 用户列表 + */ + List getUserList(Collection ids); + + /** + * 校验用户们是否有效。如下情况,视为无效: + * 1. 用户编号不存在 + * 2. 用户被禁用 + * + * @param ids 用户编号数组 + */ + void validateUserList(Collection ids); + + /** + * 获得用户 Map + * + * @param ids 用户编号数组 + * @return 用户 Map + */ + default Map getUserMap(Collection ids) { + if (CollUtil.isEmpty(ids)) { + return new HashMap<>(); + } + return CollectionUtils.convertMap(getUserList(ids), AdminUserDO::getId); + } + + /** + * 获得用户列表,基于昵称模糊匹配 + * + * @param nickname 昵称 + * @return 用户列表 + */ + List getUserListByNickname(String nickname); + + /** + * 批量导入用户 + * + * @param importUsers 导入用户列表 + * @param isUpdateSupport 是否支持更新 + * @return 导入结果 + */ + UserImportRespVO importUserList(List importUsers, boolean isUpdateSupport); + + /** + * 获得指定状态的用户们 + * + * @param status 状态 + * @return 用户们 + */ + List getUserListByStatus(Integer status); + + /** + * 判断密码是否匹配 + * + * @param rawPassword 未加密的密码 + * @param encodedPassword 加密后的密码 + * @return 是否匹配 + */ + boolean isPasswordMatch(String rawPassword, String encodedPassword); + +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/service/user/AdminUserServiceImpl.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/service/user/AdminUserServiceImpl.java new file mode 100644 index 0000000..e54f141 --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/service/user/AdminUserServiceImpl.java @@ -0,0 +1,531 @@ +package com.tashow.cloud.system.service.user; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.collection.CollectionUtil; +import cn.hutool.core.io.IoUtil; +import cn.hutool.core.util.StrUtil; +import com.tashow.cloud.common.enums.CommonStatusEnum; +import com.tashow.cloud.common.exception.ServiceException; +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.common.util.collection.CollectionUtils; +import com.tashow.cloud.common.util.object.BeanUtils; +import com.tashow.cloud.common.util.validation.ValidationUtils; +import com.tashow.cloud.infraapi.api.config.ConfigApi; +import com.tashow.cloud.infraapi.api.file.FileApi; +import com.tashow.cloud.permission.core.util.DataPermissionUtils; +import com.tashow.cloud.system.controller.admin.auth.vo.AuthRegisterReqVO; +import com.tashow.cloud.system.controller.admin.user.vo.profile.UserProfileUpdatePasswordReqVO; +import com.tashow.cloud.system.controller.admin.user.vo.profile.UserProfileUpdateReqVO; +import com.tashow.cloud.system.controller.admin.user.vo.user.UserImportExcelVO; +import com.tashow.cloud.system.controller.admin.user.vo.user.UserImportRespVO; +import com.tashow.cloud.system.controller.admin.user.vo.user.UserPageReqVO; +import com.tashow.cloud.system.controller.admin.user.vo.user.UserSaveReqVO; +import com.tashow.cloud.system.dal.dataobject.dept.DeptDO; +import com.tashow.cloud.system.dal.dataobject.dept.UserPostDO; +import com.tashow.cloud.system.dal.dataobject.user.AdminUserDO; +import com.tashow.cloud.system.dal.mysql.dept.UserPostMapper; +import com.tashow.cloud.system.dal.mysql.user.AdminUserMapper; +import com.tashow.cloud.system.service.dept.DeptService; +import com.tashow.cloud.system.service.dept.PostService; +import com.tashow.cloud.system.service.permission.PermissionService; +import com.tashow.cloud.system.service.tenant.TenantService; +import com.google.common.annotations.VisibleForTesting; +import com.mzt.logapi.context.LogRecordContext; +import com.mzt.logapi.service.impl.DiffParseFunction; +import com.mzt.logapi.starter.annotation.LogRecord; +import com.tashow.cloud.systemapi.enums.ErrorCodeConstants; +import com.tashow.cloud.systemapi.enums.LogRecordConstants; +import jakarta.annotation.Resource; +import jakarta.validation.ConstraintViolationException; +import lombok.extern.slf4j.Slf4j; +import org.springframework.context.annotation.Lazy; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.io.InputStream; +import java.time.LocalDateTime; +import java.util.*; + +import static com.tashow.cloud.common.exception.util.ServiceExceptionUtil.exception; +import static com.tashow.cloud.common.util.collection.CollectionUtils.*; + +/** + * 后台用户 Service 实现类 + * + * @author 芋道源码 + */ +@Service("adminUserService") +@Slf4j +public class AdminUserServiceImpl implements AdminUserService { + + static final String USER_INIT_PASSWORD_KEY = "system.user.init-password"; + + @Resource + private AdminUserMapper userMapper; + + @Resource + private DeptService deptService; + @Resource + private PostService postService; + @Resource + private PermissionService permissionService; + @Resource + private PasswordEncoder passwordEncoder; + @Resource + @Lazy // 延迟,避免循环依赖报错 + private TenantService tenantService; + + @Resource + private UserPostMapper userPostMapper; + + @Resource + private FileApi fileApi; + @Resource + private ConfigApi configApi; + + @Override + @Transactional(rollbackFor = Exception.class) + @LogRecord(type = LogRecordConstants.SYSTEM_USER_TYPE, subType = LogRecordConstants.SYSTEM_USER_CREATE_SUB_TYPE, bizNo = "{{#user.id}}", + success = LogRecordConstants.SYSTEM_USER_CREATE_SUCCESS) + public Long createUser(UserSaveReqVO createReqVO) { + // 1.1 校验账户配合 + tenantService.handleTenantInfo(tenant -> { + long count = userMapper.selectCount(); + if (count >= tenant.getAccountCount()) { + throw exception(ErrorCodeConstants.USER_COUNT_MAX, tenant.getAccountCount()); + } + }); + // 1.2 校验正确性 + validateUserForCreateOrUpdate(null, createReqVO.getUsername(), + createReqVO.getMobile(), createReqVO.getEmail(), createReqVO.getDeptId(), createReqVO.getPostIds()); + // 2.1 插入用户 + AdminUserDO user = BeanUtils.toBean(createReqVO, AdminUserDO.class); + user.setStatus(CommonStatusEnum.ENABLE.getStatus()); // 默认开启 + user.setPassword(encodePassword(createReqVO.getPassword())); // 加密密码 + userMapper.insert(user); + // 2.2 插入关联岗位 + if (CollectionUtil.isNotEmpty(user.getPostIds())) { + userPostMapper.insertBatch(convertList(user.getPostIds(), + postId -> new UserPostDO().setUserId(user.getId()).setPostId(postId))); + } + + // 3. 记录操作日志上下文 + LogRecordContext.putVariable("user", user); + return user.getId(); + } + + @Override + public Long registerUser(AuthRegisterReqVO registerReqVO) { + // 1.1 校验账户配合 + tenantService.handleTenantInfo(tenant -> { + long count = userMapper.selectCount(); + if (count >= tenant.getAccountCount()) { + throw exception(ErrorCodeConstants.USER_COUNT_MAX, tenant.getAccountCount()); + } + }); + // 1.2 校验正确性 + validateUserForCreateOrUpdate(null, registerReqVO.getUsername(), null, null, null, null); + + // 2. 插入用户 + AdminUserDO user = BeanUtils.toBean(registerReqVO, AdminUserDO.class); + user.setStatus(CommonStatusEnum.ENABLE.getStatus()); // 默认开启 + user.setPassword(encodePassword(registerReqVO.getPassword())); // 加密密码 + userMapper.insert(user); + return user.getId(); + } + + @Override + @Transactional(rollbackFor = Exception.class) + @LogRecord(type = LogRecordConstants.SYSTEM_USER_TYPE, subType = LogRecordConstants.SYSTEM_USER_UPDATE_SUB_TYPE, bizNo = "{{#updateReqVO.id}}", + success = LogRecordConstants.SYSTEM_USER_UPDATE_SUCCESS) + public void updateUser(UserSaveReqVO updateReqVO) { + updateReqVO.setPassword(null); // 特殊:此处不更新密码 + // 1. 校验正确性 + AdminUserDO oldUser = validateUserForCreateOrUpdate(updateReqVO.getId(), updateReqVO.getUsername(), + updateReqVO.getMobile(), updateReqVO.getEmail(), updateReqVO.getDeptId(), updateReqVO.getPostIds()); + + // 2.1 更新用户 + AdminUserDO updateObj = BeanUtils.toBean(updateReqVO, AdminUserDO.class); + userMapper.updateById(updateObj); + // 2.2 更新岗位 + updateUserPost(updateReqVO, updateObj); + + // 3. 记录操作日志上下文 + LogRecordContext.putVariable(DiffParseFunction.OLD_OBJECT, BeanUtils.toBean(oldUser, UserSaveReqVO.class)); + LogRecordContext.putVariable("user", oldUser); + } + + private void updateUserPost(UserSaveReqVO reqVO, AdminUserDO updateObj) { + Long userId = reqVO.getId(); + Set dbPostIds = convertSet(userPostMapper.selectListByUserId(userId), UserPostDO::getPostId); + // 计算新增和删除的岗位编号 + Set postIds = CollUtil.emptyIfNull(updateObj.getPostIds()); + Collection createPostIds = CollUtil.subtract(postIds, dbPostIds); + Collection deletePostIds = CollUtil.subtract(dbPostIds, postIds); + // 执行新增和删除。对于已经授权的岗位,不用做任何处理 + if (!CollectionUtil.isEmpty(createPostIds)) { + userPostMapper.insertBatch(convertList(createPostIds, + postId -> new UserPostDO().setUserId(userId).setPostId(postId))); + } + if (!CollectionUtil.isEmpty(deletePostIds)) { + userPostMapper.deleteByUserIdAndPostId(userId, deletePostIds); + } + } + + @Override + public void updateUserLogin(Long id, String loginIp) { + userMapper.updateById(new AdminUserDO().setId(id).setLoginIp(loginIp).setLoginDate(LocalDateTime.now())); + } + + @Override + public void updateUserProfile(Long id, UserProfileUpdateReqVO reqVO) { + // 校验正确性 + validateUserExists(id); + validateEmailUnique(id, reqVO.getEmail()); + validateMobileUnique(id, reqVO.getMobile()); + // 执行更新 + userMapper.updateById(BeanUtils.toBean(reqVO, AdminUserDO.class).setId(id)); + } + + @Override + public void updateUserPassword(Long id, UserProfileUpdatePasswordReqVO reqVO) { + // 校验旧密码密码 + validateOldPassword(id, reqVO.getOldPassword()); + // 执行更新 + AdminUserDO updateObj = new AdminUserDO().setId(id); + updateObj.setPassword(encodePassword(reqVO.getNewPassword())); // 加密密码 + userMapper.updateById(updateObj); + } + + @Override + public String updateUserAvatar(Long id, InputStream avatarFile) { + validateUserExists(id); + // 存储文件 + String avatar = fileApi.createFile(IoUtil.readBytes(avatarFile)); + // 更新路径 + AdminUserDO sysUserDO = new AdminUserDO(); + sysUserDO.setId(id); + sysUserDO.setAvatar(avatar); + userMapper.updateById(sysUserDO); + return avatar; + } + + @Override + @LogRecord(type = LogRecordConstants.SYSTEM_USER_TYPE, subType = LogRecordConstants.SYSTEM_USER_UPDATE_PASSWORD_SUB_TYPE, bizNo = "{{#id}}", + success = LogRecordConstants.SYSTEM_USER_UPDATE_PASSWORD_SUCCESS) + public void updateUserPassword(Long id, String password) { + // 1. 校验用户存在 + AdminUserDO user = validateUserExists(id); + + // 2. 更新密码 + AdminUserDO updateObj = new AdminUserDO(); + updateObj.setId(id); + updateObj.setPassword(encodePassword(password)); // 加密密码 + userMapper.updateById(updateObj); + + // 3. 记录操作日志上下文 + LogRecordContext.putVariable("user", user); + LogRecordContext.putVariable("newPassword", updateObj.getPassword()); + } + + @Override + public void updateUserStatus(Long id, Integer status) { + // 校验用户存在 + validateUserExists(id); + // 更新状态 + AdminUserDO updateObj = new AdminUserDO(); + updateObj.setId(id); + updateObj.setStatus(status); + userMapper.updateById(updateObj); + } + + @Override + @Transactional(rollbackFor = Exception.class) + @LogRecord(type = LogRecordConstants.SYSTEM_USER_TYPE, subType = LogRecordConstants.SYSTEM_USER_DELETE_SUB_TYPE, bizNo = "{{#id}}", + success = LogRecordConstants.SYSTEM_USER_DELETE_SUCCESS) + public void deleteUser(Long id) { + // 1. 校验用户存在 + AdminUserDO user = validateUserExists(id); + + // 2.1 删除用户 + userMapper.deleteById(id); + // 2.2 删除用户关联数据 + permissionService.processUserDeleted(id); + // 2.2 删除用户岗位 + userPostMapper.deleteByUserId(id); + + // 3. 记录操作日志上下文 + LogRecordContext.putVariable("user", user); + } + + @Override + public AdminUserDO getUserByUsername(String username) { + return userMapper.selectByUsername(username); + } + + @Override + public AdminUserDO getUserByMobile(String mobile) { + return userMapper.selectByMobile(mobile); + } + + @Override + public PageResult getUserPage(UserPageReqVO reqVO) { + // 如果有角色编号,查询角色对应的用户编号 + Set userIds = reqVO.getRoleId() != null ? + permissionService.getUserRoleIdListByRoleId(singleton(reqVO.getRoleId())) : null; + + // 分页查询 + return userMapper.selectPage(reqVO, getDeptCondition(reqVO.getDeptId()), userIds); + } + + @Override + public AdminUserDO getUser(Long id) { + return userMapper.selectById(id); + } + + @Override + public List getUserListByDeptIds(Collection deptIds) { + if (CollUtil.isEmpty(deptIds)) { + return Collections.emptyList(); + } + return userMapper.selectListByDeptIds(deptIds); + } + + @Override + public List getUserListByPostIds(Collection postIds) { + if (CollUtil.isEmpty(postIds)) { + return Collections.emptyList(); + } + Set userIds = convertSet(userPostMapper.selectListByPostIds(postIds), UserPostDO::getUserId); + if (CollUtil.isEmpty(userIds)) { + return Collections.emptyList(); + } + return userMapper.selectBatchIds(userIds); + } + + @Override + public List getUserList(Collection ids) { + if (CollUtil.isEmpty(ids)) { + return Collections.emptyList(); + } + return userMapper.selectBatchIds(ids); + } + + @Override + public void validateUserList(Collection ids) { + if (CollUtil.isEmpty(ids)) { + return; + } + // 获得岗位信息 + List users = userMapper.selectBatchIds(ids); + Map userMap = CollectionUtils.convertMap(users, AdminUserDO::getId); + // 校验 + ids.forEach(id -> { + AdminUserDO user = userMap.get(id); + if (user == null) { + throw exception(ErrorCodeConstants.USER_NOT_EXISTS); + } + if (!CommonStatusEnum.ENABLE.getStatus().equals(user.getStatus())) { + throw exception(ErrorCodeConstants.USER_IS_DISABLE, user.getNickname()); + } + }); + } + + @Override + public List getUserListByNickname(String nickname) { + return userMapper.selectListByNickname(nickname); + } + + /** + * 获得部门条件:查询指定部门的子部门编号们,包括自身 + * + * @param deptId 部门编号 + * @return 部门编号集合 + */ + private Set getDeptCondition(Long deptId) { + if (deptId == null) { + return Collections.emptySet(); + } + Set deptIds = convertSet(deptService.getChildDeptList(deptId), DeptDO::getId); + deptIds.add(deptId); // 包括自身 + return deptIds; + } + + private AdminUserDO validateUserForCreateOrUpdate(Long id, String username, String mobile, String email, + Long deptId, Set postIds) { + // 关闭数据权限,避免因为没有数据权限,查询不到数据,进而导致唯一校验不正确 + return DataPermissionUtils.executeIgnore(() -> { + // 校验用户存在 + AdminUserDO user = validateUserExists(id); + // 校验用户名唯一 + validateUsernameUnique(id, username); + // 校验手机号唯一 + validateMobileUnique(id, mobile); + // 校验邮箱唯一 + validateEmailUnique(id, email); + // 校验部门处于开启状态 + deptService.validateDeptList(singleton(deptId)); + // 校验岗位处于开启状态 + postService.validatePostList(postIds); + return user; + }); + } + + @VisibleForTesting + AdminUserDO validateUserExists(Long id) { + if (id == null) { + return null; + } + AdminUserDO user = userMapper.selectById(id); + if (user == null) { + throw exception(ErrorCodeConstants.USER_NOT_EXISTS); + } + return user; + } + + @VisibleForTesting + void validateUsernameUnique(Long id, String username) { + if (StrUtil.isBlank(username)) { + return; + } + AdminUserDO user = userMapper.selectByUsername(username); + if (user == null) { + return; + } + // 如果 id 为空,说明不用比较是否为相同 id 的用户 + if (id == null) { + throw exception(ErrorCodeConstants.USER_USERNAME_EXISTS); + } + if (!user.getId().equals(id)) { + throw exception(ErrorCodeConstants.USER_USERNAME_EXISTS); + } + } + + @VisibleForTesting + void validateEmailUnique(Long id, String email) { + if (StrUtil.isBlank(email)) { + return; + } + AdminUserDO user = userMapper.selectByEmail(email); + if (user == null) { + return; + } + // 如果 id 为空,说明不用比较是否为相同 id 的用户 + if (id == null) { + throw exception(ErrorCodeConstants.USER_EMAIL_EXISTS); + } + if (!user.getId().equals(id)) { + throw exception(ErrorCodeConstants.USER_EMAIL_EXISTS); + } + } + + @VisibleForTesting + void validateMobileUnique(Long id, String mobile) { + if (StrUtil.isBlank(mobile)) { + return; + } + AdminUserDO user = userMapper.selectByMobile(mobile); + if (user == null) { + return; + } + // 如果 id 为空,说明不用比较是否为相同 id 的用户 + if (id == null) { + throw exception(ErrorCodeConstants.USER_MOBILE_EXISTS); + } + if (!user.getId().equals(id)) { + throw exception(ErrorCodeConstants.USER_MOBILE_EXISTS); + } + } + + /** + * 校验旧密码 + * @param id 用户 id + * @param oldPassword 旧密码 + */ + @VisibleForTesting + void validateOldPassword(Long id, String oldPassword) { + AdminUserDO user = userMapper.selectById(id); + if (user == null) { + throw exception(ErrorCodeConstants.USER_NOT_EXISTS); + } + if (!isPasswordMatch(oldPassword, user.getPassword())) { + throw exception(ErrorCodeConstants.USER_PASSWORD_FAILED); + } + } + + @Override + @Transactional(rollbackFor = Exception.class) // 添加事务,异常则回滚所有导入 + public UserImportRespVO importUserList(List importUsers, boolean isUpdateSupport) { + // 1.1 参数校验 + if (CollUtil.isEmpty(importUsers)) { + throw exception(ErrorCodeConstants.USER_IMPORT_LIST_IS_EMPTY); + } + // 1.2 初始化密码不能为空 + String initPassword = configApi.getConfigValueByKey(USER_INIT_PASSWORD_KEY).getCheckedData(); + if (StrUtil.isEmpty(initPassword)) { + throw exception(ErrorCodeConstants.USER_IMPORT_INIT_PASSWORD); + } + + // 2. 遍历,逐个创建 or 更新 + UserImportRespVO respVO = UserImportRespVO.builder().createUsernames(new ArrayList<>()) + .updateUsernames(new ArrayList<>()).failureUsernames(new LinkedHashMap<>()).build(); + importUsers.forEach(importUser -> { + // 2.1.1 校验字段是否符合要求 + try { + ValidationUtils.validate(BeanUtils.toBean(importUser, UserSaveReqVO.class).setPassword(initPassword)); + } catch (ConstraintViolationException ex){ + respVO.getFailureUsernames().put(importUser.getUsername(), ex.getMessage()); + return; + } + // 2.1.2 校验,判断是否有不符合的原因 + try { + validateUserForCreateOrUpdate(null, null, importUser.getMobile(), importUser.getEmail(), + importUser.getDeptId(), null); + } catch (ServiceException ex) { + respVO.getFailureUsernames().put(importUser.getUsername(), ex.getMessage()); + return; + } + + // 2.2.1 判断如果不存在,在进行插入 + AdminUserDO existUser = userMapper.selectByUsername(importUser.getUsername()); + if (existUser == null) { + userMapper.insert(BeanUtils.toBean(importUser, AdminUserDO.class) + .setPassword(encodePassword(initPassword)).setPostIds(new HashSet<>())); // 设置默认密码及空岗位编号数组 + respVO.getCreateUsernames().add(importUser.getUsername()); + return; + } + // 2.2.2 如果存在,判断是否允许更新 + if (!isUpdateSupport) { + respVO.getFailureUsernames().put(importUser.getUsername(), ErrorCodeConstants.USER_USERNAME_EXISTS.getMsg()); + return; + } + AdminUserDO updateUser = BeanUtils.toBean(importUser, AdminUserDO.class); + updateUser.setId(existUser.getId()); + userMapper.updateById(updateUser); + respVO.getUpdateUsernames().add(importUser.getUsername()); + }); + return respVO; + } + + @Override + public List getUserListByStatus(Integer status) { + return userMapper.selectListByStatus(status); + } + + @Override + public boolean isPasswordMatch(String rawPassword, String encodedPassword) { + return passwordEncoder.matches(rawPassword, encodedPassword); + } + + /** + * 对密码进行加密 + * + * @param password 密码 + * @return 加密后的密码 + */ + private String encodePassword(String password) { + return passwordEncoder.encode(password); + } + +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/util/oauth2/OAuth2Utils.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/util/oauth2/OAuth2Utils.java new file mode 100644 index 0000000..cf291ab --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/util/oauth2/OAuth2Utils.java @@ -0,0 +1,103 @@ +package com.tashow.cloud.system.util.oauth2; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.date.LocalDateTimeUtil; +import cn.hutool.core.util.StrUtil; +import com.tashow.cloud.common.util.http.HttpUtils; +import com.tashow.cloud.security.security.core.util.SecurityFrameworkUtils; + +import java.time.LocalDateTime; +import java.time.temporal.ChronoUnit; +import java.util.*; + +/** + * OAuth2 相关的工具类 + * + * @author 芋道源码 + */ +public class OAuth2Utils { + + /** + * 构建授权码模式下,重定向的 URI + * + * copy from Spring Security OAuth2 的 AuthorizationEndpoint 类的 getSuccessfulRedirect 方法 + * + * @param redirectUri 重定向 URI + * @param authorizationCode 授权码 + * @param state 状态 + * @return 授权码模式下的重定向 URI + */ + public static String buildAuthorizationCodeRedirectUri(String redirectUri, String authorizationCode, String state) { + Map query = new LinkedHashMap<>(); + query.put("code", authorizationCode); + if (state != null) { + query.put("state", state); + } + return HttpUtils.append(redirectUri, query, null, false); + } + + /** + * 构建简化模式下,重定向的 URI + * + * copy from Spring Security OAuth2 的 AuthorizationEndpoint 类的 appendAccessToken 方法 + * + * @param redirectUri 重定向 URI + * @param accessToken 访问令牌 + * @param state 状态 + * @param expireTime 过期时间 + * @param scopes 授权范围 + * @param additionalInformation 附加信息 + * @return 简化授权模式下的重定向 URI + */ + public static String buildImplicitRedirectUri(String redirectUri, String accessToken, String state, LocalDateTime expireTime, + Collection scopes, Map additionalInformation) { + Map vars = new LinkedHashMap(); + Map keys = new HashMap(); + vars.put("access_token", accessToken); + vars.put("token_type", SecurityFrameworkUtils.AUTHORIZATION_BEARER.toLowerCase()); + if (state != null) { + vars.put("state", state); + } + if (expireTime != null) { + vars.put("expires_in", getExpiresIn(expireTime)); + } + if (CollUtil.isNotEmpty(scopes)) { + vars.put("scope", buildScopeStr(scopes)); + } + if (CollUtil.isNotEmpty(additionalInformation)) { + for (String key : additionalInformation.keySet()) { + Object value = additionalInformation.get(key); + if (value != null) { + keys.put("extra_" + key, key); + vars.put("extra_" + key, value); + } + } + } + // Do not include the refresh token (even if there is one) + return HttpUtils.append(redirectUri, vars, keys, true); + } + + public static String buildUnsuccessfulRedirect(String redirectUri, String responseType, String state, + String error, String description) { + Map query = new LinkedHashMap(); + query.put("error", error); + query.put("error_description", description); + if (state != null) { + query.put("state", state); + } + return HttpUtils.append(redirectUri, query, null, !responseType.contains("code")); + } + + public static long getExpiresIn(LocalDateTime expireTime) { + return LocalDateTimeUtil.between(LocalDateTime.now(), expireTime, ChronoUnit.SECONDS); + } + + public static String buildScopeStr(Collection scopes) { + return CollUtil.join(scopes, " "); + } + + public static List buildScopes(String scope) { + return StrUtil.split(scope, ' '); + } + +} diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/util/package-info.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/util/package-info.java new file mode 100644 index 0000000..fd439a6 --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/util/package-info.java @@ -0,0 +1,4 @@ +/** + * 每个模块的 util 包,放专属当前模块的 Utils 工具类 + */ +package com.tashow.cloud.system.util; diff --git a/tashow-module/tashow-module-system/src/main/resources/META-INF/services/com.xingyuv.captcha.service.CaptchaCacheService b/tashow-module/tashow-module-system/src/main/resources/META-INF/services/com.xingyuv.captcha.service.CaptchaCacheService new file mode 100644 index 0000000..c6113e5 --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/resources/META-INF/services/com.xingyuv.captcha.service.CaptchaCacheService @@ -0,0 +1 @@ +com.tashow.cloud.system.framework.captcha.core.RedisCaptchaServiceImpl diff --git a/tashow-module/tashow-module-system/src/main/resources/application-local.yaml b/tashow-module/tashow-module-system/src/main/resources/application-local.yaml new file mode 100644 index 0000000..a4d2d57 --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/resources/application-local.yaml @@ -0,0 +1,15 @@ +spring: + cloud: + nacos: + server-addr: 43.139.42.137:8848 # Nacos 服务器地址 + username: nacos # Nacos 账号 + password: nacos # Nacos 密码 + discovery: # 【配置中心】配置项 + namespace: 16bd40df-7cc7-4c2c-82c2-6186ade7bb08 # 命名空间。这里使用 dev 开发环境 + group: DEFAULT_GROUP # 使用的 Nacos 配置分组,默认为 DEFAULT_GROUP + metadata: + version: 1.0.0 # 服务实例的版本号,可用于灰度发布 + config: # 【注册中心】配置项 + namespace: 16bd40df-7cc7-4c2c-82c2-6186ade7bb08 # 命名空间。这里使用 dev 开发环境 + group: DEFAULT_GROUP # 使用的 Nacos 配置分组,默认为 DEFAULT_GROUP + diff --git a/tashow-module/tashow-module-system/src/main/resources/application.yaml b/tashow-module/tashow-module-system/src/main/resources/application.yaml new file mode 100644 index 0000000..7634367 --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/resources/application.yaml @@ -0,0 +1,18 @@ +server: + port: 48081 +spring: + application: + name: system-server + profiles: + active: local + config: + import: + - optional:classpath:application-${spring.profiles.active}.yaml # 加载【本地】配置 + - optional:nacos:application.yaml # 加载【Nacos】通用的配置 + - optional:nacos:tenant.yaml # 加载【Nacos】通用的配置 + - optional:nacos:application-login.yaml # 加载登录配置 + - optional:nacos:${spring.application.name}-${spring.profiles.active}.yaml # 加载【Nacos】的配置 + + + + diff --git a/tashow-module/tashow-module-system/src/main/resources/images/jigsaw/original/bg1.png b/tashow-module/tashow-module-system/src/main/resources/images/jigsaw/original/bg1.png new file mode 100644 index 0000000..c481457 Binary files /dev/null and b/tashow-module/tashow-module-system/src/main/resources/images/jigsaw/original/bg1.png differ diff --git a/tashow-module/tashow-module-system/src/main/resources/images/jigsaw/original/bg2.png b/tashow-module/tashow-module-system/src/main/resources/images/jigsaw/original/bg2.png new file mode 100644 index 0000000..bf8fb38 Binary files /dev/null and b/tashow-module/tashow-module-system/src/main/resources/images/jigsaw/original/bg2.png differ diff --git a/tashow-module/tashow-module-system/src/main/resources/images/jigsaw/original/bg3.png b/tashow-module/tashow-module-system/src/main/resources/images/jigsaw/original/bg3.png new file mode 100644 index 0000000..f871d3d Binary files /dev/null and b/tashow-module/tashow-module-system/src/main/resources/images/jigsaw/original/bg3.png differ diff --git a/tashow-module/tashow-module-system/src/main/resources/images/jigsaw/original/bg4.png b/tashow-module/tashow-module-system/src/main/resources/images/jigsaw/original/bg4.png new file mode 100644 index 0000000..2e3d871 Binary files /dev/null and b/tashow-module/tashow-module-system/src/main/resources/images/jigsaw/original/bg4.png differ diff --git a/tashow-module/tashow-module-system/src/main/resources/images/jigsaw/original/bg5.png b/tashow-module/tashow-module-system/src/main/resources/images/jigsaw/original/bg5.png new file mode 100644 index 0000000..fe383b7 Binary files /dev/null and b/tashow-module/tashow-module-system/src/main/resources/images/jigsaw/original/bg5.png differ diff --git a/tashow-module/tashow-module-system/src/main/resources/images/jigsaw/original/bg6.png b/tashow-module/tashow-module-system/src/main/resources/images/jigsaw/original/bg6.png new file mode 100644 index 0000000..5024ceb Binary files /dev/null and b/tashow-module/tashow-module-system/src/main/resources/images/jigsaw/original/bg6.png differ diff --git a/tashow-module/tashow-module-system/src/main/resources/images/jigsaw/original/bg7.png b/tashow-module/tashow-module-system/src/main/resources/images/jigsaw/original/bg7.png new file mode 100644 index 0000000..efe76f8 Binary files /dev/null and b/tashow-module/tashow-module-system/src/main/resources/images/jigsaw/original/bg7.png differ diff --git a/tashow-module/tashow-module-system/src/main/resources/images/jigsaw/original/bg8.png b/tashow-module/tashow-module-system/src/main/resources/images/jigsaw/original/bg8.png new file mode 100644 index 0000000..2727aa3 Binary files /dev/null and b/tashow-module/tashow-module-system/src/main/resources/images/jigsaw/original/bg8.png differ diff --git a/tashow-module/tashow-module-system/src/main/resources/images/jigsaw/original/bg9.png b/tashow-module/tashow-module-system/src/main/resources/images/jigsaw/original/bg9.png new file mode 100644 index 0000000..4463aa2 Binary files /dev/null and b/tashow-module/tashow-module-system/src/main/resources/images/jigsaw/original/bg9.png differ diff --git a/tashow-module/tashow-module-system/src/main/resources/images/jigsaw/slidingBlock/1.png b/tashow-module/tashow-module-system/src/main/resources/images/jigsaw/slidingBlock/1.png new file mode 100644 index 0000000..ef11324 Binary files /dev/null and b/tashow-module/tashow-module-system/src/main/resources/images/jigsaw/slidingBlock/1.png differ diff --git a/tashow-module/tashow-module-system/src/main/resources/images/jigsaw/slidingBlock/11/10.png b/tashow-module/tashow-module-system/src/main/resources/images/jigsaw/slidingBlock/11/10.png new file mode 100644 index 0000000..297e44c Binary files /dev/null and b/tashow-module/tashow-module-system/src/main/resources/images/jigsaw/slidingBlock/11/10.png differ diff --git a/tashow-module/tashow-module-system/src/main/resources/images/jigsaw/slidingBlock/11/11.png b/tashow-module/tashow-module-system/src/main/resources/images/jigsaw/slidingBlock/11/11.png new file mode 100644 index 0000000..d9b1da8 Binary files /dev/null and b/tashow-module/tashow-module-system/src/main/resources/images/jigsaw/slidingBlock/11/11.png differ diff --git a/tashow-module/tashow-module-system/src/main/resources/images/jigsaw/slidingBlock/11/12.png b/tashow-module/tashow-module-system/src/main/resources/images/jigsaw/slidingBlock/11/12.png new file mode 100644 index 0000000..07e7313 Binary files /dev/null and b/tashow-module/tashow-module-system/src/main/resources/images/jigsaw/slidingBlock/11/12.png differ diff --git a/tashow-module/tashow-module-system/src/main/resources/images/jigsaw/slidingBlock/11/13.png b/tashow-module/tashow-module-system/src/main/resources/images/jigsaw/slidingBlock/11/13.png new file mode 100644 index 0000000..82c3dd9 Binary files /dev/null and b/tashow-module/tashow-module-system/src/main/resources/images/jigsaw/slidingBlock/11/13.png differ diff --git a/tashow-module/tashow-module-system/src/main/resources/images/jigsaw/slidingBlock/11/14.png b/tashow-module/tashow-module-system/src/main/resources/images/jigsaw/slidingBlock/11/14.png new file mode 100644 index 0000000..0b9a866 Binary files /dev/null and b/tashow-module/tashow-module-system/src/main/resources/images/jigsaw/slidingBlock/11/14.png differ diff --git a/tashow-module/tashow-module-system/src/main/resources/images/jigsaw/slidingBlock/11/15.png b/tashow-module/tashow-module-system/src/main/resources/images/jigsaw/slidingBlock/11/15.png new file mode 100644 index 0000000..86b0d1c Binary files /dev/null and b/tashow-module/tashow-module-system/src/main/resources/images/jigsaw/slidingBlock/11/15.png differ diff --git a/tashow-module/tashow-module-system/src/main/resources/images/jigsaw/slidingBlock/11/16.png b/tashow-module/tashow-module-system/src/main/resources/images/jigsaw/slidingBlock/11/16.png new file mode 100644 index 0000000..e90a6e2 Binary files /dev/null and b/tashow-module/tashow-module-system/src/main/resources/images/jigsaw/slidingBlock/11/16.png differ diff --git a/tashow-module/tashow-module-system/src/main/resources/images/jigsaw/slidingBlock/11/17.png b/tashow-module/tashow-module-system/src/main/resources/images/jigsaw/slidingBlock/11/17.png new file mode 100644 index 0000000..a82cbc7 Binary files /dev/null and b/tashow-module/tashow-module-system/src/main/resources/images/jigsaw/slidingBlock/11/17.png differ diff --git a/tashow-module/tashow-module-system/src/main/resources/images/jigsaw/slidingBlock/11/18.png b/tashow-module/tashow-module-system/src/main/resources/images/jigsaw/slidingBlock/11/18.png new file mode 100644 index 0000000..d3f3cfd Binary files /dev/null and b/tashow-module/tashow-module-system/src/main/resources/images/jigsaw/slidingBlock/11/18.png differ diff --git a/tashow-module/tashow-module-system/src/main/resources/images/jigsaw/slidingBlock/11/19.png b/tashow-module/tashow-module-system/src/main/resources/images/jigsaw/slidingBlock/11/19.png new file mode 100644 index 0000000..eb2855b Binary files /dev/null and b/tashow-module/tashow-module-system/src/main/resources/images/jigsaw/slidingBlock/11/19.png differ diff --git a/tashow-module/tashow-module-system/src/main/resources/images/jigsaw/slidingBlock/11/8.png b/tashow-module/tashow-module-system/src/main/resources/images/jigsaw/slidingBlock/11/8.png new file mode 100644 index 0000000..3cb5ce1 Binary files /dev/null and b/tashow-module/tashow-module-system/src/main/resources/images/jigsaw/slidingBlock/11/8.png differ diff --git a/tashow-module/tashow-module-system/src/main/resources/images/jigsaw/slidingBlock/11/9.png b/tashow-module/tashow-module-system/src/main/resources/images/jigsaw/slidingBlock/11/9.png new file mode 100644 index 0000000..384d354 Binary files /dev/null and b/tashow-module/tashow-module-system/src/main/resources/images/jigsaw/slidingBlock/11/9.png differ diff --git a/tashow-module/tashow-module-system/src/main/resources/images/jigsaw/slidingBlock/2.png b/tashow-module/tashow-module-system/src/main/resources/images/jigsaw/slidingBlock/2.png new file mode 100644 index 0000000..baf3f06 Binary files /dev/null and b/tashow-module/tashow-module-system/src/main/resources/images/jigsaw/slidingBlock/2.png differ diff --git a/tashow-module/tashow-module-system/src/main/resources/images/jigsaw/slidingBlock/3.png b/tashow-module/tashow-module-system/src/main/resources/images/jigsaw/slidingBlock/3.png new file mode 100644 index 0000000..ccaf617 Binary files /dev/null and b/tashow-module/tashow-module-system/src/main/resources/images/jigsaw/slidingBlock/3.png differ diff --git a/tashow-module/tashow-module-system/src/main/resources/images/jigsaw/slidingBlock/4.png b/tashow-module/tashow-module-system/src/main/resources/images/jigsaw/slidingBlock/4.png new file mode 100644 index 0000000..7dab162 Binary files /dev/null and b/tashow-module/tashow-module-system/src/main/resources/images/jigsaw/slidingBlock/4.png differ diff --git a/tashow-module/tashow-module-system/src/main/resources/images/pic-click/bg1.png b/tashow-module/tashow-module-system/src/main/resources/images/pic-click/bg1.png new file mode 100644 index 0000000..14e7345 Binary files /dev/null and b/tashow-module/tashow-module-system/src/main/resources/images/pic-click/bg1.png differ diff --git a/tashow-module/tashow-module-system/src/main/resources/images/pic-click/bg10.png b/tashow-module/tashow-module-system/src/main/resources/images/pic-click/bg10.png new file mode 100644 index 0000000..1ea1d6d Binary files /dev/null and b/tashow-module/tashow-module-system/src/main/resources/images/pic-click/bg10.png differ diff --git a/tashow-module/tashow-module-system/src/main/resources/images/pic-click/bg2.png b/tashow-module/tashow-module-system/src/main/resources/images/pic-click/bg2.png new file mode 100644 index 0000000..0edb329 Binary files /dev/null and b/tashow-module/tashow-module-system/src/main/resources/images/pic-click/bg2.png differ diff --git a/tashow-module/tashow-module-system/src/main/resources/images/pic-click/bg3.png b/tashow-module/tashow-module-system/src/main/resources/images/pic-click/bg3.png new file mode 100644 index 0000000..9167996 Binary files /dev/null and b/tashow-module/tashow-module-system/src/main/resources/images/pic-click/bg3.png differ diff --git a/tashow-module/tashow-module-system/src/main/resources/images/pic-click/bg4.png b/tashow-module/tashow-module-system/src/main/resources/images/pic-click/bg4.png new file mode 100644 index 0000000..e8e8e6c Binary files /dev/null and b/tashow-module/tashow-module-system/src/main/resources/images/pic-click/bg4.png differ diff --git a/tashow-module/tashow-module-system/src/main/resources/images/pic-click/bg5.png b/tashow-module/tashow-module-system/src/main/resources/images/pic-click/bg5.png new file mode 100644 index 0000000..66a3181 Binary files /dev/null and b/tashow-module/tashow-module-system/src/main/resources/images/pic-click/bg5.png differ diff --git a/tashow-module/tashow-module-system/src/main/resources/images/pic-click/bg6.png b/tashow-module/tashow-module-system/src/main/resources/images/pic-click/bg6.png new file mode 100644 index 0000000..9b0f5d8 Binary files /dev/null and b/tashow-module/tashow-module-system/src/main/resources/images/pic-click/bg6.png differ diff --git a/tashow-module/tashow-module-system/src/main/resources/images/pic-click/bg7.png b/tashow-module/tashow-module-system/src/main/resources/images/pic-click/bg7.png new file mode 100644 index 0000000..db41c74 Binary files /dev/null and b/tashow-module/tashow-module-system/src/main/resources/images/pic-click/bg7.png differ diff --git a/tashow-module/tashow-module-system/src/main/resources/images/pic-click/bg8.png b/tashow-module/tashow-module-system/src/main/resources/images/pic-click/bg8.png new file mode 100644 index 0000000..3496813 Binary files /dev/null and b/tashow-module/tashow-module-system/src/main/resources/images/pic-click/bg8.png differ diff --git a/tashow-module/tashow-module-system/src/main/resources/images/pic-click/bg9.png b/tashow-module/tashow-module-system/src/main/resources/images/pic-click/bg9.png new file mode 100644 index 0000000..4e7b477 Binary files /dev/null and b/tashow-module/tashow-module-system/src/main/resources/images/pic-click/bg9.png differ diff --git a/tashow-module/tashow-module-system/src/main/resources/logback-spring.xml b/tashow-module/tashow-module-system/src/main/resources/logback-spring.xml new file mode 100644 index 0000000..16f0c0f --- /dev/null +++ b/tashow-module/tashow-module-system/src/main/resources/logback-spring.xml @@ -0,0 +1,76 @@ + + + + + + + + + +       + + + ${PATTERN_DEFAULT} + + + + + + + + + + ${PATTERN_DEFAULT} + + + + ${LOG_FILE} + + + ${LOGBACK_ROLLINGPOLICY_FILE_NAME_PATTERN:-${LOG_FILE}.%d{yyyy-MM-dd}.%i.gz} + + ${LOGBACK_ROLLINGPOLICY_CLEAN_HISTORY_ON_START:-false} + + ${LOGBACK_ROLLINGPOLICY_MAX_FILE_SIZE:-10MB} + + ${LOGBACK_ROLLINGPOLICY_TOTAL_SIZE_CAP:-0} + + ${LOGBACK_ROLLINGPOLICY_MAX_HISTORY:-30} + + + + + + 0 + + 256 + + + + + + + + ${PATTERN_DEFAULT} + + + + + + + + + + + + + + + + + + + + + + diff --git a/tashow-sdk/pom.xml b/tashow-sdk/pom.xml new file mode 100644 index 0000000..58ee94c --- /dev/null +++ b/tashow-sdk/pom.xml @@ -0,0 +1,18 @@ + + 4.0.0 + + com.tashow.cloud + tashow-platform + ${revision} + + + tashow-sdk + pom + + + tashow-sdk-payment + tashow-feishu-sdk + + + diff --git a/tashow-sdk/tashow-feishu-sdk/.gitignore b/tashow-sdk/tashow-feishu-sdk/.gitignore new file mode 100644 index 0000000..7423f8b --- /dev/null +++ b/tashow-sdk/tashow-feishu-sdk/.gitignore @@ -0,0 +1,3 @@ +.idea/ +.DS_Store +src/main/java/com/tashow/cloud/sdk/feishu/client/chat_history.txt \ No newline at end of file diff --git a/tashow-sdk/tashow-feishu-sdk/pom.xml b/tashow-sdk/tashow-feishu-sdk/pom.xml new file mode 100644 index 0000000..097a044 --- /dev/null +++ b/tashow-sdk/tashow-feishu-sdk/pom.xml @@ -0,0 +1,44 @@ + + 4.0.0 + + com.tashow.cloud + tashow-sdk + ${revision} + + + tashow-feishu-sdk + jar + + + + + + com.tashow.cloud + tashow-data-redis + + + + com.larksuite.oapi + oapi-sdk + 2.4.18 + + + httpclient + org.apache.httpcomponents + 4.5.13 + + + + + junit + junit + test + 4.13.2 + + + com.tashow.cloud + tashow-common + + + diff --git a/tashow-sdk/tashow-feishu-sdk/src/main/java/com/tashow/cloud/sdk/feishu/client/FeiShuAlertClient.java b/tashow-sdk/tashow-feishu-sdk/src/main/java/com/tashow/cloud/sdk/feishu/client/FeiShuAlertClient.java new file mode 100644 index 0000000..642592b --- /dev/null +++ b/tashow-sdk/tashow-feishu-sdk/src/main/java/com/tashow/cloud/sdk/feishu/client/FeiShuAlertClient.java @@ -0,0 +1,164 @@ +package com.tashow.cloud.sdk.feishu.client; +import com.lark.oapi.Client; +import com.lark.oapi.service.im.v1.model.*; +import com.tashow.cloud.sdk.feishu.util.ChartImageGenerator; +import com.lark.oapi.service.im.v1.model.ext.MessageTemplate; +import com.lark.oapi.service.im.v1.model.ext.MessageTemplateData; +import java.io.File; +import java.util.*; +import com.tashow.cloud.sdk.feishu.config.LarkConfig; +import com.tashow.cloud.sdk.feishu.util.LarkClientUtil; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +/** + * 飞书告警客户端 + * 用于处理系统告警消息的发送 + */ +@Service +public class FeiShuAlertClient { + private final Client client; + private final LarkConfig larkConfig; + private final ChartImageGenerator chartImageGenerator; + + @Autowired + public FeiShuAlertClient(LarkClientUtil larkClientUtil, LarkConfig larkConfig, + ChartImageGenerator chartImageGenerator) { + this.client = larkClientUtil.getLarkClient(); + this.larkConfig = larkConfig; + this.chartImageGenerator = chartImageGenerator; + } + + /** + * 创建报警群并拉人入群 + * + * @return 创建的群聊ID + * @throws Exception 异常信息 + */ + public String createAlertChat() throws Exception { + CreateChatReq req = CreateChatReq.newBuilder() + .userIdType("open_id") + .createChatReqBody(CreateChatReqBody.newBuilder() + .name("[待处理] 线上事故处理") + .description("线上紧急事故处理") + .userIdList(larkConfig.getAlertUserOpenIds()) + .build()) + .build(); + CreateChatResp resp = client.im().chat().create(req); + if (!resp.success()) { + throw new Exception(String.format("client.im.chat.create failed, code: %d, msg: %s, logId: %s", + resp.getCode(), resp.getMsg(), resp.getRequestId())); + } + return resp.getData().getChatId(); + } + + + /** + * 发送报警消息 + * + * @param chatId 会话ID + * @param msgType 消息类型 + * @param content 消息内容 + * @return 消息ID + * @throws Exception 异常信息 + */ + public String sendMessage(String chatId, String msgType, String content) throws Exception { + CreateMessageReq req = CreateMessageReq.newBuilder() + .receiveIdType("chat_id") + .createMessageReqBody(CreateMessageReqBody.newBuilder() + .receiveId(chatId) + .msgType(msgType) + .content(content) + .build()) + .build(); + + CreateMessageResp resp = client.im().message().create(req); + if (!resp.success()) { + throw new Exception(String.format("client.im.message.create failed, code: %d, msg: %s, logId: %s", + resp.getCode(), resp.getMsg(), resp.getRequestId())); + } + + return resp.getData().getMessageId(); + } + + /** + * 更新卡片消息 + * + * @param messageId 消息ID + * @param content 新的卡片内容 + * @throws Exception 异常信息 + */ + public void updateCardMessage(String messageId, String content) throws Exception { + PatchMessageReq req = PatchMessageReq.newBuilder() + .messageId(messageId) + .patchMessageReqBody(PatchMessageReqBody.newBuilder() + .content(content) + .build()) + .build(); + PatchMessageResp resp = client.im().message().patch(req); + + if (!resp.success()) { + throw new Exception(String.format("client.im.message.patch failed, code: %d, msg: %s, logId: %s", + resp.getCode(), resp.getMsg(), resp.getRequestId())); + } + } + + /** + * 上传指定数据的监控图表(带错误信息) + * + * @param monitoringData 监控数据 + * @param errorMessage 错误信息 + * @return 上传后的图片KEY + * @throws Exception 异常信息 + */ + public String uploadImage(List monitoringData, String errorMessage) throws Exception { + // 动态生成监控图表 + File tempFile = File.createTempFile("alert", ".png"); + // 使用提供的数据生成图表 + chartImageGenerator.generateDashboardImage(tempFile, monitoringData, errorMessage); + CreateImageReq req = CreateImageReq.newBuilder() + .createImageReqBody(CreateImageReqBody.newBuilder() + .imageType("message") + .image(tempFile) + .build()) + .build(); + + CreateImageResp resp = client.im().image().create(req); + if (!resp.success()) { + throw new Exception(String.format("client.im.image.create failed, code: %d, msg: %s, logId: %s", + resp.getCode(), resp.getMsg(), resp.getRequestId())); + } + tempFile.delete(); + return resp.getData().getImageKey(); + } + + /** + * 使用模板数据构建卡片内容 + * + * @param templateId 卡片模板ID + * @param templateData 模板数据 + * @return 卡片JSON内容 + */ + public String buildCardWithData(String templateId, Map templateData) { + return new MessageTemplate.Builder() + .data(new MessageTemplateData.Builder().templateId(templateId) + .templateVariable(templateData) + .build()) + .build(); + } + + + + /** + * 发送卡片消息 + * + * @param chatId 会话ID + * @param templateId 卡片模板ID + * @param templateData 模板数据 + * @return 消息ID + * @throws Exception 异常信息 + */ + public String sendCardMessage(String chatId, String templateId, Map templateData) throws Exception { + String cardContent = buildCardWithData(templateId, templateData); + return sendMessage(chatId, "interactive", cardContent); + } +} diff --git a/tashow-sdk/tashow-feishu-sdk/src/main/java/com/tashow/cloud/sdk/feishu/client/FeiShuMessageClient.java b/tashow-sdk/tashow-feishu-sdk/src/main/java/com/tashow/cloud/sdk/feishu/client/FeiShuMessageClient.java new file mode 100644 index 0000000..a0084b1 --- /dev/null +++ b/tashow-sdk/tashow-feishu-sdk/src/main/java/com/tashow/cloud/sdk/feishu/client/FeiShuMessageClient.java @@ -0,0 +1,87 @@ +package com.tashow.cloud.sdk.feishu.client; +import com.lark.oapi.Client; +import com.lark.oapi.service.im.v1.model.*; +import com.tashow.cloud.sdk.feishu.util.LarkClientUtil; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import java.util.*; +import java.util.Arrays; + +/** + * 飞书普通消息客户端 + * 用于处理与警报无关的消息发送 + */ +@Service +public class FeiShuMessageClient { + private final Logger log = LoggerFactory.getLogger(FeiShuMessageClient.class); + private final Client client; + + + + @Autowired + public FeiShuMessageClient(LarkClientUtil larkClientUtil) { + this.client = larkClientUtil.getLarkClient(); + } + + /** + * 发送通用消息 + * @param chatId 会话ID + * @param msgType 消息类型 + * @param content 消息内容(不同消息类型有不同的格式要求) + * @return 发送结果,包含消息ID和是否成功 + * @throws Exception 异常信息 + */ + public Map sendMessage(String chatId, String msgType, String content) throws Exception { + CreateMessageReq req = CreateMessageReq.newBuilder() + .receiveIdType("chat_id") + .createMessageReqBody(CreateMessageReqBody.newBuilder() + .receiveId(chatId) + .msgType(msgType) + .content(content) + .build()) + .build(); + + CreateMessageResp resp = client.im().message().create(req); + Map result = new HashMap<>(); + result.put("success", resp.success()); + + if (resp.success()) { + result.put("messageId", resp.getData().getMessageId()); + } else { + result.put("errorCode", resp.getCode()); + result.put("errorMessage", resp.getMsg()); + result.put("requestId", resp.getRequestId()); + log.error("发送消息失败: 类型={}, 错误码={}, 错误信息={}", msgType, resp.getCode(), resp.getMsg()); + } + + return result; + } + + + + /** + * 获取会话历史消息 + * @param chatId 会话ID + * @return 消息列表 + * @throws Exception 异常信息 + */ + public List listChatHistory(String chatId) throws Exception { + ListMessageReq req = ListMessageReq.newBuilder() + .containerIdType("chat") + .containerId(chatId) + .build(); + + ListMessageResp resp = client.im().message().list(req); + + if (!resp.success()) { + throw new Exception(String.format("获取消息历史失败,错误码: %d, 错误信息: %s, 请求ID: %s", + resp.getCode(), resp.getMsg(), resp.getRequestId())); + } + + return Arrays.asList(resp.getData().getItems()); + } + + +} \ No newline at end of file diff --git a/tashow-sdk/tashow-feishu-sdk/src/main/java/com/tashow/cloud/sdk/feishu/config/LarkConfig.java b/tashow-sdk/tashow-feishu-sdk/src/main/java/com/tashow/cloud/sdk/feishu/config/LarkConfig.java new file mode 100644 index 0000000..d6b8559 --- /dev/null +++ b/tashow-sdk/tashow-feishu-sdk/src/main/java/com/tashow/cloud/sdk/feishu/config/LarkConfig.java @@ -0,0 +1,39 @@ +package com.tashow.cloud.sdk.feishu.config; + +import lombok.Data; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Configuration; +import org.springframework.stereotype.Component; + +/** + * 飞书配置类 + * 用于管理飞书应用的配置信息 + */ +@Component +@Data +public class LarkConfig { + + @Value("${lark.app.id}") + private String appId; + + @Value("${lark.app.secret}") + private String appSecret; + + @Value("${lark.app.encrypt-key}") + private String encryptKey; + + @Value("${lark.app.verification-token}") + private String verificationToken; + + @Value("${lark.alert.chat-id}") + private String chatId; + + @Value("${lark.alert.user-open-ids}") + private String[] alertUserOpenIds; + + @Value("${lark.alert.exception-card}") + private String exceptionCards; + + @Value("${lark.alert.success-card}") + private String successCards; +} \ No newline at end of file diff --git a/tashow-sdk/tashow-feishu-sdk/src/main/java/com/tashow/cloud/sdk/feishu/util/ChartImageGenerator.java b/tashow-sdk/tashow-feishu-sdk/src/main/java/com/tashow/cloud/sdk/feishu/util/ChartImageGenerator.java new file mode 100644 index 0000000..6183f49 --- /dev/null +++ b/tashow-sdk/tashow-feishu-sdk/src/main/java/com/tashow/cloud/sdk/feishu/util/ChartImageGenerator.java @@ -0,0 +1,324 @@ +package com.tashow.cloud.sdk.feishu.util; +import java.awt.BasicStroke; +import java.awt.Color; +import java.awt.Font; +import java.awt.FontMetrics; +import java.awt.Graphics2D; +import java.awt.RenderingHints; +import java.awt.image.BufferedImage; +import java.io.File; +import java.io.IOException; +import java.util.List; +import javax.imageio.ImageIO; +import org.springframework.stereotype.Component; + +/** + * 图表生成工具类 + * 用于生成监控数据图表 + */ +@Component +public class ChartImageGenerator { + + /** + * 监控数据点类 + */ + public static class MonitoringDataPoint { + private String timestamp; // 时间戳,格式如 "13:54" + private int successCount; // 成功数量 + private int failureCount; // 失败数量 + + public MonitoringDataPoint(String timestamp, int successCount, int failureCount) { + this.timestamp = timestamp; + this.successCount = successCount; + this.failureCount = failureCount; + } + + public String getTimestamp() { + return timestamp; + } + + public int getSuccessCount() { + return successCount; + } + + public int getFailureCount() { + return failureCount; + } + } + + /** + * 生成监控仪表盘图像 + * @param outputFile 输出文件 + * @param monitoringData 监控数据 + * @throws IOException 如果图像创建失败 + */ + public void generateDashboardImage(File outputFile, List monitoringData) throws IOException { + generateDashboardImage(outputFile, monitoringData, null); + } + + /** + * 生成监控仪表盘图像(带错误信息) + * @param outputFile 输出文件 + * @param monitoringData 监控数据 + * @param errorMessage 错误信息,如为null则不显示 + * @throws IOException 如果图像创建失败 + */ + public void generateDashboardImage(File outputFile, List monitoringData, String errorMessage) throws IOException { + int width = 850; + int height = 350; // 减小高度,原来是550 + int padding = 70; + int topPadding = 30; // 减少顶部空白,使用单独的顶部padding值 + + BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB); + Graphics2D g2d = image.createGraphics(); + + // 启用抗锯齿 + g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); + g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON); + g2d.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY); + + // 设置背景为白色 + g2d.setColor(Color.WHITE); + g2d.fillRect(0, 0, width, height); + + // 添加科技感背景网格 + drawTechBackground(g2d, width, height); + + // 计算图表区域 + int chartWidth = width - padding * 2; + int chartHeight = height - padding - topPadding; // 调整图表高度计算 + + // 绘制水平网格线 + Font labelFont = new Font("Microsoft YaHei", Font.PLAIN, 12); + g2d.setFont(labelFont); + FontMetrics metrics = g2d.getFontMetrics(labelFont); + + // 找出最大值以确定y轴的刻度 + int maxValue = 0; + for (MonitoringDataPoint point : monitoringData) { + maxValue = Math.max(maxValue, Math.max(point.getSuccessCount(), point.getFailureCount())); + } + + // 向上调整10%,确保有足够空间显示数据 + maxValue = (int)(maxValue * 1.1); + + // 如果最大值太小,设置一个最小值确保图表可读性 + if (maxValue < 10) { + maxValue = 10; + } + + // 向上取整到合适的刻度 + if (maxValue <= 100) { + // 小于100时,取整到10的倍数 + maxValue = ((maxValue + 9) / 10) * 10; + } else if (maxValue <= 1000) { + // 100-1000时,取整到50的倍数 + maxValue = ((maxValue + 49) / 50) * 50; + } else { + // 大于1000时,取整到100的倍数 + maxValue = ((maxValue + 99) / 100) * 100; + } + + // 动态计算y轴刻度 + int yDivisions = 5; // Y轴分段数 + int yStep = maxValue / yDivisions; + + // 绘制水平网格线 + for (int i = 0; i <= yDivisions; i++) { + int y = height - padding - (i * chartHeight / yDivisions); + + // 科技感网格线 + g2d.setColor(new Color(220, 220, 240, 100)); + g2d.setStroke(new BasicStroke(0.8f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_ROUND, 0, new float[]{3}, 0)); + g2d.drawLine(padding, y, width - padding, y); + + // 添加y轴标签 + String yLabel = String.format("%d", i * yStep); + int labelWidth = metrics.stringWidth(yLabel); + g2d.setColor(new Color(80, 80, 120)); + g2d.drawString(yLabel, padding - labelWidth - 10, y + metrics.getHeight() / 2 - 2); + } + + // 绘制垂直网格线和X轴标签 + int totalPoints = monitoringData.size(); + for (int i = 0; i < totalPoints; i++) { + int x = padding + (i * chartWidth / (totalPoints - 1)); + + // 科技感垂直网格线 + g2d.setColor(new Color(220, 220, 240, 100)); + g2d.setStroke(new BasicStroke(0.8f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_ROUND, 0, new float[]{3}, 0)); + g2d.drawLine(x, topPadding, x, height - padding); // 调整网格线顶部起点 + + // 添加每个点对应的时间标签 + if (i % 2 == 0 || i == totalPoints - 1) { // 每隔1个点显示标签,减少拥挤感 + String timeLabel = monitoringData.get(i).getTimestamp(); + int labelWidth = metrics.stringWidth(timeLabel); + g2d.setColor(new Color(80, 80, 120)); + g2d.drawString(timeLabel, x - labelWidth / 2, height - padding + 20); + } + } + + // 绘制成功线(荧光蓝色)- 使用带标签的方法替代原方法 + drawGlowingLineWithLabels(g2d, + calculateXPoints(totalPoints, padding, chartWidth), + calculateSuccessYPoints(monitoringData, totalPoints, height, padding, chartHeight, yDivisions, yStep), + new Color(0, 191, 255), new Color(0, 120, 215), 4.0f, + monitoringData, true); + + // 绘制失败线(荧光红色)- 使用带标签的方法替代原方法 + drawGlowingLineWithLabels(g2d, + calculateXPoints(totalPoints, padding, chartWidth), + calculateFailureYPoints(monitoringData, totalPoints, height, padding, chartHeight, yDivisions, yStep), + new Color(255, 50, 100), new Color(200, 30, 80), 4.0f, + monitoringData, false); + + // 绘制图表边框 + g2d.setColor(new Color(210, 210, 230)); + g2d.setStroke(new BasicStroke(1.5f)); + g2d.drawRect(padding, topPadding, chartWidth, chartHeight); // 调整边框位置 + + // 释放资源 + g2d.dispose(); + + // 在底部添加错误信息 + // 重新获取图像的Graphics2D对象 + g2d = image.createGraphics(); + g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); + g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON); + g2d.setColor(new Color(100, 100, 130)); + g2d.setFont(new Font("Microsoft YaHei", Font.PLAIN, 12)); + + // 使用动态传入的错误信息,而非硬编码 + if (errorMessage != null && !errorMessage.trim().isEmpty()) { + g2d.drawString(errorMessage, 70, height - 10); + } + + g2d.dispose(); + + // 保存图像 + ImageIO.write(image, "png", outputFile); + } + + /** + * 添加科技感背景 + */ + private void drawTechBackground(Graphics2D g2d, int width, int height) { + g2d.setColor(new Color(240, 240, 250, 120)); + g2d.setStroke(new BasicStroke(0.5f)); + + // 小网格 + int smallGridSize = 15; + for (int x = 0; x < width; x += smallGridSize) { + g2d.drawLine(x, 0, x, height); + } + for (int y = 0; y < height; y += smallGridSize) { + g2d.drawLine(0, y, width, y); + } + } + + /** + * 绘制发光线条 + */ + private void drawGlowingLine(Graphics2D g2d, int[] xPoints, int[] yPoints, Color mainColor, Color glowColor, float thickness) { + int totalPoints = xPoints.length; + + // 绘制发光效果(外层) + g2d.setColor(new Color(glowColor.getRed(), glowColor.getGreen(), glowColor.getBlue(), 80)); + g2d.setStroke(new BasicStroke(thickness + 4.0f, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND)); + for (int i = 0; i < totalPoints - 1; i++) { + g2d.drawLine(xPoints[i], yPoints[i], xPoints[i + 1], yPoints[i + 1]); + } + + // 绘制发光效果(中层) + g2d.setColor(new Color(glowColor.getRed(), glowColor.getGreen(), glowColor.getBlue(), 120)); + g2d.setStroke(new BasicStroke(thickness + 2.0f, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND)); + for (int i = 0; i < totalPoints - 1; i++) { + g2d.drawLine(xPoints[i], yPoints[i], xPoints[i + 1], yPoints[i + 1]); + } + + // 绘制主线 + g2d.setColor(mainColor); + g2d.setStroke(new BasicStroke(thickness, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND)); + for (int i = 0; i < totalPoints - 1; i++) { + g2d.drawLine(xPoints[i], yPoints[i], xPoints[i + 1], yPoints[i + 1]); + } + + // 绘制高亮数据点 + for (int i = 0; i < totalPoints; i++) { + // 外发光 + g2d.setColor(new Color(glowColor.getRed(), glowColor.getGreen(), glowColor.getBlue(), 80)); + g2d.fillOval(xPoints[i] - 6, yPoints[i] - 6, 12, 12); + + // 中发光 + g2d.setColor(new Color(mainColor.getRed(), mainColor.getGreen(), mainColor.getBlue(), 150)); + g2d.fillOval(xPoints[i] - 4, yPoints[i] - 4, 8, 8); + + // 内部点 + g2d.setColor(Color.WHITE); + g2d.fillOval(xPoints[i] - 2, yPoints[i] - 2, 4, 4); + } + } + + /** + * 绘制带有数值标签的发光线条 + */ + private void drawGlowingLineWithLabels(Graphics2D g2d, int[] xPoints, int[] yPoints, + Color mainColor, Color glowColor, float thickness, + List data, boolean isSuccess) { + // 先绘制基本的发光线条 + drawGlowingLine(g2d, xPoints, yPoints, mainColor, glowColor, thickness); + // 添加数值标签 - 使用普通字体而非粗体 + g2d.setFont(new Font("Microsoft YaHei", Font.PLAIN, 11)); + FontMetrics metrics = g2d.getFontMetrics(); + + for (int i = 0; i < xPoints.length; i++) { + // 获取当前值 + int currentValue = isSuccess ? data.get(i).getSuccessCount() : data.get(i).getFailureCount(); + + // 始终显示所有数据点的数值标签 + String label = String.valueOf(currentValue); + int labelWidth = metrics.stringWidth(label); + + // 设置标签文本 + g2d.setColor(mainColor.darker()); + g2d.drawString(label, xPoints[i] - labelWidth/2, yPoints[i] - 5); + } + } + + /** + * 计算X坐标点 + */ + private int[] calculateXPoints(int totalPoints, int padding, int chartWidth) { + int[] points = new int[totalPoints]; + for (int i = 0; i < totalPoints; i++) { + points[i] = padding + (i * chartWidth / (totalPoints - 1)); + } + return points; + } + + /** + * 计算成功线的Y坐标点 + */ + private int[] calculateSuccessYPoints(List data, int totalPoints, int height, + int padding, int chartHeight, int yDivisions, int yStep) { + int[] points = new int[totalPoints]; + for (int i = 0; i < totalPoints; i++) { + int successScaled = (int)((double)data.get(i).getSuccessCount() * chartHeight / (yDivisions * yStep)); + points[i] = height - padding - successScaled; + } + return points; + } + + /** + * 计算失败线的Y坐标点 + */ + private int[] calculateFailureYPoints(List data, int totalPoints, int height, + int padding, int chartHeight, int yDivisions, int yStep) { + int[] points = new int[totalPoints]; + for (int i = 0; i < totalPoints; i++) { + int failureScaled = (int)((double)data.get(i).getFailureCount() * chartHeight / (yDivisions * yStep)); + points[i] = height - padding - failureScaled; + } + return points; + } +} \ No newline at end of file diff --git a/tashow-sdk/tashow-feishu-sdk/src/main/java/com/tashow/cloud/sdk/feishu/util/LarkClientUtil.java b/tashow-sdk/tashow-feishu-sdk/src/main/java/com/tashow/cloud/sdk/feishu/util/LarkClientUtil.java new file mode 100644 index 0000000..5375137 --- /dev/null +++ b/tashow-sdk/tashow-feishu-sdk/src/main/java/com/tashow/cloud/sdk/feishu/util/LarkClientUtil.java @@ -0,0 +1,28 @@ +package com.tashow.cloud.sdk.feishu.util; +import com.lark.oapi.Client; +import com.tashow.cloud.sdk.feishu.config.LarkConfig; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +/** + * 飞书客户端工具类 + * 用于创建和获取飞书客户端实例 + */ +@Component +public class LarkClientUtil { + + private final LarkConfig larkConfig; + + @Autowired + public LarkClientUtil(LarkConfig larkConfig) { + this.larkConfig = larkConfig; + } + + /** + * 获取飞书客户端实例 + * @return 飞书客户端 + */ + public Client getLarkClient() { + return Client.newBuilder(larkConfig.getAppId(), larkConfig.getAppSecret()).build(); + } +} \ No newline at end of file diff --git a/tashow-sdk/tashow-feishu-sdk/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/tashow-sdk/tashow-feishu-sdk/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports new file mode 100644 index 0000000..0a69ff2 --- /dev/null +++ b/tashow-sdk/tashow-feishu-sdk/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -0,0 +1 @@ +com.tashow.cloud.sdk.feishu.config.LarkConfig diff --git a/tashow-sdk/tashow-feishu-sdk/src/main/resources/card.json b/tashow-sdk/tashow-feishu-sdk/src/main/resources/card.json new file mode 100644 index 0000000..327b1b8 --- /dev/null +++ b/tashow-sdk/tashow-feishu-sdk/src/main/resources/card.json @@ -0,0 +1,266 @@ +{ + "name": "12", + "dsl": { + "schema": "2.0", + "config": { + "update_multi": true, + "locales": [ + "en_us" + ], + "style": { + "text_size": { + "normal_v2": { + "default": "normal", + "pc": "normal", + "mobile": "heading" + } + } + } + }, + "body": { + "direction": "vertical", + "padding": "12px 12px 12px 12px", + "elements": [ + { + "tag": "column_set", + "horizontal_spacing": "8px", + "horizontal_align": "left", + "columns": [ + { + "tag": "column", + "width": "weighted", + "elements": [ + { + "tag": "markdown", + "content": "负责人\n", + "i18n_content": { + "en_us": "Alert details\nMobile client crash rate at 5%" + }, + "text_align": "left", + "text_size": "normal_v2", + "margin": "0px 0px 0px 0px", + "icon": { + "tag": "standard_icon", + "token": "contacts_outlined", + "color": "grey" + } + } + ], + "vertical_spacing": "8px", + "horizontal_align": "left", + "vertical_align": "top", + "weight": 1 + }, + { + "tag": "column", + "width": "weighted", + "elements": [ + { + "tag": "markdown", + "content": "失败数量\n${fail_count}", + "i18n_content": { + "en_us": "Diagnostic info\nService request volume exceeds rate limit" + }, + "text_align": "left", + "text_size": "normal_v2", + "margin": "0px 0px 0px 0px", + "icon": { + "tag": "standard_icon", + "token": "meego_colorful", + "color": "grey" + } + } + ], + "vertical_spacing": "8px", + "horizontal_align": "left", + "vertical_align": "top", + "weight": 1 + } + ], + "margin": "0px 0px 0px 0px" + }, + { + "tag": "column_set", + "horizontal_spacing": "8px", + "horizontal_align": "left", + "columns": [ + { + "tag": "column", + "width": "weighted", + "elements": [ + { + "tag": "markdown", + "content": "项目\nTashow平台", + "i18n_content": { + "en_us": "Priority level\nP0" + }, + "text_align": "left", + "text_size": "normal_v2", + "margin": "0px 0px 0px 0px", + "icon": { + "tag": "standard_icon", + "token": "file-form_colorful", + "color": "grey" + } + } + ], + "direction": "vertical", + "horizontal_spacing": "8px", + "vertical_spacing": "8px", + "horizontal_align": "left", + "vertical_align": "top", + "weight": 1 + }, + { + "tag": "column", + "width": "weighted", + "elements": [ + { + "tag": "markdown", + "content": "告警时间\n${current_time}", + "i18n_content": { + "en_us": "Incident time\n${alarm_time}" + }, + "text_align": "left", + "text_size": "normal_v2", + "margin": "0px 0px 0px 0px", + "icon": { + "tag": "standard_icon", + "token": "calendar_colorful", + "color": "grey" + } + } + ], + "direction": "vertical", + "horizontal_spacing": "8px", + "vertical_spacing": "8px", + "horizontal_align": "left", + "vertical_align": "top", + "weight": 1 + } + ], + "margin": "0px 0px 0px 0px" + }, + { + "tag": "form", + "elements": [ + { + "tag": "img", + "img_key": "img_v3_02nc_085db227-0547-40eb-90a1-dd80434b229g", + "preview": true, + "transparent": false, + "scale_type": "fit_horizontal", + "margin": "0px 0px 0px 0px" + }, + { + "tag": "input", + "placeholder": { + "tag": "plain_text", + "content": "处理情况说明,选填", + "i18n_content": { + "en_us": "Action taken (if any)" + } + }, + "default_value": "", + "width": "fill", + "name": "notes_input", + "margin": "0px 0px 0px 0px" + }, + { + "tag": "column_set", + "horizontal_align": "left", + "columns": [ + { + "tag": "column", + "width": "auto", + "elements": [ + { + "tag": "button", + "text": { + "tag": "plain_text", + "content": "处理完成", + "i18n_content": { + "en_us": "Mark as Resolved" + } + }, + "type": "primary", + "width": "default", + "behaviors": [ + { + "type": "callback", + "value": { + "action": "complete_alarm", + "time": "${alarm_time}" + } + } + ], + "form_action_type": "submit", + "name": "Button_m6vy7xom" + } + ], + "vertical_spacing": "8px", + "horizontal_align": "left", + "vertical_align": "top" + } + ], + "margin": "0px 0px 0px 0px" + } + ], + "direction": "vertical", + "padding": "4px 0px 4px 0px", + "margin": "0px 0px 0px 0px", + "name": "Form_m6vy7xol" + } + ] + }, + "header": { + "title": { + "tag": "plain_text", + "content": "${alert_title}", + "i18n_content": { + "en_us": "[Action Needed] Alert: Process Error - Please Address Promptly" + } + }, + "subtitle": { + "tag": "plain_text", + "content": "" + }, + "template": "red", + "icon": { + "tag": "standard_icon", + "token": "warning-hollow_filled" + }, + "padding": "12px 12px 12px 12px" + } + }, + "variables": [ + { + "type": "text", + "apiName": "var_m6vy7ngf", + "name": "alarm_time", + "desc": "告警时间", + "mockData": "2025-01-01 10:10:08" + }, + { + "type": "text", + "apiName": "var_mc1d8e1w", + "name": "fail_count", + "desc": "", + "mockData": "0" + }, + { + "type": "text", + "apiName": "var_mc1d8e1z", + "name": "current_time", + "desc": "", + "mockData": "2025-06-17 17:32:13" + }, + { + "type": "text", + "apiName": "var_mc1d8e6b", + "name": "alert_title", + "desc": "", + "mockData": "埋点数据异常告警" + } + ] +} \ No newline at end of file diff --git a/tashow-sdk/tashow-sdk-payment/pom.xml b/tashow-sdk/tashow-sdk-payment/pom.xml new file mode 100644 index 0000000..cd24efd --- /dev/null +++ b/tashow-sdk/tashow-sdk-payment/pom.xml @@ -0,0 +1,44 @@ + + 4.0.0 + + com.tashow.cloud + tashow-sdk + ${revision} + + + tashow-sdk-payment + jar + + + + com.tashow.cloud + tashow-common + + + + + com.alipay.sdk + alipay-sdk-java + 4.35.79.ALL + + + org.bouncycastle + bcprov-jdk15on + + + + + com.github.binarywang + weixin-java-pay + + + jakarta.validation + jakarta.validation-api + + + org.hibernate.validator + hibernate-validator + + + diff --git a/tashow-sdk/tashow-sdk-payment/src/main/java/com/tashow/cloud/sdk/payment/client/PayClient.java b/tashow-sdk/tashow-sdk-payment/src/main/java/com/tashow/cloud/sdk/payment/client/PayClient.java new file mode 100644 index 0000000..013c75e --- /dev/null +++ b/tashow-sdk/tashow-sdk-payment/src/main/java/com/tashow/cloud/sdk/payment/client/PayClient.java @@ -0,0 +1,111 @@ +package com.tashow.cloud.sdk.payment.client; + + +import com.tashow.cloud.sdk.payment.dto.order.PayOrderRespDTO; +import com.tashow.cloud.sdk.payment.dto.order.PayOrderUnifiedReqDTO; +import com.tashow.cloud.sdk.payment.dto.refund.PayRefundRespDTO; +import com.tashow.cloud.sdk.payment.dto.refund.PayRefundUnifiedReqDTO; +import com.tashow.cloud.sdk.payment.dto.transfer.PayTransferRespDTO; +import com.tashow.cloud.sdk.payment.dto.transfer.PayTransferUnifiedReqDTO; +import com.tashow.cloud.sdk.payment.enums.transfer.PayTransferTypeEnum; + +import java.util.Map; + +/** + * 支付客户端,用于对接各支付渠道的 SDK,实现发起支付、退款等功能 + * + * @author 芋道源码 + */ +public interface PayClient { + + /** + * 获得渠道编号 + * + * @return 渠道编号 + */ + Long getId(); + + // ============ 支付相关 ========== + + /** + * 调用支付渠道,统一下单 + * + * @param reqDTO 下单信息 + * @return 支付订单信息 + */ + PayOrderRespDTO unifiedOrder(PayOrderUnifiedReqDTO reqDTO); + + /** + * 解析 order 回调数据 + * + * @param params HTTP 回调接口 content type 为 application/x-www-form-urlencoded 的所有参数 + * @param body HTTP 回调接口的 request body + * @return 支付订单信息 + */ + PayOrderRespDTO parseOrderNotify(Map params, String body); + + /** + * 获得支付订单信息 + * + * @param outTradeNo 外部订单号 + * @return 支付订单信息 + */ + PayOrderRespDTO getOrder(String outTradeNo); + + // ============ 退款相关 ========== + + /** + * 调用支付渠道,进行退款 + * + * @param reqDTO 统一退款请求信息 + * @return 退款信息 + */ + PayRefundRespDTO unifiedRefund(PayRefundUnifiedReqDTO reqDTO); + + /** + * 解析 refund 回调数据 + * + * @param params HTTP 回调接口 content type 为 application/x-www-form-urlencoded 的所有参数 + * @param body HTTP 回调接口的 request body + * @return 支付订单信息 + */ + PayRefundRespDTO parseRefundNotify(Map params, String body); + + /** + * 获得退款订单信息 + * + * @param outTradeNo 外部订单号 + * @param outRefundNo 外部退款号 + * @return 退款订单信息 + */ + PayRefundRespDTO getRefund(String outTradeNo, String outRefundNo); + + // ============ 转账相关 ========== + + /** + * 调用渠道,进行转账 + * + * @param reqDTO 统一转账请求信息 + * @return 转账信息 + */ + PayTransferRespDTO unifiedTransfer(PayTransferUnifiedReqDTO reqDTO); + + /** + * 获得转账订单信息 + * + * @param outTradeNo 外部订单号 + * @param type 转账类型 + * @return 转账信息 + */ + PayTransferRespDTO getTransfer(String outTradeNo, PayTransferTypeEnum type); + + /** + * 解析 transfer 回调数据 + * + * @param params HTTP 回调接口 content type 为 application/x-www-form-urlencoded 的所有参数 + * @param body HTTP 回调接口的 request body + * @return 转账信息 + */ + PayTransferRespDTO parseTransferNotify(Map params, String body); + +} diff --git a/tashow-sdk/tashow-sdk-payment/src/main/java/com/tashow/cloud/sdk/payment/client/PayClientConfig.java b/tashow-sdk/tashow-sdk-payment/src/main/java/com/tashow/cloud/sdk/payment/client/PayClientConfig.java new file mode 100644 index 0000000..eabdfb3 --- /dev/null +++ b/tashow-sdk/tashow-sdk-payment/src/main/java/com/tashow/cloud/sdk/payment/client/PayClientConfig.java @@ -0,0 +1,25 @@ +package com.tashow.cloud.sdk.payment.client; + +import com.fasterxml.jackson.annotation.JsonTypeInfo; +import jakarta.validation.Validator; + +/** + * 支付客户端的配置,本质是支付渠道的配置 + * 每个不同的渠道,需要不同的配置,通过子类来定义 + * + * @author 芋道源码 + */ +@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS) +// @JsonTypeInfo 注解的作用,Jackson 多态 +// 1. 序列化到时数据库时,增加 @class 属性。 +// 2. 反序列化到内存对象时,通过 @class 属性,可以创建出正确的类型 +public interface PayClientConfig { + + /** + * 参数校验 + * + * @param validator 校验对象 + */ + void validate(Validator validator); + +} diff --git a/tashow-sdk/tashow-sdk-payment/src/main/java/com/tashow/cloud/sdk/payment/client/PayClientFactory.java b/tashow-sdk/tashow-sdk-payment/src/main/java/com/tashow/cloud/sdk/payment/client/PayClientFactory.java new file mode 100644 index 0000000..57a1093 --- /dev/null +++ b/tashow-sdk/tashow-sdk-payment/src/main/java/com/tashow/cloud/sdk/payment/client/PayClientFactory.java @@ -0,0 +1,38 @@ +package com.tashow.cloud.sdk.payment.client; + + +import com.tashow.cloud.sdk.payment.enums.channel.PayChannelEnum; + +/** + * 支付客户端的工厂接口 + * + * @author lwq + */ +public interface PayClientFactory { + + /** + * 获得支付客户端 + * @param channelId 渠道编号 + * @return 支付客户端 + */ + PayClient getPayClient(Long channelId); + + /** + * 创建支付客户端 + * + * @param channelId 渠道编号 + * @param channelCode 渠道编码 + * @param config 支付配置 + * @return 支付客户端 + */ + PayClient createOrUpdatePayClient(Long channelId, String channelCode, Config config); + + /** + * 注册支付客户端 Class,用于模块中实现的 PayClient + * + * @param channel 支付渠道的编码的枚举 + * @param payClientClass 支付客户端 class + */ + void registerPayClientClass(PayChannelEnum channel, Class payClientClass); + +} diff --git a/tashow-sdk/tashow-sdk-payment/src/main/java/com/tashow/cloud/sdk/payment/client/impl/AbstractPayClient.java b/tashow-sdk/tashow-sdk-payment/src/main/java/com/tashow/cloud/sdk/payment/client/impl/AbstractPayClient.java new file mode 100644 index 0000000..b002af0 --- /dev/null +++ b/tashow-sdk/tashow-sdk-payment/src/main/java/com/tashow/cloud/sdk/payment/client/impl/AbstractPayClient.java @@ -0,0 +1,267 @@ +package com.tashow.cloud.sdk.payment.client.impl; + +import com.tashow.cloud.common.exception.ServiceException; +import com.tashow.cloud.common.util.validation.ValidationUtils; +import com.tashow.cloud.sdk.payment.client.PayClient; +import com.tashow.cloud.sdk.payment.client.PayClientConfig; +import com.tashow.cloud.sdk.payment.dto.order.PayOrderRespDTO; +import com.tashow.cloud.sdk.payment.dto.order.PayOrderUnifiedReqDTO; +import com.tashow.cloud.sdk.payment.dto.refund.PayRefundRespDTO; +import com.tashow.cloud.sdk.payment.dto.refund.PayRefundUnifiedReqDTO; +import com.tashow.cloud.sdk.payment.dto.transfer.PayTransferRespDTO; +import com.tashow.cloud.sdk.payment.dto.transfer.PayTransferUnifiedReqDTO; +import com.tashow.cloud.sdk.payment.enums.transfer.PayTransferTypeEnum; +import com.tashow.cloud.sdk.payment.exception.PayException; +import lombok.extern.slf4j.Slf4j; + +import java.util.Map; + +import static com.tashow.cloud.common.exception.enums.GlobalErrorCodeConstants.NOT_IMPLEMENTED; +import static com.tashow.cloud.common.exception.util.ServiceExceptionUtil.exception; +import static com.tashow.cloud.common.util.json.JsonUtils.toJsonString; + + +/** + * 支付客户端的抽象类,提供模板方法,减少子类的冗余代码 + * + * @author 芋道源码 + */ +@Slf4j +public abstract class AbstractPayClient implements PayClient { + + /** + * 渠道编号 + */ + private final Long channelId; + /** + * 渠道编码 + */ + @SuppressWarnings("FieldCanBeLocal") + private final String channelCode; + /** + * 支付配置 + */ + protected Config config; + + public AbstractPayClient(Long channelId, String channelCode, Config config) { + this.channelId = channelId; + this.channelCode = channelCode; + this.config = config; + } + + /** + * 初始化 + */ + public final void init() { + doInit(); + log.debug("[init][客户端({}) 初始化完成]", getId()); + } + + /** + * 自定义初始化 + */ + protected abstract void doInit(); + + public final void refresh(Config config) { + // 判断是否更新 + if (config.equals(this.config)) { + return; + } + log.info("[refresh][客户端({})发生变化,重新初始化]", getId()); + this.config = config; + // 初始化 + this.init(); + } + + @Override + public Long getId() { + return channelId; + } + + // ============ 支付相关 ========== + + @Override + public final PayOrderRespDTO unifiedOrder(PayOrderUnifiedReqDTO reqDTO) { + ValidationUtils.validate(reqDTO); + // 执行统一下单 + PayOrderRespDTO resp; + try { + resp = doUnifiedOrder(reqDTO); + } catch (ServiceException ex) { // 业务异常,都是实现类已经翻译,所以直接抛出即可 + throw ex; + } catch (Throwable ex) { + // 系统异常,则包装成 PayException 异常抛出 + log.error("[unifiedOrder][客户端({}) request({}) 发起支付异常]", + getId(), toJsonString(reqDTO), ex); + throw buildPayException(ex); + } + return resp; + } + + protected abstract PayOrderRespDTO doUnifiedOrder(PayOrderUnifiedReqDTO reqDTO) + throws Throwable; + + @Override + public final PayOrderRespDTO parseOrderNotify(Map params, String body) { + try { + return doParseOrderNotify(params, body); + } catch (ServiceException ex) { // 业务异常,都是实现类已经翻译,所以直接抛出即可 + throw ex; + } catch (Throwable ex) { + log.error("[parseOrderNotify][客户端({}) params({}) body({}) 解析失败]", + getId(), params, body, ex); + throw buildPayException(ex); + } + } + + protected abstract PayOrderRespDTO doParseOrderNotify(Map params, String body) + throws Throwable; + + @Override + public final PayOrderRespDTO getOrder(String outTradeNo) { + try { + return doGetOrder(outTradeNo); + } catch (ServiceException ex) { // 业务异常,都是实现类已经翻译,所以直接抛出即可 + throw ex; + } catch (Throwable ex) { + log.error("[getOrder][客户端({}) outTradeNo({}) 查询支付单异常]", + getId(), outTradeNo, ex); + throw buildPayException(ex); + } + } + + protected abstract PayOrderRespDTO doGetOrder(String outTradeNo) + throws Throwable; + + // ============ 退款相关 ========== + + @Override + public final PayRefundRespDTO unifiedRefund(PayRefundUnifiedReqDTO reqDTO) { + ValidationUtils.validate(reqDTO); + // 执行统一退款 + PayRefundRespDTO resp; + try { + resp = doUnifiedRefund(reqDTO); + } catch (ServiceException ex) { // 业务异常,都是实现类已经翻译,所以直接抛出即可 + throw ex; + } catch (Throwable ex) { + // 系统异常,则包装成 PayException 异常抛出 + log.error("[unifiedRefund][客户端({}) request({}) 发起退款异常]", + getId(), toJsonString(reqDTO), ex); + throw buildPayException(ex); + } + return resp; + } + + protected abstract PayRefundRespDTO doUnifiedRefund(PayRefundUnifiedReqDTO reqDTO) throws Throwable; + + @Override + public final PayRefundRespDTO parseRefundNotify(Map params, String body) { + try { + return doParseRefundNotify(params, body); + } catch (ServiceException ex) { // 业务异常,都是实现类已经翻译,所以直接抛出即可 + throw ex; + } catch (Throwable ex) { + log.error("[parseRefundNotify][客户端({}) params({}) body({}) 解析失败]", + getId(), params, body, ex); + throw buildPayException(ex); + } + } + + protected abstract PayRefundRespDTO doParseRefundNotify(Map params, String body) + throws Throwable; + + @Override + public final PayRefundRespDTO getRefund(String outTradeNo, String outRefundNo) { + try { + return doGetRefund(outTradeNo, outRefundNo); + } catch (ServiceException ex) { // 业务异常,都是实现类已经翻译,所以直接抛出即可 + throw ex; + } catch (Throwable ex) { + log.error("[getRefund][客户端({}) outTradeNo({}) outRefundNo({}) 查询退款单异常]", + getId(), outTradeNo, outRefundNo, ex); + throw buildPayException(ex); + } + } + + protected abstract PayRefundRespDTO doGetRefund(String outTradeNo, String outRefundNo) + throws Throwable; + + @Override + public final PayTransferRespDTO unifiedTransfer(PayTransferUnifiedReqDTO reqDTO) { + validatePayTransferReqDTO(reqDTO); + PayTransferRespDTO resp; + try { + resp = doUnifiedTransfer(reqDTO); + } catch (ServiceException ex) { // 业务异常,都是实现类已经翻译,所以直接抛出即可 + throw ex; + } catch (Throwable ex) { + // 系统异常,则包装成 PayException 异常抛出 + log.error("[unifiedTransfer][客户端({}) request({}) 发起转账异常]", + getId(), toJsonString(reqDTO), ex); + throw buildPayException(ex); + } + return resp; + } + private void validatePayTransferReqDTO(PayTransferUnifiedReqDTO reqDTO) { + PayTransferTypeEnum transferType = PayTransferTypeEnum.typeOf(reqDTO.getType()); + switch (transferType) { + case ALIPAY_BALANCE: { + ValidationUtils.validate(reqDTO, PayTransferTypeEnum.Alipay.class); + break; + } + case WX_BALANCE: { + ValidationUtils.validate(reqDTO, PayTransferTypeEnum.WxPay.class); + break; + } + default: { + throw exception(NOT_IMPLEMENTED); + } + } + } + + @Override + public final PayTransferRespDTO parseTransferNotify(Map params, String body) { + try { + return doParseTransferNotify(params, body); + } catch (ServiceException ex) { // 业务异常,都是实现类已经翻译,所以直接抛出即可 + throw ex; + } catch (Throwable ex) { + log.error("[doParseTransferNotify][客户端({}) params({}) body({}) 解析失败]", + getId(), params, body, ex); + throw buildPayException(ex); + } + } + + protected abstract PayTransferRespDTO doParseTransferNotify(Map params, String body) + throws Throwable; + + @Override + public final PayTransferRespDTO getTransfer(String outTradeNo, PayTransferTypeEnum type) { + try { + return doGetTransfer(outTradeNo, type); + } catch (ServiceException ex) { // 业务异常,都是实现类已经翻译,所以直接抛出即可 + throw ex; + } catch (Throwable ex) { + log.error("[getTransfer][客户端({}) outTradeNo({}) type({}) 查询转账单异常]", + getId(), outTradeNo, type, ex); + throw buildPayException(ex); + } + } + + protected abstract PayTransferRespDTO doUnifiedTransfer(PayTransferUnifiedReqDTO reqDTO) + throws Throwable; + + protected abstract PayTransferRespDTO doGetTransfer(String outTradeNo, PayTransferTypeEnum type) + throws Throwable; + + // ========== 各种工具方法 ========== + + private PayException buildPayException(Throwable ex) { + if (ex instanceof PayException) { + return (PayException) ex; + } + throw new PayException(ex); + } + +} diff --git a/tashow-sdk/tashow-sdk-payment/src/main/java/com/tashow/cloud/sdk/payment/client/impl/PayClientFactoryImpl.java b/tashow-sdk/tashow-sdk-payment/src/main/java/com/tashow/cloud/sdk/payment/client/impl/PayClientFactoryImpl.java new file mode 100644 index 0000000..cf29aed --- /dev/null +++ b/tashow-sdk/tashow-sdk-payment/src/main/java/com/tashow/cloud/sdk/payment/client/impl/PayClientFactoryImpl.java @@ -0,0 +1,97 @@ +package com.tashow.cloud.sdk.payment.client.impl; + +import cn.hutool.core.lang.Assert; +import cn.hutool.core.util.ReflectUtil; +import com.tashow.cloud.sdk.payment.client.PayClient; +import com.tashow.cloud.sdk.payment.client.PayClientConfig; +import com.tashow.cloud.sdk.payment.client.PayClientFactory; +import com.tashow.cloud.sdk.payment.client.impl.alipay.*; +import com.tashow.cloud.sdk.payment.client.impl.weixin.*; +import com.tashow.cloud.sdk.payment.enums.channel.PayChannelEnum; +import lombok.extern.slf4j.Slf4j; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +import static com.tashow.cloud.sdk.payment.enums.channel.PayChannelEnum.*; + + +/** + * 支付客户端的工厂实现类 + * + * @author 芋道源码 + */ +@Slf4j +public class PayClientFactoryImpl implements PayClientFactory { + + /** + * 支付客户端 Map + * + * key:渠道编号 + */ + private final ConcurrentMap> clients = new ConcurrentHashMap<>(); + + /** + * 支付客户端 Class Map + */ + private final Map> clientClass = new ConcurrentHashMap<>(); + + public PayClientFactoryImpl() { + // 微信支付客户端 + clientClass.put(WX_PUB, WxPubPayClient.class); + clientClass.put(WX_LITE, WxLitePayClient.class); + clientClass.put(WX_APP, WxAppPayClient.class); + clientClass.put(WX_BAR, WxBarPayClient.class); + clientClass.put(WX_NATIVE, WxNativePayClient.class); + clientClass.put(WX_WAP, WxWapPayClient.class); + // 支付包支付客户端 + clientClass.put(ALIPAY_WAP, AlipayWapPayClient.class); + clientClass.put(ALIPAY_QR, AlipayQrPayClient.class); + clientClass.put(ALIPAY_APP, AlipayAppPayClient.class); + clientClass.put(ALIPAY_PC, AlipayPcPayClient.class); + clientClass.put(ALIPAY_BAR, AlipayBarPayClient.class); + // Mock 支付客户端 +// clientClass.put(MOCK, MockPayClient.class); + } + + @Override + public void registerPayClientClass(PayChannelEnum channel, Class payClientClass) { + clientClass.put(channel, payClientClass); + } + + @Override + public PayClient getPayClient(Long channelId) { + AbstractPayClient client = clients.get(channelId); + if (client == null) { + log.error("[getPayClient][渠道编号({}) 找不到客户端]", channelId); + } + return client; + } + + @Override + @SuppressWarnings("unchecked") + public PayClient createOrUpdatePayClient(Long channelId, String channelCode, + Config config) { + AbstractPayClient client = (AbstractPayClient) clients.get(channelId); + if (client == null) { + client = this.createPayClient(channelId, channelCode, config); + client.init(); + clients.put(client.getId(), client); + } else { + client.refresh(config); + } + return client; + } + + @SuppressWarnings("unchecked") + private AbstractPayClient createPayClient(Long channelId, String channelCode, + Config config) { + PayChannelEnum channelEnum = PayChannelEnum.getByCode(channelCode); + Assert.notNull(channelEnum, String.format("支付渠道(%s) 为空", channelCode)); + Class payClientClass = clientClass.get(channelEnum); + Assert.notNull(payClientClass, String.format("支付渠道(%s) Class 为空", channelCode)); + return (AbstractPayClient) ReflectUtil.newInstance(payClientClass, channelId, config); + } + +} diff --git a/tashow-sdk/tashow-sdk-payment/src/main/java/com/tashow/cloud/sdk/payment/client/impl/alipay/AbstractAlipayPayClient.java b/tashow-sdk/tashow-sdk-payment/src/main/java/com/tashow/cloud/sdk/payment/client/impl/alipay/AbstractAlipayPayClient.java new file mode 100644 index 0000000..75c0b9b --- /dev/null +++ b/tashow-sdk/tashow-sdk-payment/src/main/java/com/tashow/cloud/sdk/payment/client/impl/alipay/AbstractAlipayPayClient.java @@ -0,0 +1,348 @@ +package com.tashow.cloud.sdk.payment.client.impl.alipay; + +import cn.hutool.core.bean.BeanUtil; +import cn.hutool.core.date.LocalDateTimeUtil; +import cn.hutool.core.lang.Assert; +import cn.hutool.core.map.MapUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.http.HttpUtil; +import com.alipay.api.AlipayApiException; +import com.alipay.api.AlipayConfig; +import com.alipay.api.AlipayResponse; +import com.alipay.api.DefaultAlipayClient; +import com.alipay.api.domain.*; +import com.alipay.api.internal.util.AlipaySignature; +import com.alipay.api.request.*; +import com.alipay.api.response.*; +import com.tashow.cloud.common.util.json.JsonUtils; +import com.tashow.cloud.common.util.object.ObjectUtils; +import com.tashow.cloud.sdk.payment.client.impl.AbstractPayClient; +import com.tashow.cloud.sdk.payment.dto.order.PayOrderRespDTO; +import com.tashow.cloud.sdk.payment.dto.order.PayOrderUnifiedReqDTO; +import com.tashow.cloud.sdk.payment.dto.refund.PayRefundRespDTO; +import com.tashow.cloud.sdk.payment.dto.refund.PayRefundUnifiedReqDTO; +import com.tashow.cloud.sdk.payment.dto.transfer.PayTransferRespDTO; +import com.tashow.cloud.sdk.payment.dto.transfer.PayTransferUnifiedReqDTO; +import com.tashow.cloud.sdk.payment.enums.order.PayOrderStatusRespEnum; +import com.tashow.cloud.sdk.payment.enums.transfer.PayTransferTypeEnum; +import lombok.Getter; +import lombok.SneakyThrows; +import lombok.extern.slf4j.Slf4j; + +import java.nio.charset.StandardCharsets; +import java.time.LocalDateTime; +import java.util.Collections; +import java.util.Map; +import java.util.Objects; +import java.util.function.Supplier; + +import static cn.hutool.core.date.DatePattern.NORM_DATETIME_FORMATTER; +import static com.tashow.cloud.common.exception.enums.GlobalErrorCodeConstants.*; +import static com.tashow.cloud.common.exception.util.ServiceExceptionUtil.exception; +import static com.tashow.cloud.common.exception.util.ServiceExceptionUtil.exception0; +import static com.tashow.cloud.sdk.payment.client.impl.alipay.AlipayPayClientConfig.MODE_CERTIFICATE; + +/** + * 支付宝抽象类,实现支付宝统一的接口、以及部分实现(退款) + * + * @author jason + */ +@Slf4j +public abstract class AbstractAlipayPayClient extends AbstractPayClient { + + @Getter // 仅用于单测场景 + protected DefaultAlipayClient client; + + public AbstractAlipayPayClient(Long channelId, String channelCode, AlipayPayClientConfig config) { + super(channelId, channelCode, config); + } + + @Override + @SneakyThrows + protected void doInit() { + AlipayConfig alipayConfig = new AlipayConfig(); + BeanUtil.copyProperties(config, alipayConfig, false); + this.client = new DefaultAlipayClient(alipayConfig); + } + + // ============ 支付相关 ========== + + /** + * 构造支付关闭的 {@link PayOrderRespDTO} 对象 + * + * @return 支付关闭的 {@link PayOrderRespDTO} 对象 + */ + protected PayOrderRespDTO buildClosedPayOrderRespDTO(PayOrderUnifiedReqDTO reqDTO, AlipayResponse response) { + Assert.isFalse(response.isSuccess()); + return PayOrderRespDTO.closedOf(response.getSubCode(), response.getSubMsg(), + reqDTO.getOutTradeNo(), response); + } + + @Override + public PayOrderRespDTO doParseOrderNotify(Map params, String body) throws Throwable { + // 1. 校验回调数据 + Map bodyObj = HttpUtil.decodeParamMap(body, StandardCharsets.UTF_8); + AlipaySignature.rsaCheckV1(bodyObj, config.getAlipayPublicKey(), + StandardCharsets.UTF_8.name(), config.getSignType()); + + // 2. 解析订单的状态 + // 额外说明:支付宝不仅仅支付成功会回调,再各种触发支付单数据变化时,都会进行回调,所以这里 status 的解析会写的比较复杂 + Integer status = parseStatus(bodyObj.get("trade_status")); + // 特殊逻辑: 支付宝没有退款成功的状态,所以,如果有退款金额,我们认为是退款成功 + if (MapUtil.getDouble(bodyObj, "refund_fee", 0D) > 0) { + status = PayOrderStatusRespEnum.REFUND.getStatus(); + } + Assert.notNull(status, (Supplier) () -> { + throw new IllegalArgumentException(StrUtil.format("body({}) 的 trade_status 不正确", body)); + }); + return PayOrderRespDTO.of(status, bodyObj.get("trade_no"), bodyObj.get("seller_id"), parseTime(params.get("gmt_payment")), + bodyObj.get("out_trade_no"), body); + } + + @Override + protected PayOrderRespDTO doGetOrder(String outTradeNo) throws Throwable { + // 1.1 构建 AlipayTradeRefundModel 请求 + AlipayTradeQueryModel model = new AlipayTradeQueryModel(); + model.setOutTradeNo(outTradeNo); + // 1.2 构建 AlipayTradeQueryRequest 请求 + AlipayTradeQueryRequest request = new AlipayTradeQueryRequest(); + request.setBizModel(model); + AlipayTradeQueryResponse response; + if (Objects.equals(config.getMode(), MODE_CERTIFICATE)) { + // 证书模式 + response = client.certificateExecute(request); + } else { + response = client.execute(request); + } + if (!response.isSuccess()) { // 不成功,例如说订单不存在 + return PayOrderRespDTO.closedOf(response.getSubCode(), response.getSubMsg(), + outTradeNo, response); + } + // 2.2 解析订单的状态 + Integer status = parseStatus(response.getTradeStatus()); + Assert.notNull(status, () -> { + throw new IllegalArgumentException(StrUtil.format("body({}) 的 trade_status 不正确", response.getBody())); + }); + return PayOrderRespDTO.of(status, response.getTradeNo(), response.getBuyerUserId(), LocalDateTimeUtil.of(response.getSendPayDate()), + outTradeNo, response); + } + + private static Integer parseStatus(String tradeStatus) { + return Objects.equals("WAIT_BUYER_PAY", tradeStatus) ? PayOrderStatusRespEnum.WAITING.getStatus() + : ObjectUtils.equalsAny(tradeStatus, "TRADE_FINISHED", "TRADE_SUCCESS") ? PayOrderStatusRespEnum.SUCCESS.getStatus() + : Objects.equals("TRADE_CLOSED", tradeStatus) ? PayOrderStatusRespEnum.CLOSED.getStatus() : null; + } + + // ============ 退款相关 ========== + + /** + * 支付宝统一的退款接口 alipay.trade.refund + * + * @param reqDTO 退款请求 request DTO + * @return 退款请求 Response + */ + @Override + protected PayRefundRespDTO doUnifiedRefund(PayRefundUnifiedReqDTO reqDTO) throws AlipayApiException { + // 1.1 构建 AlipayTradeRefundModel 请求 + AlipayTradeRefundModel model = new AlipayTradeRefundModel(); + model.setOutTradeNo(reqDTO.getOutTradeNo()); + model.setOutRequestNo(reqDTO.getOutRefundNo()); + model.setRefundAmount(formatAmount(reqDTO.getRefundPrice())); + model.setRefundReason(reqDTO.getReason()); + // 1.2 构建 AlipayTradePayRequest 请求 + AlipayTradeRefundRequest request = new AlipayTradeRefundRequest(); + request.setBizModel(model); + + // 2.1 执行请求 + AlipayTradeRefundResponse response; + if (Objects.equals(config.getMode(), MODE_CERTIFICATE)) { // 证书模式 + response = client.certificateExecute(request); + } else { + response = client.execute(request); + } + if (!response.isSuccess()) { + // 当出现 ACQ.SYSTEM_ERROR, 退款可能成功也可能失败。 返回 WAIT 状态. 后续 job 会轮询 + if (ObjectUtils.equalsAny(response.getSubCode(), "ACQ.SYSTEM_ERROR", "SYSTEM_ERROR")) { + return PayRefundRespDTO.waitingOf(null, reqDTO.getOutRefundNo(), response); + } + return PayRefundRespDTO.failureOf(response.getSubCode(), response.getSubMsg(), reqDTO.getOutRefundNo(), response); + } + // 2.2 创建返回结果 + // 支付宝只要退款调用返回 success,就认为退款成功,不需要回调。具体可见 parseNotify 方法的说明。 + // 另外,支付宝没有退款单号,所以不用设置 + return PayRefundRespDTO.successOf(null, LocalDateTimeUtil.of(response.getGmtRefundPay()), + reqDTO.getOutRefundNo(), response); + } + + @Override + public PayRefundRespDTO doParseRefundNotify(Map params, String body) { + // 补充说明:支付宝退款时,没有回调,这点和微信支付是不同的。并且,退款分成部分退款、和全部退款。 + // ① 部分退款:是会有回调,但是它回调的是订单状态的同步回调,不是退款订单的回调 + // ② 全部退款:Wap 支付有订单状态的同步回调,但是 PC/扫码又没有 + // 所以,这里在解析时,即使是退款导致的订单状态同步,我们也忽略不做为“退款同步”,而是订单的回调。 + // 实际上,支付宝退款只要发起成功,就可以认为退款成功,不需要等待回调。 + throw new UnsupportedOperationException("支付宝无退款回调"); + } + + @Override + protected PayRefundRespDTO doGetRefund(String outTradeNo, String outRefundNo) throws AlipayApiException { + // 1.1 构建 AlipayTradeFastpayRefundQueryModel 请求 + AlipayTradeFastpayRefundQueryModel model = new AlipayTradeFastpayRefundQueryModel(); + model.setOutTradeNo(outTradeNo); + model.setOutRequestNo(outRefundNo); + model.setQueryOptions(Collections.singletonList("gmt_refund_pay")); + // 1.2 构建 AlipayTradeFastpayRefundQueryRequest 请求 + AlipayTradeFastpayRefundQueryRequest request = new AlipayTradeFastpayRefundQueryRequest(); + request.setBizModel(model); + + // 2.1 执行请求 + AlipayTradeFastpayRefundQueryResponse response; + if (Objects.equals(config.getMode(), MODE_CERTIFICATE)) { // 证书模式 + response = client.certificateExecute(request); + } else { + response = client.execute(request); + } + if (!response.isSuccess()) { + // 明确不存在的情况,应该就是失败,可进行关闭 + if (ObjectUtils.equalsAny(response.getSubCode(), "TRADE_NOT_EXIST", "ACQ.TRADE_NOT_EXIST")) { + return PayRefundRespDTO.failureOf(outRefundNo, response); + } + // 可能存在“ACQ.SYSTEM_ERROR”系统错误等情况,所以返回 WAIT 继续等待 + return PayRefundRespDTO.waitingOf(null, outRefundNo, response); + } + // 2.2 创建返回结果 + if (Objects.equals(response.getRefundStatus(), "REFUND_SUCCESS")) { + return PayRefundRespDTO.successOf(null, LocalDateTimeUtil.of(response.getGmtRefundPay()), + outRefundNo, response); + } + return PayRefundRespDTO.waitingOf(null, outRefundNo, response); + } + + @Override + protected PayTransferRespDTO doUnifiedTransfer(PayTransferUnifiedReqDTO reqDTO) throws AlipayApiException { + // 1.1 校验公钥类型 必须使用公钥证书模式 + if (!Objects.equals(config.getMode(), MODE_CERTIFICATE)) { + throw exception0(ERROR_CONFIGURATION.getCode(), "支付宝单笔转账必须使用公钥证书模式"); + } + // 1.2 构建 AlipayFundTransUniTransferModel + AlipayFundTransUniTransferModel model = new AlipayFundTransUniTransferModel(); + // ① 通用的参数 + model.setTransAmount(formatAmount(reqDTO.getPrice())); // 转账金额 + model.setOrderTitle(reqDTO.getSubject()); // 转账业务的标题,用于在支付宝用户的账单里显示。 + model.setOutBizNo(reqDTO.getOutTransferNo()); + model.setProductCode("TRANS_ACCOUNT_NO_PWD"); // 销售产品码。单笔无密转账固定为 TRANS_ACCOUNT_NO_PWD + model.setBizScene("DIRECT_TRANSFER"); // 业务场景 单笔无密转账固定为 DIRECT_TRANSFER + if (reqDTO.getChannelExtras() != null) { + model.setBusinessParams(JsonUtils.toJsonString(reqDTO.getChannelExtras())); + } + // ② 个性化的参数 + Participant payeeInfo = new Participant(); + PayTransferTypeEnum transferType = PayTransferTypeEnum.typeOf(reqDTO.getType()); + switch (transferType) { + // TODO @jason:是不是不用传递 transferType 参数哈?因为应该已经明确是支付宝啦? + // @芋艿。 是不是还要考虑转账到银行卡。所以传 transferType 但是转账到银行卡不知道要如何测试?? + case ALIPAY_BALANCE: { + payeeInfo.setIdentityType("ALIPAY_LOGON_ID"); + payeeInfo.setIdentity(reqDTO.getAlipayLogonId()); // 支付宝登录号 + payeeInfo.setName(reqDTO.getUserName()); // 支付宝账号姓名 + model.setPayeeInfo(payeeInfo); + break; + } + case BANK_CARD: { + payeeInfo.setIdentityType("BANKCARD_ACCOUNT"); + // TODO 待实现 + throw exception(NOT_IMPLEMENTED); + } + default: { + throw exception0(BAD_REQUEST.getCode(), "不正确的转账类型: {}", transferType); + } + } + // 1.3 构建 AlipayFundTransUniTransferRequest + AlipayFundTransUniTransferRequest request = new AlipayFundTransUniTransferRequest(); + request.setBizModel(model); + // 执行请求 + AlipayFundTransUniTransferResponse response = client.certificateExecute(request); + // 处理结果 + if (!response.isSuccess()) { + // 当出现 SYSTEM_ERROR, 转账可能成功也可能失败。 返回 WAIT 状态. 后续 job 会轮询,或相同 outBizNo 重新发起转账 + // 发现 outBizNo 相同 两次请求参数相同. 会返回 "PAYMENT_INFO_INCONSISTENCY", 不知道哪里的问题. 暂时返回 WAIT. 后续job 会轮询 + if (ObjectUtils.equalsAny(response.getSubCode(),"PAYMENT_INFO_INCONSISTENCY", "SYSTEM_ERROR", "ACQ.SYSTEM_ERROR")) { + return PayTransferRespDTO.waitingOf(null, reqDTO.getOutTransferNo(), response); + } + return PayTransferRespDTO.closedOf(response.getSubCode(), response.getSubMsg(), + reqDTO.getOutTransferNo(), response); + } else { + if (ObjectUtils.equalsAny(response.getStatus(), "REFUND", "FAIL")) { // 转账到银行卡会出现 "REFUND" "FAIL" + return PayTransferRespDTO.closedOf(response.getSubCode(), response.getSubMsg(), + reqDTO.getOutTransferNo(), response); + } + if (Objects.equals(response.getStatus(), "DEALING")) { // 转账到银行卡会出现 "DEALING" 处理中 + return PayTransferRespDTO.dealingOf(response.getOrderId(), reqDTO.getOutTransferNo(), response); + } + return PayTransferRespDTO.successOf(response.getOrderId(), parseTime(response.getTransDate()), + response.getOutBizNo(), response); + } + + } + + @Override + protected PayTransferRespDTO doGetTransfer(String outTradeNo, PayTransferTypeEnum type) throws Throwable { + // 1.1 构建 AlipayFundTransCommonQueryModel + AlipayFundTransCommonQueryModel model = new AlipayFundTransCommonQueryModel(); + model.setProductCode(type == PayTransferTypeEnum.BANK_CARD ? "TRANS_BANKCARD_NO_PWD" : "TRANS_ACCOUNT_NO_PWD"); + model.setBizScene("DIRECT_TRANSFER"); //业务场景 + model.setOutBizNo(outTradeNo); + // 1.2 构建 AlipayFundTransCommonQueryRequest + AlipayFundTransCommonQueryRequest request = new AlipayFundTransCommonQueryRequest(); + request.setBizModel(model); + + // 2.1 执行请求 + AlipayFundTransCommonQueryResponse response; + if (Objects.equals(config.getMode(), MODE_CERTIFICATE)) { // 证书模式 + response = client.certificateExecute(request); + } else { + response = client.execute(request); + } + // 2.2 处理返回结果 + if (response.isSuccess()) { + if (ObjectUtils.equalsAny(response.getStatus(), "REFUND", "FAIL")) { // 转账到银行卡会出现 "REFUND" "FAIL" + return PayTransferRespDTO.closedOf(response.getSubCode(), response.getSubMsg(), + outTradeNo, response); + } + if (Objects.equals(response.getStatus(), "DEALING")) { // 转账到银行卡会出现 "DEALING" 处理中 + return PayTransferRespDTO.dealingOf(response.getOrderId(), outTradeNo, response); + } + return PayTransferRespDTO.successOf(response.getOrderId(), parseTime(response.getPayDate()), + response.getOutBizNo(), response); + } else { + // 当出现 SYSTEM_ERROR, 转账可能成功也可能失败。 返回 WAIT 状态. 后续 job 会轮询, 或相同 outBizNo 重新发起转账 + // 当出现 ORDER_NOT_EXIST 可能是转账还在处理中,也可能是转账处理失败. 返回 WAIT 状态. 后续 job 会轮询, 或相同 outBizNo 重新发起转账 + if (ObjectUtils.equalsAny(response.getSubCode(), "ORDER_NOT_EXIST", "SYSTEM_ERROR", "ACQ.SYSTEM_ERROR")) { + return PayTransferRespDTO.waitingOf(null, outTradeNo, response); + } + return PayTransferRespDTO.closedOf(response.getSubCode(), response.getSubMsg(), + outTradeNo, response); + } + } + + // TODO @chihuo:这里是不是也要实现,支付宝的。 + @Override + protected PayTransferRespDTO doParseTransferNotify(Map params, String body) throws Throwable { + throw new UnsupportedOperationException("未实现"); + } + + // ========== 各种工具方法 ========== + + protected String formatAmount(Integer amount) { + return String.valueOf(amount / 100.0); + } + + protected String formatTime(LocalDateTime time) { + return LocalDateTimeUtil.format(time, NORM_DATETIME_FORMATTER); + } + + protected LocalDateTime parseTime(String str) { + return LocalDateTimeUtil.parse(str, NORM_DATETIME_FORMATTER); + } + +} diff --git a/tashow-sdk/tashow-sdk-payment/src/main/java/com/tashow/cloud/sdk/payment/client/impl/alipay/AlipayAppPayClient.java b/tashow-sdk/tashow-sdk-payment/src/main/java/com/tashow/cloud/sdk/payment/client/impl/alipay/AlipayAppPayClient.java new file mode 100644 index 0000000..1067ab8 --- /dev/null +++ b/tashow-sdk/tashow-sdk-payment/src/main/java/com/tashow/cloud/sdk/payment/client/impl/alipay/AlipayAppPayClient.java @@ -0,0 +1,60 @@ +package com.tashow.cloud.sdk.payment.client.impl.alipay; + +import com.alipay.api.AlipayApiException; +import com.alipay.api.domain.AlipayTradeAppPayModel; +import com.alipay.api.request.AlipayTradeAppPayRequest; +import com.alipay.api.response.AlipayTradeAppPayResponse; +import com.tashow.cloud.sdk.payment.dto.order.PayOrderRespDTO; +import com.tashow.cloud.sdk.payment.dto.order.PayOrderUnifiedReqDTO; +import com.tashow.cloud.sdk.payment.enums.channel.PayChannelEnum; +import com.tashow.cloud.sdk.payment.enums.order.PayOrderDisplayModeEnum; +import lombok.extern.slf4j.Slf4j; + +/** + * 支付宝【App 支付】的 PayClient 实现类 + * + * 文档:App 支付 + * + * // TODO 芋艿:未详细测试,因为手头没 App + * + * @author 芋道源码 + */ +@Slf4j +public class AlipayAppPayClient extends AbstractAlipayPayClient { + + public AlipayAppPayClient(Long channelId, AlipayPayClientConfig config) { + super(channelId, PayChannelEnum.ALIPAY_APP.getCode(), config); + } + + @Override + public PayOrderRespDTO doUnifiedOrder(PayOrderUnifiedReqDTO reqDTO) throws AlipayApiException { + // 1.1 构建 AlipayTradeAppPayModel 请求 + AlipayTradeAppPayModel model = new AlipayTradeAppPayModel(); + // ① 通用的参数 + model.setOutTradeNo(reqDTO.getOutTradeNo()); + model.setSubject(reqDTO.getSubject()); + model.setBody(reqDTO.getBody() + "test"); + model.setTotalAmount(formatAmount(reqDTO.getPrice())); + model.setTimeExpire(formatTime(reqDTO.getExpireTime())); + model.setProductCode("QUICK_MSECURITY_PAY"); // 销售产品码:无线快捷支付产品 + // ② 个性化的参数【无】 + // ③ 支付宝扫码支付只有一种展示 + String displayMode = PayOrderDisplayModeEnum.APP.getMode(); + + // 1.2 构建 AlipayTradePrecreateRequest 请求 + AlipayTradeAppPayRequest request = new AlipayTradeAppPayRequest(); + request.setBizModel(model); + request.setNotifyUrl(reqDTO.getNotifyUrl()); + request.setReturnUrl(reqDTO.getReturnUrl()); + + // 2.1 执行请求 + AlipayTradeAppPayResponse response = client.sdkExecute(request); + // 2.2 处理结果 + if (!response.isSuccess()) { + return buildClosedPayOrderRespDTO(reqDTO, response); + } + return PayOrderRespDTO.waitingOf(displayMode, response.getBody(), + reqDTO.getOutTradeNo(), response); + } + +} diff --git a/tashow-sdk/tashow-sdk-payment/src/main/java/com/tashow/cloud/sdk/payment/client/impl/alipay/AlipayBarPayClient.java b/tashow-sdk/tashow-sdk-payment/src/main/java/com/tashow/cloud/sdk/payment/client/impl/alipay/AlipayBarPayClient.java new file mode 100644 index 0000000..3466dd2 --- /dev/null +++ b/tashow-sdk/tashow-sdk-payment/src/main/java/com/tashow/cloud/sdk/payment/client/impl/alipay/AlipayBarPayClient.java @@ -0,0 +1,87 @@ +package com.tashow.cloud.sdk.payment.client.impl.alipay; + +import cn.hutool.core.date.LocalDateTimeUtil; +import cn.hutool.core.map.MapUtil; +import cn.hutool.core.util.StrUtil; +import com.alipay.api.AlipayApiException; +import com.alipay.api.domain.AlipayTradePayModel; +import com.alipay.api.request.AlipayTradePayRequest; +import com.alipay.api.response.AlipayTradePayResponse; +import com.tashow.cloud.sdk.payment.dto.order.PayOrderRespDTO; +import com.tashow.cloud.sdk.payment.dto.order.PayOrderUnifiedReqDTO; +import com.tashow.cloud.sdk.payment.enums.channel.PayChannelEnum; +import com.tashow.cloud.sdk.payment.enums.order.PayOrderDisplayModeEnum; +import lombok.extern.slf4j.Slf4j; + +import java.time.LocalDateTime; +import java.util.Objects; + +import static com.tashow.cloud.common.exception.enums.GlobalErrorCodeConstants.BAD_REQUEST; +import static com.tashow.cloud.common.exception.util.ServiceExceptionUtil.exception0; +import static com.tashow.cloud.sdk.payment.client.impl.alipay.AlipayPayClientConfig.MODE_CERTIFICATE; + + +/** + * 支付宝【条码支付】的 PayClient 实现类 + * + * 文档:当面付 + * + * @author 芋道源码 + */ +@Slf4j +public class AlipayBarPayClient extends AbstractAlipayPayClient { + + public AlipayBarPayClient(Long channelId, AlipayPayClientConfig config) { + super(channelId, PayChannelEnum.ALIPAY_BAR.getCode(), config); + } + + @Override + public PayOrderRespDTO doUnifiedOrder(PayOrderUnifiedReqDTO reqDTO) throws AlipayApiException { + String authCode = MapUtil.getStr(reqDTO.getChannelExtras(), "auth_code"); + if (StrUtil.isEmpty(authCode)) { + throw exception0(BAD_REQUEST.getCode(), "条形码不能为空"); + } + + // 1.1 构建 AlipayTradePayModel 请求 + AlipayTradePayModel model = new AlipayTradePayModel(); + // ① 通用的参数 + model.setOutTradeNo(reqDTO.getOutTradeNo()); + model.setSubject(reqDTO.getSubject()); + model.setBody(reqDTO.getBody()); + model.setTotalAmount(formatAmount(reqDTO.getPrice())); + model.setScene("bar_code"); // 当面付条码支付场景 + // ② 个性化的参数 + model.setAuthCode(authCode); + // ③ 支付宝条码支付只有一种展示 + String displayMode = PayOrderDisplayModeEnum.BAR_CODE.getMode(); + + // 1.2 构建 AlipayTradePayRequest 请求 + AlipayTradePayRequest request = new AlipayTradePayRequest(); + request.setBizModel(model); + request.setNotifyUrl(reqDTO.getNotifyUrl()); + request.setReturnUrl(reqDTO.getReturnUrl()); + + // 2.1 执行请求 + AlipayTradePayResponse response; + if (Objects.equals(config.getMode(), MODE_CERTIFICATE)) { + // 证书模式 + response = client.certificateExecute(request); + } else { + response = client.execute(request); + } + // 2.2 处理结果 + if (!response.isSuccess()) { + return buildClosedPayOrderRespDTO(reqDTO, response); + } + if ("10000".equals(response.getCode())) { // 免密支付 + LocalDateTime successTime = LocalDateTimeUtil.of(response.getGmtPayment()); + return PayOrderRespDTO.successOf(response.getTradeNo(), response.getBuyerUserId(), successTime, + response.getOutTradeNo(), response) + .setDisplayMode(displayMode).setDisplayContent(""); + } + // 大额支付,需要用户输入密码,所以返回 waiting。此时,前端一般会进行轮询 + return PayOrderRespDTO.waitingOf(displayMode, "", + reqDTO.getOutTradeNo(), response); + } + +} diff --git a/tashow-sdk/tashow-sdk-payment/src/main/java/com/tashow/cloud/sdk/payment/client/impl/alipay/AlipayPayClientConfig.java b/tashow-sdk/tashow-sdk-payment/src/main/java/com/tashow/cloud/sdk/payment/client/impl/alipay/AlipayPayClientConfig.java new file mode 100644 index 0000000..cf10a54 --- /dev/null +++ b/tashow-sdk/tashow-sdk-payment/src/main/java/com/tashow/cloud/sdk/payment/client/impl/alipay/AlipayPayClientConfig.java @@ -0,0 +1,127 @@ +package com.tashow.cloud.sdk.payment.client.impl.alipay; + +import com.tashow.cloud.common.util.validation.ValidationUtils; +import com.tashow.cloud.sdk.payment.client.PayClientConfig; +import jakarta.validation.Validator; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +/** + * 支付宝的 PayClientConfig 实现类 + * 属性主要来自 {@link com.alipay.api.AlipayConfig} 的必要属性 + * + * @author 芋道源码 + */ +@Data +public class AlipayPayClientConfig implements PayClientConfig { + + /** + * 公钥类型 - 公钥模式 + */ + public static final Integer MODE_PUBLIC_KEY = 1; + /** + * 公钥类型 - 证书模式 + */ + public static final Integer MODE_CERTIFICATE = 2; + + /** + * 接口内容加密方式 - AES 加密 + */ + public static final String ENC_TYPE_AES = "AES"; + + /** + * 签名算法类型 - RSA + */ + public static final String SIGN_TYPE_DEFAULT = "RSA2"; + + /** + * 网关地址 + * + * 1. 生产环境 + * 2. 沙箱环境 + */ + @NotBlank(message = "网关地址不能为空", groups = {ModePublicKey.class, ModeCertificate.class}) + private String serverUrl; + + /** + * 开放平台上创建的应用的 ID + */ + @NotBlank(message = "开放平台上创建的应用的 ID不能为空", groups = {ModePublicKey.class, ModeCertificate.class}) + private String appId; + + /** + * 签名算法类型,推荐:RSA2 + *

+ * {@link #SIGN_TYPE_DEFAULT} + */ + @NotBlank(message = "签名算法类型不能为空", groups = {ModePublicKey.class, ModeCertificate.class}) + private String signType; + + /** + * 公钥类型 + * 1. {@link #MODE_PUBLIC_KEY} 情况,privateKey + alipayPublicKey + * 2. {@link #MODE_CERTIFICATE} 情况,appCertContent + alipayPublicCertContent + rootCertContent + */ + @NotNull(message = "公钥类型不能为空", groups = {ModePublicKey.class, ModeCertificate.class}) + private Integer mode; + + // ========== 公钥模式 ========== + /** + * 商户私钥 + */ + @NotBlank(message = "商户私钥不能为空", groups = {ModePublicKey.class}) + private String privateKey; + + /** + * 支付宝公钥字符串 + */ + @NotBlank(message = "支付宝公钥字符串不能为空", groups = {ModePublicKey.class}) + private String alipayPublicKey; + + // ========== 证书模式 ========== + /** + * 指定商户公钥应用证书内容字符串 + */ + @NotBlank(message = "指定商户公钥应用证书内容不能为空", groups = {ModeCertificate.class}) + private String appCertContent; + /** + * 指定支付宝公钥证书内容字符串 + */ + @NotBlank(message = "指定支付宝公钥证书内容不能为空", groups = {ModeCertificate.class}) + private String alipayPublicCertContent; + /** + * 指定根证书内容字符串 + */ + @NotBlank(message = "指定根证书内容字符串不能为空", groups = {ModeCertificate.class}) + private String rootCertContent; + + /** + * 接口内容加密方式 + * + * 1. 如果为空,将使用无加密方式 + * 2. 如果要加密,目前支付宝只有 AES 一种加密方式 + * + * @see 支付宝开放平台 + * @see AlipayPayClientConfig#ENC_TYPE_AES + */ + private String encryptType; + + /** + * 接口内容加密的私钥 + */ + private String encryptKey; + + public interface ModePublicKey { + } + + public interface ModeCertificate { + } + + @Override + public void validate(Validator validator) { + ValidationUtils.validate(validator, this, + MODE_PUBLIC_KEY.equals(this.getMode()) ? ModePublicKey.class : ModeCertificate.class); + } + +} diff --git a/tashow-sdk/tashow-sdk-payment/src/main/java/com/tashow/cloud/sdk/payment/client/impl/alipay/AlipayPcPayClient.java b/tashow-sdk/tashow-sdk-payment/src/main/java/com/tashow/cloud/sdk/payment/client/impl/alipay/AlipayPcPayClient.java new file mode 100644 index 0000000..0e45d45 --- /dev/null +++ b/tashow-sdk/tashow-sdk-payment/src/main/java/com/tashow/cloud/sdk/payment/client/impl/alipay/AlipayPcPayClient.java @@ -0,0 +1,70 @@ +package com.tashow.cloud.sdk.payment.client.impl.alipay; + +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.http.Method; +import com.alipay.api.AlipayApiException; +import com.alipay.api.domain.AlipayTradePagePayModel; +import com.alipay.api.request.AlipayTradePagePayRequest; +import com.alipay.api.response.AlipayTradePagePayResponse; +import com.tashow.cloud.sdk.payment.dto.order.PayOrderRespDTO; +import com.tashow.cloud.sdk.payment.dto.order.PayOrderUnifiedReqDTO; +import com.tashow.cloud.sdk.payment.enums.channel.PayChannelEnum; +import com.tashow.cloud.sdk.payment.enums.order.PayOrderDisplayModeEnum; +import lombok.extern.slf4j.Slf4j; + +import java.util.Objects; + +/** + * 支付宝【PC 网站】的 PayClient 实现类 + * + * 文档:电脑网站支付 + * + * @author XGD + */ +@Slf4j +public class AlipayPcPayClient extends AbstractAlipayPayClient { + + public AlipayPcPayClient(Long channelId, AlipayPayClientConfig config) { + super(channelId, PayChannelEnum.ALIPAY_PC.getCode(), config); + } + + @Override + public PayOrderRespDTO doUnifiedOrder(PayOrderUnifiedReqDTO reqDTO) throws AlipayApiException { + // 1.1 构建 AlipayTradePagePayModel 请求 + AlipayTradePagePayModel model = new AlipayTradePagePayModel(); + // ① 通用的参数 + model.setOutTradeNo(reqDTO.getOutTradeNo()); + model.setSubject(reqDTO.getSubject()); + model.setBody(reqDTO.getBody()); + model.setTotalAmount(formatAmount(reqDTO.getPrice())); + model.setTimeExpire(formatTime(reqDTO.getExpireTime())); + model.setProductCode("FAST_INSTANT_TRADE_PAY"); // 销售产品码. 目前 PC 支付场景下仅支持 FAST_INSTANT_TRADE_PAY + // ② 个性化的参数 + // 如果想弄更多个性化的参数,可参考 https://www.pingxx.com/api/支付渠道 extra 参数说明.html 的 alipay_pc_direct 部分进行拓展 + model.setQrPayMode("2"); // 跳转模式 - 订单码,效果参见:https://help.pingxx.com/article/1137360/ + // ③ 支付宝 PC 支付有两种展示模式:FORM、URL + String displayMode = ObjectUtil.defaultIfNull(reqDTO.getDisplayMode(), + PayOrderDisplayModeEnum.URL.getMode()); + + // 1.2 构建 AlipayTradePagePayRequest 请求 + AlipayTradePagePayRequest request = new AlipayTradePagePayRequest(); + request.setBizModel(model); + request.setNotifyUrl(reqDTO.getNotifyUrl()); + request.setReturnUrl(reqDTO.getReturnUrl()); + + // 2.1 执行请求 + AlipayTradePagePayResponse response; + if (Objects.equals(displayMode, PayOrderDisplayModeEnum.FORM.getMode())) { + response = client.pageExecute(request, Method.POST.name()); // 需要特殊使用 POST 请求 + } else { + response = client.pageExecute(request, Method.GET.name()); + } + // 2.2 处理结果 + if (!response.isSuccess()) { + return buildClosedPayOrderRespDTO(reqDTO, response); + } + return PayOrderRespDTO.waitingOf(displayMode, response.getBody(), + reqDTO.getOutTradeNo(), response); + } + +} diff --git a/tashow-sdk/tashow-sdk-payment/src/main/java/com/tashow/cloud/sdk/payment/client/impl/alipay/AlipayQrPayClient.java b/tashow-sdk/tashow-sdk-payment/src/main/java/com/tashow/cloud/sdk/payment/client/impl/alipay/AlipayQrPayClient.java new file mode 100644 index 0000000..9995ef4 --- /dev/null +++ b/tashow-sdk/tashow-sdk-payment/src/main/java/com/tashow/cloud/sdk/payment/client/impl/alipay/AlipayQrPayClient.java @@ -0,0 +1,67 @@ +package com.tashow.cloud.sdk.payment.client.impl.alipay; + +import com.alipay.api.AlipayApiException; +import com.alipay.api.domain.AlipayTradePrecreateModel; +import com.alipay.api.request.AlipayTradePrecreateRequest; +import com.alipay.api.response.AlipayTradePrecreateResponse; +import com.tashow.cloud.sdk.payment.dto.order.PayOrderRespDTO; +import com.tashow.cloud.sdk.payment.dto.order.PayOrderUnifiedReqDTO; +import com.tashow.cloud.sdk.payment.enums.channel.PayChannelEnum; +import com.tashow.cloud.sdk.payment.enums.order.PayOrderDisplayModeEnum; +import lombok.extern.slf4j.Slf4j; + +import java.util.Objects; + +import static com.tashow.cloud.sdk.payment.client.impl.alipay.AlipayPayClientConfig.MODE_CERTIFICATE; + + +/** + * 支付宝【扫码支付】的 PayClient 实现类 + * + * 文档:扫码支付 + * + * @author 芋道源码 + */ +@Slf4j +public class AlipayQrPayClient extends AbstractAlipayPayClient { + + public AlipayQrPayClient(Long channelId, AlipayPayClientConfig config) { + super(channelId, PayChannelEnum.ALIPAY_QR.getCode(), config); + } + + @Override + public PayOrderRespDTO doUnifiedOrder(PayOrderUnifiedReqDTO reqDTO) throws AlipayApiException { + // 1.1 构建 AlipayTradePrecreateModel 请求 + AlipayTradePrecreateModel model = new AlipayTradePrecreateModel(); + // ① 通用的参数 + model.setOutTradeNo(reqDTO.getOutTradeNo()); + model.setSubject(reqDTO.getSubject()); + model.setBody(reqDTO.getBody()); + model.setTotalAmount(formatAmount(reqDTO.getPrice())); + model.setProductCode("FACE_TO_FACE_PAYMENT"); // 销售产品码. 目前扫码支付场景下仅支持 FACE_TO_FACE_PAYMENT + // ② 个性化的参数【无】 + // ③ 支付宝扫码支付只有一种展示,考虑到前端可能希望二维码扫描后,手机打开 + String displayMode = PayOrderDisplayModeEnum.QR_CODE.getMode(); + + // 1.2 构建 AlipayTradePrecreateRequest 请求 + AlipayTradePrecreateRequest request = new AlipayTradePrecreateRequest(); + request.setBizModel(model); + request.setNotifyUrl(reqDTO.getNotifyUrl()); + request.setReturnUrl(reqDTO.getReturnUrl()); + + // 2.1 执行请求 + AlipayTradePrecreateResponse response; + if (Objects.equals(config.getMode(), MODE_CERTIFICATE)) { + // 证书模式 + response = client.certificateExecute(request); + } else { + response = client.execute(request); + } + // 2.2 处理结果 + if (!response.isSuccess()) { + return buildClosedPayOrderRespDTO(reqDTO, response); + } + return PayOrderRespDTO.waitingOf(displayMode, response.getQrCode(), + reqDTO.getOutTradeNo(), response); + } +} diff --git a/tashow-sdk/tashow-sdk-payment/src/main/java/com/tashow/cloud/sdk/payment/client/impl/alipay/AlipayWapPayClient.java b/tashow-sdk/tashow-sdk-payment/src/main/java/com/tashow/cloud/sdk/payment/client/impl/alipay/AlipayWapPayClient.java new file mode 100644 index 0000000..560fdd8 --- /dev/null +++ b/tashow-sdk/tashow-sdk-payment/src/main/java/com/tashow/cloud/sdk/payment/client/impl/alipay/AlipayWapPayClient.java @@ -0,0 +1,59 @@ +package com.tashow.cloud.sdk.payment.client.impl.alipay; + +import cn.hutool.http.Method; +import com.alipay.api.AlipayApiException; +import com.alipay.api.domain.AlipayTradeWapPayModel; +import com.alipay.api.request.AlipayTradeWapPayRequest; +import com.alipay.api.response.AlipayTradeWapPayResponse; +import com.tashow.cloud.sdk.payment.dto.order.PayOrderRespDTO; +import com.tashow.cloud.sdk.payment.dto.order.PayOrderUnifiedReqDTO; +import com.tashow.cloud.sdk.payment.enums.channel.PayChannelEnum; +import com.tashow.cloud.sdk.payment.enums.order.PayOrderDisplayModeEnum; +import lombok.extern.slf4j.Slf4j; + +/** + * 支付宝【Wap 网站】的 PayClient 实现类 + * + * 文档:手机网站支付接口 + * + * @author 芋道源码 + */ +@Slf4j +public class AlipayWapPayClient extends AbstractAlipayPayClient { + + public AlipayWapPayClient(Long channelId, AlipayPayClientConfig config) { + super(channelId, PayChannelEnum.ALIPAY_WAP.getCode(), config); + } + + @Override + public PayOrderRespDTO doUnifiedOrder(PayOrderUnifiedReqDTO reqDTO) throws AlipayApiException { + // 1.1 构建 AlipayTradeWapPayModel 请求 + AlipayTradeWapPayModel model = new AlipayTradeWapPayModel(); + // ① 通用的参数 + model.setOutTradeNo(reqDTO.getOutTradeNo()); + model.setSubject(reqDTO.getSubject()); + model.setBody(reqDTO.getBody()); + model.setTotalAmount(formatAmount(reqDTO.getPrice())); + model.setProductCode("QUICK_WAP_PAY"); // 销售产品码. 目前 Wap 支付场景下仅支持 QUICK_WAP_PAY + // ② 个性化的参数【无】 + // ③ 支付宝 Wap 支付只有一种展示:URL + String displayMode = PayOrderDisplayModeEnum.URL.getMode(); + + // 1.2 构建 AlipayTradeWapPayRequest 请求 + AlipayTradeWapPayRequest request = new AlipayTradeWapPayRequest(); + request.setBizModel(model); + request.setNotifyUrl(reqDTO.getNotifyUrl()); + request.setReturnUrl(reqDTO.getReturnUrl()); + model.setQuitUrl(reqDTO.getReturnUrl()); + model.setTimeExpire(formatTime(reqDTO.getExpireTime())); + + // 2.1 执行请求 + AlipayTradeWapPayResponse response = client.pageExecute(request, Method.GET.name()); + // 2.2 处理结果 + if (!response.isSuccess()) { + return buildClosedPayOrderRespDTO(reqDTO, response); + } + return PayOrderRespDTO.waitingOf(displayMode, response.getBody(), + reqDTO.getOutTradeNo(), response); + } +} diff --git a/tashow-sdk/tashow-sdk-payment/src/main/java/com/tashow/cloud/sdk/payment/client/impl/weixin/AbstractWxPayClient.java b/tashow-sdk/tashow-sdk-payment/src/main/java/com/tashow/cloud/sdk/payment/client/impl/weixin/AbstractWxPayClient.java new file mode 100644 index 0000000..dadea5b --- /dev/null +++ b/tashow-sdk/tashow-sdk-payment/src/main/java/com/tashow/cloud/sdk/payment/client/impl/weixin/AbstractWxPayClient.java @@ -0,0 +1,557 @@ +package com.tashow.cloud.sdk.payment.client.impl.weixin; + +import cn.hutool.core.bean.BeanUtil; +import cn.hutool.core.codec.Base64; +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.date.LocalDateTimeUtil; +import cn.hutool.core.date.TemporalAccessorUtil; +import cn.hutool.core.util.StrUtil; +import com.github.binarywang.wxpay.bean.notify.WxPayNotifyV3Result; +import com.github.binarywang.wxpay.bean.notify.WxPayOrderNotifyResult; +import com.github.binarywang.wxpay.bean.notify.WxPayRefundNotifyResult; +import com.github.binarywang.wxpay.bean.notify.WxPayRefundNotifyV3Result; +import com.github.binarywang.wxpay.bean.request.*; +import com.github.binarywang.wxpay.bean.result.*; +import com.github.binarywang.wxpay.bean.transfer.QueryTransferBatchesRequest; +import com.github.binarywang.wxpay.bean.transfer.QueryTransferBatchesResult; +import com.github.binarywang.wxpay.bean.transfer.TransferBatchesRequest; +import com.github.binarywang.wxpay.bean.transfer.TransferBatchesResult; +import com.github.binarywang.wxpay.config.WxPayConfig; +import com.github.binarywang.wxpay.exception.WxPayException; +import com.github.binarywang.wxpay.service.WxPayService; +import com.github.binarywang.wxpay.service.impl.WxPayServiceImpl; +import com.tashow.cloud.common.util.io.FileUtils; +import com.tashow.cloud.common.util.object.ObjectUtils; +import com.tashow.cloud.sdk.payment.client.impl.AbstractPayClient; +import com.tashow.cloud.sdk.payment.dto.order.PayOrderRespDTO; +import com.tashow.cloud.sdk.payment.dto.order.PayOrderUnifiedReqDTO; +import com.tashow.cloud.sdk.payment.dto.refund.PayRefundRespDTO; +import com.tashow.cloud.sdk.payment.dto.refund.PayRefundUnifiedReqDTO; +import com.tashow.cloud.sdk.payment.dto.transfer.PayTransferRespDTO; +import com.tashow.cloud.sdk.payment.dto.transfer.PayTransferUnifiedReqDTO; +import com.tashow.cloud.sdk.payment.dto.transfer.WxPayTransferPartnerNotifyV3Result; +import com.tashow.cloud.sdk.payment.enums.order.PayOrderStatusRespEnum; +import com.tashow.cloud.sdk.payment.enums.transfer.PayTransferTypeEnum; +import lombok.extern.slf4j.Slf4j; + +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Objects; + +import static cn.hutool.core.date.DatePattern.*; +import static com.tashow.cloud.sdk.payment.client.impl.weixin.WxPayClientConfig.API_VERSION_V2; +import static com.tashow.cloud.sdk.payment.client.impl.weixin.WxPayClientConfig.API_VERSION_V3; + +/** + * 微信支付抽象类,实现微信统一的接口、以及部分实现(退款) + * + * @author 芋道源码 + */ +@Slf4j +public abstract class AbstractWxPayClient extends AbstractPayClient { + + protected WxPayService client; + + public AbstractWxPayClient(Long channelId, String channelCode, WxPayClientConfig config) { + super(channelId, channelCode, config); + } + + /** + * 初始化 client 客户端 + * + * @param tradeType 交易类型 + */ + protected void doInit(String tradeType) { + // 创建 config 配置 + WxPayConfig payConfig = new WxPayConfig(); + BeanUtil.copyProperties(config, payConfig, "keyContent", "privateKeyContent"); + payConfig.setTradeType(tradeType); + // weixin-pay-java 无法设置内容,只允许读取文件,所以这里要创建临时文件来解决 + if (Objects.equals(config.getApiVersion(), API_VERSION_V2)) { + payConfig.setKeyPath(FileUtils.createTempFile(Base64.decode(config.getKeyContent())).getPath()); + } else if (Objects.equals(config.getApiVersion(), API_VERSION_V3)) { + payConfig.setPrivateKeyPath(FileUtils.createTempFile(config.getPrivateKeyContent()).getPath()); + } + + // 创建 client 客户端 + client = new WxPayServiceImpl(); + client.setConfig(payConfig); + } + + // ============ 支付相关 ========== + + @Override + protected PayOrderRespDTO doUnifiedOrder(PayOrderUnifiedReqDTO reqDTO) throws Exception { + try { + switch (config.getApiVersion()) { + case API_VERSION_V2: + return doUnifiedOrderV2(reqDTO); + case API_VERSION_V3: + return doUnifiedOrderV3(reqDTO); + default: + throw new IllegalArgumentException(String.format("未知的 API 版本(%s)", config.getApiVersion())); + } + } catch (WxPayException e) { + log.error("[doUnifiedOrder][退款({}) 发起微信支付异常", reqDTO, e); + String errorCode = getErrorCode(e); + String errorMessage = getErrorMessage(e); + return PayOrderRespDTO.closedOf(errorCode, errorMessage, + reqDTO.getOutTradeNo(), e.getXmlString()); + } + } + + /** + * 【V2】调用支付渠道,统一下单 + * + * @param reqDTO 下单信息 + * @return 各支付渠道的返回结果 + */ + protected abstract PayOrderRespDTO doUnifiedOrderV2(PayOrderUnifiedReqDTO reqDTO) + throws Exception; + + /** + * 【V3】调用支付渠道,统一下单 + * + * @param reqDTO 下单信息 + * @return 各支付渠道的返回结果 + */ + protected abstract PayOrderRespDTO doUnifiedOrderV3(PayOrderUnifiedReqDTO reqDTO) + throws WxPayException; + + /** + * 【V2】创建微信下单请求 + * + * @param reqDTO 下信息 + * @return 下单请求 + */ + protected WxPayUnifiedOrderRequest buildPayUnifiedOrderRequestV2(PayOrderUnifiedReqDTO reqDTO) { + return WxPayUnifiedOrderRequest.newBuilder() + .outTradeNo(reqDTO.getOutTradeNo()) + .body(reqDTO.getSubject()) + .detail(reqDTO.getBody()) + .totalFee(reqDTO.getPrice()) // 单位分 + .timeExpire(formatDateV2(reqDTO.getExpireTime())) + .spbillCreateIp(reqDTO.getUserIp()) + .notifyUrl(reqDTO.getNotifyUrl()) + .build(); + } + + /** + * 【V3】创建微信下单请求 + * + * @param reqDTO 下信息 + * @return 下单请求 + */ + protected WxPayUnifiedOrderV3Request buildPayUnifiedOrderRequestV3(PayOrderUnifiedReqDTO reqDTO) { + WxPayUnifiedOrderV3Request request = new WxPayUnifiedOrderV3Request(); + request.setOutTradeNo(reqDTO.getOutTradeNo()); + request.setDescription(reqDTO.getSubject()); + request.setAmount(new WxPayUnifiedOrderV3Request.Amount().setTotal(reqDTO.getPrice())); // 单位分 + request.setTimeExpire(formatDateV3(reqDTO.getExpireTime())); + request.setSceneInfo(new WxPayUnifiedOrderV3Request.SceneInfo().setPayerClientIp(reqDTO.getUserIp())); + request.setNotifyUrl(reqDTO.getNotifyUrl()); + return request; + } + + @Override + public PayOrderRespDTO doParseOrderNotify(Map params, String body) throws WxPayException { + switch (config.getApiVersion()) { + case API_VERSION_V2: + return doParseOrderNotifyV2(body); + case API_VERSION_V3: + return doParseOrderNotifyV3(body); + default: + throw new IllegalArgumentException(String.format("未知的 API 版本(%s)", config.getApiVersion())); + } + } + + private PayOrderRespDTO doParseOrderNotifyV2(String body) throws WxPayException { + // 1. 解析回调 + WxPayOrderNotifyResult response = client.parseOrderNotifyResult(body); + // 2. 构建结果 + // V2 微信支付的回调,只有 SUCCESS 支付成功、CLOSED 支付失败两种情况,无需像支付宝一样解析的比较复杂 + Integer status = Objects.equals(response.getResultCode(), "SUCCESS") ? + PayOrderStatusRespEnum.SUCCESS.getStatus() : PayOrderStatusRespEnum.CLOSED.getStatus(); + return PayOrderRespDTO.of(status, response.getTransactionId(), response.getOpenid(), parseDateV2(response.getTimeEnd()), + response.getOutTradeNo(), body); + } + + private PayOrderRespDTO doParseOrderNotifyV3(String body) throws WxPayException { + // 1. 解析回调 + WxPayNotifyV3Result response = client.parseOrderNotifyV3Result(body, null); + WxPayNotifyV3Result.DecryptNotifyResult result = response.getResult(); + // 2. 构建结果 + Integer status = parseStatus(result.getTradeState()); + String openid = result.getPayer() != null ? result.getPayer().getOpenid() : null; + return PayOrderRespDTO.of(status, result.getTransactionId(), openid, parseDateV3(result.getSuccessTime()), + result.getOutTradeNo(), body); + } + + @Override + protected PayOrderRespDTO doGetOrder(String outTradeNo) throws Throwable { + try { + switch (config.getApiVersion()) { + case API_VERSION_V2: + return doGetOrderV2(outTradeNo); + case API_VERSION_V3: + return doGetOrderV3(outTradeNo); + default: + throw new IllegalArgumentException(String.format("未知的 API 版本(%s)", config.getApiVersion())); + } + } catch (WxPayException e) { + if (ObjectUtils.equalsAny(e.getErrCode(), "ORDERNOTEXIST", "ORDER_NOT_EXIST")) { + String errorCode = getErrorCode(e); + String errorMessage = getErrorMessage(e); + return PayOrderRespDTO.closedOf(errorCode, errorMessage, + outTradeNo, e.getXmlString()); + } + throw e; + } + } + + private PayOrderRespDTO doGetOrderV2(String outTradeNo) throws WxPayException { + // 构建 WxPayUnifiedOrderRequest 对象 + WxPayOrderQueryRequest request = WxPayOrderQueryRequest.newBuilder() + .outTradeNo(outTradeNo).build(); + // 执行请求 + WxPayOrderQueryResult response = client.queryOrder(request); + + // 转换结果 + Integer status = parseStatus(response.getTradeState()); + return PayOrderRespDTO.of(status, response.getTransactionId(), response.getOpenid(), parseDateV2(response.getTimeEnd()), + outTradeNo, response); + } + + private PayOrderRespDTO doGetOrderV3(String outTradeNo) throws WxPayException { + // 构建 WxPayUnifiedOrderRequest 对象 + WxPayOrderQueryV3Request request = new WxPayOrderQueryV3Request() + .setOutTradeNo(outTradeNo); + // 执行请求 + WxPayOrderQueryV3Result response = client.queryOrderV3(request); + + // 转换结果 + Integer status = parseStatus(response.getTradeState()); + String openid = response.getPayer() != null ? response.getPayer().getOpenid() : null; + return PayOrderRespDTO.of(status, response.getTransactionId(), openid, parseDateV3(response.getSuccessTime()), + outTradeNo, response); + } + + private static Integer parseStatus(String tradeState) { + switch (tradeState) { + case "NOTPAY": + case "USERPAYING": // 支付中,等待用户输入密码(条码支付独有) + return PayOrderStatusRespEnum.WAITING.getStatus(); + case "SUCCESS": + return PayOrderStatusRespEnum.SUCCESS.getStatus(); + case "REFUND": + return PayOrderStatusRespEnum.REFUND.getStatus(); + case "CLOSED": + case "REVOKED": // 已撤销(刷卡支付独有) + case "PAYERROR": // 支付失败(其它原因,如银行返回失败) + return PayOrderStatusRespEnum.CLOSED.getStatus(); + default: + throw new IllegalArgumentException(StrUtil.format("未知的支付状态({})", tradeState)); + } + } + + // ============ 退款相关 ========== + + @Override + protected PayRefundRespDTO doUnifiedRefund(PayRefundUnifiedReqDTO reqDTO) throws Throwable { + try { + switch (config.getApiVersion()) { + case API_VERSION_V2: + return doUnifiedRefundV2(reqDTO); + case API_VERSION_V3: + return doUnifiedRefundV3(reqDTO); + default: + throw new IllegalArgumentException(String.format("未知的 API 版本(%s)", config.getApiVersion())); + } + } catch (WxPayException e) { + String errorCode = getErrorCode(e); + String errorMessage = getErrorMessage(e); + return PayRefundRespDTO.failureOf(errorCode, errorMessage, + reqDTO.getOutRefundNo(), e.getXmlString()); + } + } + + private PayRefundRespDTO doUnifiedRefundV2(PayRefundUnifiedReqDTO reqDTO) throws Throwable { + // 1. 构建 WxPayRefundRequest 请求 + WxPayRefundRequest request = new WxPayRefundRequest() + .setOutTradeNo(reqDTO.getOutTradeNo()) + .setOutRefundNo(reqDTO.getOutRefundNo()) + .setRefundFee(reqDTO.getRefundPrice()) + .setRefundDesc(reqDTO.getReason()) + .setTotalFee(reqDTO.getPayPrice()) + .setNotifyUrl(reqDTO.getNotifyUrl()); + // 2.1 执行请求 + WxPayRefundResult response = client.refundV2(request); + // 2.2 创建返回结果 + if (Objects.equals("SUCCESS", response.getResultCode())) { // V2 情况下,不直接返回退款成功,而是等待异步通知 + return PayRefundRespDTO.waitingOf(response.getRefundId(), + reqDTO.getOutRefundNo(), response); + } + return PayRefundRespDTO.failureOf(reqDTO.getOutRefundNo(), response); + } + + private PayRefundRespDTO doUnifiedRefundV3(PayRefundUnifiedReqDTO reqDTO) throws Throwable { + // 1. 构建 WxPayRefundRequest 请求 + WxPayRefundV3Request request = new WxPayRefundV3Request() + .setOutTradeNo(reqDTO.getOutTradeNo()) + .setOutRefundNo(reqDTO.getOutRefundNo()) + .setAmount(new WxPayRefundV3Request.Amount().setRefund(reqDTO.getRefundPrice()) + .setTotal(reqDTO.getPayPrice()).setCurrency("CNY")) + .setReason(reqDTO.getReason()) + .setNotifyUrl(reqDTO.getNotifyUrl()); + // 2.1 执行请求 + WxPayRefundV3Result response = client.refundV3(request); + // 2.2 创建返回结果 + if (Objects.equals("SUCCESS", response.getStatus())) { + return PayRefundRespDTO.successOf(response.getRefundId(), parseDateV3(response.getSuccessTime()), + reqDTO.getOutRefundNo(), response); + } + if (Objects.equals("PROCESSING", response.getStatus())) { + return PayRefundRespDTO.waitingOf(response.getRefundId(), + reqDTO.getOutRefundNo(), response); + } + return PayRefundRespDTO.failureOf(reqDTO.getOutRefundNo(), response); + } + + @Override + public PayRefundRespDTO doParseRefundNotify(Map params, String body) throws WxPayException { + switch (config.getApiVersion()) { + case API_VERSION_V2: + return doParseRefundNotifyV2(body); + case API_VERSION_V3: + return parseRefundNotifyV3(body); + default: + throw new IllegalArgumentException(String.format("未知的 API 版本(%s)", config.getApiVersion())); + } + } + + private PayRefundRespDTO doParseRefundNotifyV2(String body) throws WxPayException { + // 1. 解析回调 + WxPayRefundNotifyResult response = client.parseRefundNotifyResult(body); + WxPayRefundNotifyResult.ReqInfo result = response.getReqInfo(); + // 2. 构建结果 + if (Objects.equals("SUCCESS", result.getRefundStatus())) { + return PayRefundRespDTO.successOf(result.getRefundId(), parseDateV2B(result.getSuccessTime()), + result.getOutRefundNo(), response); + } + return PayRefundRespDTO.failureOf(result.getOutRefundNo(), response); + } + + private PayRefundRespDTO parseRefundNotifyV3(String body) throws WxPayException { + // 1. 解析回调 + WxPayRefundNotifyV3Result response = client.parseRefundNotifyV3Result(body, null); + WxPayRefundNotifyV3Result.DecryptNotifyResult result = response.getResult(); + // 2. 构建结果 + if (Objects.equals("SUCCESS", result.getRefundStatus())) { + return PayRefundRespDTO.successOf(result.getRefundId(), parseDateV3(result.getSuccessTime()), + result.getOutRefundNo(), response); + } + return PayRefundRespDTO.failureOf(result.getOutRefundNo(), response); + } + + @Override + public PayTransferRespDTO doParseTransferNotify(Map params, String body) throws WxPayException { + switch (config.getApiVersion()) { + case API_VERSION_V3: + return parseTransferNotifyV3(body); + case API_VERSION_V2: + throw new UnsupportedOperationException("V2 版本暂不支持,建议使用 V3 版本"); + default: + throw new IllegalArgumentException(String.format("未知的 API 版本(%s)", config.getApiVersion())); + } + } + + private PayTransferRespDTO parseTransferNotifyV3(String body) throws WxPayException { + // 1. 解析回调 + // TODO @luchi:这个可以复用 wxjava 里的类么? + WxPayTransferPartnerNotifyV3Result response = client.baseParseOrderNotifyV3Result(body, null, WxPayTransferPartnerNotifyV3Result.class, WxPayTransferPartnerNotifyV3Result.TransferNotifyResult.class); + WxPayTransferPartnerNotifyV3Result.TransferNotifyResult result = response.getResult(); + // 2. 构建结果 + if (Objects.equals("FINISHED", result.getBatchStatus())) { + if (result.getFailNum() <= 0) { + return PayTransferRespDTO.successOf(result.getBatchId(), parseDateV3(result.getUpdateTime()), + result.getOutBatchNo(), response); + } + } + return PayTransferRespDTO.closedOf(result.getBatchStatus(), result.getCloseReason(), result.getOutBatchNo(), response); + } + + @Override + protected PayRefundRespDTO doGetRefund(String outTradeNo, String outRefundNo) throws WxPayException { + try { + switch (config.getApiVersion()) { + case API_VERSION_V2: + return doGetRefundV2(outTradeNo, outRefundNo); + case API_VERSION_V3: + return doGetRefundV3(outTradeNo, outRefundNo); + default: + throw new IllegalArgumentException(String.format("未知的 API 版本(%s)", config.getApiVersion())); + } + } catch (WxPayException e) { + if (ObjectUtils.equalsAny(e.getErrCode(), "REFUNDNOTEXIST", "RESOURCE_NOT_EXISTS")) { + String errorCode = getErrorCode(e); + String errorMessage = getErrorMessage(e); + return PayRefundRespDTO.failureOf(errorCode, errorMessage, + outRefundNo, e.getXmlString()); + } + throw e; + } + } + + private PayRefundRespDTO doGetRefundV2(String outTradeNo, String outRefundNo) throws WxPayException { + // 1. 构建 WxPayRefundRequest 请求 + WxPayRefundQueryRequest request = WxPayRefundQueryRequest.newBuilder() + .outTradeNo(outTradeNo) + .outRefundNo(outRefundNo) + .build(); + // 2.1 执行请求 + WxPayRefundQueryResult response = client.refundQuery(request); + // 2.2 创建返回结果 + if (!Objects.equals("SUCCESS", response.getResultCode())) { + return PayRefundRespDTO.waitingOf(null, + outRefundNo, response); + } + WxPayRefundQueryResult.RefundRecord refund = CollUtil.findOne(response.getRefundRecords(), + record -> record.getOutRefundNo().equals(outRefundNo)); + if (refund == null) { + return PayRefundRespDTO.failureOf(outRefundNo, response); + } + switch (refund.getRefundStatus()) { + case "SUCCESS": + return PayRefundRespDTO.successOf(refund.getRefundId(), parseDateV2B(refund.getRefundSuccessTime()), + outRefundNo, response); + case "PROCESSING": + return PayRefundRespDTO.waitingOf(refund.getRefundId(), + outRefundNo, response); + case "CHANGE": // 退款到银行发现用户的卡作废或者冻结了,导致原路退款银行卡失败,资金回流到商户的现金帐号,需要商户人工干预,通过线下或者财付通转账的方式进行退款 + case "FAIL": + return PayRefundRespDTO.failureOf(outRefundNo, response); + default: + throw new IllegalArgumentException(String.format("未知的退款状态(%s)", refund.getRefundStatus())); + } + } + + private PayRefundRespDTO doGetRefundV3(String outTradeNo, String outRefundNo) throws WxPayException { + // 1. 构建 WxPayRefundRequest 请求 + WxPayRefundQueryV3Request request = new WxPayRefundQueryV3Request(); + request.setOutRefundNo(outRefundNo); + // 2.1 执行请求 + WxPayRefundQueryV3Result response = client.refundQueryV3(request); + // 2.2 创建返回结果 + switch (response.getStatus()) { + case "SUCCESS": + return PayRefundRespDTO.successOf(response.getRefundId(), parseDateV3(response.getSuccessTime()), + outRefundNo, response); + case "PROCESSING": + return PayRefundRespDTO.waitingOf(response.getRefundId(), + outRefundNo, response); + case "ABNORMAL": // 退款异常 + case "CLOSED": + return PayRefundRespDTO.failureOf(outRefundNo, response); + default: + throw new IllegalArgumentException(String.format("未知的退款状态(%s)", response.getStatus())); + } + } + + @Override + protected PayTransferRespDTO doUnifiedTransfer(PayTransferUnifiedReqDTO reqDTO) throws WxPayException { + // 1. 构建 TransferBatchesRequest 请求 + List transferDetailList = Collections.singletonList( + TransferBatchesRequest.TransferDetail.newBuilder() + .outDetailNo(reqDTO.getOutTransferNo()) + .transferAmount(reqDTO.getPrice()) + .transferRemark(reqDTO.getSubject()) + .openid(reqDTO.getOpenid()) + .build()); + // TODO @luchi:能不能我们搞个 TransferBatchesRequestX extends TransferBatchesRequest,这样更简洁一点。 + TransferBatchesRequest transferBatches = TransferBatchesRequest.newBuilder() + .appid(this.config.getAppId()) + .outBatchNo(reqDTO.getOutTransferNo()) + .batchName(reqDTO.getSubject()) + .batchRemark(reqDTO.getSubject()) + .totalAmount(reqDTO.getPrice()) + .totalNum(transferDetailList.size()) + .transferDetailList(transferDetailList).build() +// .setNotifyUrl(reqDTO.getNotifyUrl()) + ; + // 2.1 执行请求 + TransferBatchesResult transferBatchesResult = client.getTransferService().transferBatches(transferBatches); + // 2.2 创建返回结果 + return PayTransferRespDTO.dealingOf(transferBatchesResult.getBatchId(), reqDTO.getOutTransferNo(), transferBatchesResult); + } + + @Override + protected PayTransferRespDTO doGetTransfer(String outTradeNo, PayTransferTypeEnum type) throws WxPayException { + QueryTransferBatchesRequest request = QueryTransferBatchesRequest.newBuilder() + .outBatchNo(outTradeNo).needQueryDetail(true).offset(0).limit(20).detailStatus("ALL") + .build(); + QueryTransferBatchesResult response = client.getTransferService().transferBatchesOutBatchNo(request); + QueryTransferBatchesResult.TransferBatch transferBatch = response.getTransferBatch(); + if (Objects.equals("FINISHED", transferBatch.getBatchStatus())) { + // 明细中全部成功则成功,任一失败则失败 + if (response.getTransferDetailList().stream().allMatch(detail -> Objects.equals("SUCCESS", detail.getDetailStatus()))) { + return PayTransferRespDTO.successOf(transferBatch.getBatchId(), parseDateV3(transferBatch.getUpdateTime()), + transferBatch.getOutBatchNo(), response); + } + if (response.getTransferDetailList().stream().anyMatch(detail -> Objects.equals("FAIL", detail.getDetailStatus()))) { + return PayTransferRespDTO.closedOf(transferBatch.getBatchStatus(), transferBatch.getCloseReason(), + transferBatch.getOutBatchNo(), response); + } + } + if (Objects.equals("CLOSED", transferBatch.getBatchStatus())) { + return PayTransferRespDTO.closedOf(transferBatch.getBatchStatus(), transferBatch.getCloseReason(), + transferBatch.getOutBatchNo(), response); + } + return PayTransferRespDTO.dealingOf(transferBatch.getBatchId(), transferBatch.getOutBatchNo(), response); + } + + // ========== 各种工具方法 ========== + + static String formatDateV2(LocalDateTime time) { + return TemporalAccessorUtil.format(time.atZone(ZoneId.systemDefault()), PURE_DATETIME_PATTERN); + } + + static LocalDateTime parseDateV2(String time) { + return LocalDateTimeUtil.parse(time, PURE_DATETIME_PATTERN); + } + + static LocalDateTime parseDateV2B(String time) { + return LocalDateTimeUtil.parse(time, NORM_DATETIME_PATTERN); + } + + static String formatDateV3(LocalDateTime time) { + return TemporalAccessorUtil.format(time.atZone(ZoneId.systemDefault()), UTC_WITH_XXX_OFFSET_PATTERN); + } + + static LocalDateTime parseDateV3(String time) { + return LocalDateTimeUtil.parse(time, UTC_WITH_XXX_OFFSET_PATTERN); + } + + static String getErrorCode(WxPayException e) { + if (StrUtil.isNotEmpty(e.getErrCode())) { + return e.getErrCode(); + } + if (StrUtil.isNotEmpty(e.getCustomErrorMsg())) { + return "CUSTOM_ERROR"; + } + return e.getReturnCode(); + } + + static String getErrorMessage(WxPayException e) { + if (StrUtil.isNotEmpty(e.getErrCode())) { + return e.getErrCodeDes(); + } + if (StrUtil.isNotEmpty(e.getCustomErrorMsg())) { + return e.getCustomErrorMsg(); + } + return e.getReturnMsg(); + } + +} diff --git a/tashow-sdk/tashow-sdk-payment/src/main/java/com/tashow/cloud/sdk/payment/client/impl/weixin/WxAppPayClient.java b/tashow-sdk/tashow-sdk-payment/src/main/java/com/tashow/cloud/sdk/payment/client/impl/weixin/WxAppPayClient.java new file mode 100644 index 0000000..aa0eb68 --- /dev/null +++ b/tashow-sdk/tashow-sdk-payment/src/main/java/com/tashow/cloud/sdk/payment/client/impl/weixin/WxAppPayClient.java @@ -0,0 +1,64 @@ +package com.tashow.cloud.sdk.payment.client.impl.weixin; + +import com.github.binarywang.wxpay.bean.order.WxPayAppOrderResult; +import com.github.binarywang.wxpay.bean.request.WxPayUnifiedOrderRequest; +import com.github.binarywang.wxpay.bean.request.WxPayUnifiedOrderV3Request; +import com.github.binarywang.wxpay.bean.result.WxPayUnifiedOrderV3Result; +import com.github.binarywang.wxpay.bean.result.enums.TradeTypeEnum; +import com.github.binarywang.wxpay.constant.WxPayConstants; +import com.github.binarywang.wxpay.exception.WxPayException; +import com.tashow.cloud.sdk.payment.dto.order.PayOrderRespDTO; +import com.tashow.cloud.sdk.payment.dto.order.PayOrderUnifiedReqDTO; +import com.tashow.cloud.sdk.payment.enums.channel.PayChannelEnum; +import com.tashow.cloud.sdk.payment.enums.order.PayOrderDisplayModeEnum; +import lombok.extern.slf4j.Slf4j; + +import static com.tashow.cloud.common.util.json.JsonUtils.toJsonString; + + +/** + * 微信支付【App 支付】的 PayClient 实现类 + * + * 文档:App 支付 + * + * // TODO 芋艿:未详细测试,因为手头没 App + * + * @author 芋道源码 + */ +@Slf4j +public class WxAppPayClient extends AbstractWxPayClient { + + public WxAppPayClient(Long channelId, WxPayClientConfig config) { + super(channelId, PayChannelEnum.WX_APP.getCode(), config); + } + + @Override + protected void doInit() { + super.doInit(WxPayConstants.TradeType.APP); + } + + @Override + protected PayOrderRespDTO doUnifiedOrderV2(PayOrderUnifiedReqDTO reqDTO) throws WxPayException { + // 构建 WxPayUnifiedOrderRequest 对象 + WxPayUnifiedOrderRequest request = buildPayUnifiedOrderRequestV2(reqDTO); + // 执行请求 + WxPayAppOrderResult response = client.createOrder(request); + + // 转换结果 + return PayOrderRespDTO.waitingOf(PayOrderDisplayModeEnum.APP.getMode(), toJsonString(response), + reqDTO.getOutTradeNo(), response); + } + + @Override + protected PayOrderRespDTO doUnifiedOrderV3(PayOrderUnifiedReqDTO reqDTO) throws WxPayException { + // 构建 WxPayUnifiedOrderV3Request 对象 + WxPayUnifiedOrderV3Request request = buildPayUnifiedOrderRequestV3(reqDTO); + // 执行请求 + WxPayUnifiedOrderV3Result.AppResult response = client.createOrderV3(TradeTypeEnum.APP, request); + + // 转换结果 + return PayOrderRespDTO.waitingOf(PayOrderDisplayModeEnum.APP.getMode(), toJsonString(response), + reqDTO.getOutTradeNo(), response); + } + +} diff --git a/tashow-sdk/tashow-sdk-payment/src/main/java/com/tashow/cloud/sdk/payment/client/impl/weixin/WxBarPayClient.java b/tashow-sdk/tashow-sdk-payment/src/main/java/com/tashow/cloud/sdk/payment/client/impl/weixin/WxBarPayClient.java new file mode 100644 index 0000000..21c17e8 --- /dev/null +++ b/tashow-sdk/tashow-sdk-payment/src/main/java/com/tashow/cloud/sdk/payment/client/impl/weixin/WxBarPayClient.java @@ -0,0 +1,108 @@ +package com.tashow.cloud.sdk.payment.client.impl.weixin; + +import cn.hutool.core.map.MapUtil; +import cn.hutool.core.thread.ThreadUtil; +import cn.hutool.core.util.StrUtil; +import com.github.binarywang.wxpay.bean.request.WxPayMicropayRequest; +import com.github.binarywang.wxpay.bean.result.WxPayMicropayResult; +import com.github.binarywang.wxpay.constant.WxPayConstants; +import com.github.binarywang.wxpay.exception.WxPayException; +import com.tashow.cloud.common.util.date.LocalDateTimeUtils; +import com.tashow.cloud.sdk.payment.dto.order.PayOrderRespDTO; +import com.tashow.cloud.sdk.payment.dto.order.PayOrderUnifiedReqDTO; +import com.tashow.cloud.sdk.payment.enums.channel.PayChannelEnum; +import com.tashow.cloud.sdk.payment.enums.order.PayOrderDisplayModeEnum; +import lombok.extern.slf4j.Slf4j; + +import java.time.Duration; +import java.time.LocalDateTime; +import java.util.concurrent.TimeUnit; + +import static com.tashow.cloud.common.exception.util.ServiceExceptionUtil.invalidParamException; +import static com.tashow.cloud.common.util.json.JsonUtils.toJsonString; + + +/** + * 微信支付【付款码支付】的 PayClient 实现类 + * + * 文档:付款码支付 + * + * @author 芋道源码 + */ +@Slf4j +public class WxBarPayClient extends AbstractWxPayClient { + + /** + * 微信付款码的过期时间 + */ + private static final Duration AUTH_CODE_EXPIRE = Duration.ofMinutes(3); + + public WxBarPayClient(Long channelId, WxPayClientConfig config) { + super(channelId, PayChannelEnum.WX_BAR.getCode(), config); + } + + @Override + protected void doInit() { + super.doInit(WxPayConstants.TradeType.MICROPAY); + } + + @Override + protected PayOrderRespDTO doUnifiedOrderV2(PayOrderUnifiedReqDTO reqDTO) throws WxPayException { + // 由于付款码需要不断轮询,所以需要在较短的时间完成支付 + LocalDateTime expireTime = LocalDateTimeUtils.addTime(AUTH_CODE_EXPIRE); + if (expireTime.isAfter(reqDTO.getExpireTime())) { + expireTime = reqDTO.getExpireTime(); + } + // 构建 WxPayMicropayRequest 对象 + WxPayMicropayRequest request = WxPayMicropayRequest.newBuilder() + .outTradeNo(reqDTO.getOutTradeNo()) + .body(reqDTO.getSubject()) + .detail(reqDTO.getBody()) + .totalFee(reqDTO.getPrice()) // 单位分 + .timeExpire(formatDateV2(expireTime)) + .spbillCreateIp(reqDTO.getUserIp()) + .authCode(getAuthCode(reqDTO)) + .build(); + // 执行请求,重试直到失败(过期),或者成功 + WxPayException lastWxPayException = null; + for (int i = 1; i < Byte.MAX_VALUE; i++) { + try { + WxPayMicropayResult response = client.micropay(request); + // 支付成功,例如说:1)用户输入了密码;2)用户免密支付 + return PayOrderRespDTO.successOf(response.getTransactionId(), response.getOpenid(), parseDateV2(response.getTimeEnd()), + response.getOutTradeNo(), response) + .setDisplayMode(PayOrderDisplayModeEnum.BAR_CODE.getMode()); + } catch (WxPayException ex) { + lastWxPayException = ex; + // 如果不满足这 3 种任一的,则直接抛出 WxPayException 异常,不仅需处理 + // 1. SYSTEMERROR:接口返回错误:请立即调用被扫订单结果查询API,查询当前订单状态,并根据订单的状态决定下一步的操作。 + // 2. USERPAYING:用户支付中,需要输入密码:等待 5 秒,然后调用被扫订单结果查询 API,查询当前订单的不同状态,决定下一步的操作。 + // 3. BANKERROR:银行系统异常:请立即调用被扫订单结果查询 API,查询当前订单的不同状态,决定下一步的操作。 + if (!StrUtil.equalsAny(ex.getErrCode(), "SYSTEMERROR", "USERPAYING", "BANKERROR")) { + throw ex; + } + // 等待 5 秒,继续下一轮重新发起支付 + log.info("[doUnifiedOrderV2][发起微信 Bar 支付第({})失败,等待下一轮重试,请求({}),响应({})]", i, + toJsonString(request), ex.getMessage()); + ThreadUtil.sleep(5, TimeUnit.SECONDS); + } + } + throw lastWxPayException; + } + + @Override + protected PayOrderRespDTO doUnifiedOrderV3(PayOrderUnifiedReqDTO reqDTO) throws WxPayException { + return doUnifiedOrderV2(reqDTO); + } + + // ========== 各种工具方法 ========== + + static String getAuthCode(PayOrderUnifiedReqDTO reqDTO) { + String authCode = MapUtil.getStr(reqDTO.getChannelExtras(), "authCode"); + if (StrUtil.isEmpty(authCode)) { + throw invalidParamException("支付请求的 authCode 不能为空!"); + } + return authCode; + } + +} diff --git a/tashow-sdk/tashow-sdk-payment/src/main/java/com/tashow/cloud/sdk/payment/client/impl/weixin/WxLitePayClient.java b/tashow-sdk/tashow-sdk-payment/src/main/java/com/tashow/cloud/sdk/payment/client/impl/weixin/WxLitePayClient.java new file mode 100644 index 0000000..0f764fb --- /dev/null +++ b/tashow-sdk/tashow-sdk-payment/src/main/java/com/tashow/cloud/sdk/payment/client/impl/weixin/WxLitePayClient.java @@ -0,0 +1,22 @@ +package com.tashow.cloud.sdk.payment.client.impl.weixin; + +import com.tashow.cloud.sdk.payment.enums.channel.PayChannelEnum; +import lombok.extern.slf4j.Slf4j; + +/** + * 微信支付【小程序】的 PayClient 实现类 + * + * 由于公众号和小程序的微信支付逻辑一致,所以直接进行继承 + * + * 文档:JSAPI 下单 + * + * @author zwy + */ +@Slf4j +public class WxLitePayClient extends WxPubPayClient { + + public WxLitePayClient(Long channelId, WxPayClientConfig config) { + super(channelId, PayChannelEnum.WX_LITE.getCode(), config); + } + +} diff --git a/tashow-sdk/tashow-sdk-payment/src/main/java/com/tashow/cloud/sdk/payment/client/impl/weixin/WxNativePayClient.java b/tashow-sdk/tashow-sdk-payment/src/main/java/com/tashow/cloud/sdk/payment/client/impl/weixin/WxNativePayClient.java new file mode 100644 index 0000000..55423fc --- /dev/null +++ b/tashow-sdk/tashow-sdk-payment/src/main/java/com/tashow/cloud/sdk/payment/client/impl/weixin/WxNativePayClient.java @@ -0,0 +1,59 @@ +package com.tashow.cloud.sdk.payment.client.impl.weixin; + +import com.github.binarywang.wxpay.bean.order.WxPayNativeOrderResult; +import com.github.binarywang.wxpay.bean.request.WxPayUnifiedOrderRequest; +import com.github.binarywang.wxpay.bean.request.WxPayUnifiedOrderV3Request; +import com.github.binarywang.wxpay.bean.result.enums.TradeTypeEnum; +import com.github.binarywang.wxpay.constant.WxPayConstants; +import com.github.binarywang.wxpay.exception.WxPayException; +import com.tashow.cloud.sdk.payment.dto.order.PayOrderRespDTO; +import com.tashow.cloud.sdk.payment.dto.order.PayOrderUnifiedReqDTO; +import com.tashow.cloud.sdk.payment.enums.channel.PayChannelEnum; +import com.tashow.cloud.sdk.payment.enums.order.PayOrderDisplayModeEnum; +import lombok.extern.slf4j.Slf4j; + +/** + * 微信支付【Native 二维码】的 PayClient 实现类 + * + * 文档:Native 下单 + * + * @author zwy + */ +@Slf4j +public class WxNativePayClient extends AbstractWxPayClient { + + public WxNativePayClient(Long channelId, WxPayClientConfig config) { + super(channelId, PayChannelEnum.WX_NATIVE.getCode(), config); + } + + @Override + protected void doInit() { + super.doInit(WxPayConstants.TradeType.NATIVE); + } + + @Override + protected PayOrderRespDTO doUnifiedOrderV2(PayOrderUnifiedReqDTO reqDTO) throws WxPayException { + // 构建 WxPayUnifiedOrderRequest 对象 + WxPayUnifiedOrderRequest request = buildPayUnifiedOrderRequestV2(reqDTO) + .setProductId(reqDTO.getOutTradeNo()); // V2 必须传递 productId,无需在微信配置。该参数在 V3 简化,无需传递! + // 执行请求 + WxPayNativeOrderResult response = client.createOrder(request); + + // 转换结果 + return PayOrderRespDTO.waitingOf(PayOrderDisplayModeEnum.QR_CODE.getMode(), response.getCodeUrl(), + reqDTO.getOutTradeNo(), response); + } + + @Override + protected PayOrderRespDTO doUnifiedOrderV3(PayOrderUnifiedReqDTO reqDTO) throws WxPayException { + // 构建 WxPayUnifiedOrderV3Request 对象 + WxPayUnifiedOrderV3Request request = buildPayUnifiedOrderRequestV3(reqDTO); + // 执行请求 + String response = client.createOrderV3(TradeTypeEnum.NATIVE, request); + + // 转换结果 + return PayOrderRespDTO.waitingOf(PayOrderDisplayModeEnum.QR_CODE.getMode(), response, + reqDTO.getOutTradeNo(), response); + } + +} diff --git a/tashow-sdk/tashow-sdk-payment/src/main/java/com/tashow/cloud/sdk/payment/client/impl/weixin/WxPayClientConfig.java b/tashow-sdk/tashow-sdk-payment/src/main/java/com/tashow/cloud/sdk/payment/client/impl/weixin/WxPayClientConfig.java new file mode 100644 index 0000000..a83a6f1 --- /dev/null +++ b/tashow-sdk/tashow-sdk-payment/src/main/java/com/tashow/cloud/sdk/payment/client/impl/weixin/WxPayClientConfig.java @@ -0,0 +1,102 @@ +package com.tashow.cloud.sdk.payment.client.impl.weixin; + +import com.tashow.cloud.common.util.validation.ValidationUtils; +import com.tashow.cloud.sdk.payment.client.PayClientConfig; +import jakarta.validation.Validator; +import jakarta.validation.constraints.NotBlank; +import lombok.Data; + +/** + * 微信支付的 PayClientConfig 实现类 + * 属性主要来自 {@link com.github.binarywang.wxpay.config.WxPayConfig} 的必要属性 + * + * @author 芋道源码 + */ +@Data +public class WxPayClientConfig implements PayClientConfig { + + /** + * API 版本 - V2 + * + * V2 协议说明 + */ + public static final String API_VERSION_V2 = "v2"; + /** + * API 版本 - V3 + * + * V3 协议说明 + */ + public static final String API_VERSION_V3 = "v3"; + + /** + * 公众号或者小程序的 appid + * + * 只有公众号或小程序需要该字段 + */ + @NotBlank(message = "APPID 不能为空", groups = {V2.class, V3.class}) + private String appId; + /** + * 商户号 + */ + @NotBlank(message = "商户号不能为空", groups = {V2.class, V3.class}) + private String mchId; + /** + * API 版本 + */ + @NotBlank(message = "API 版本不能为空", groups = {V2.class, V3.class}) + private String apiVersion; + + // ========== V2 版本的参数 ========== + + /** + * 商户密钥 + */ + @NotBlank(message = "商户密钥不能为空", groups = V2.class) + private String mchKey; + /** + * apiclient_cert.p12 证书文件的对应字符串【base64 格式】 + * + * 为什么采用 base64 格式?因为 p12 读取后是二进制,需要转换成 base64 格式才好传输和存储 + */ + @NotBlank(message = "apiclient_cert.p12 不能为空", groups = V2.class) + private String keyContent; + + // ========== V3 版本的参数 ========== + /** + * apiclient_key.pem 证书文件的对应字符串 + */ + @NotBlank(message = "apiclient_key 不能为空", groups = V3.class) + private String privateKeyContent; + /** + * apiV3 密钥值 + */ + @NotBlank(message = "apiV3 密钥值不能为空", groups = V3.class) + private String apiV3Key; + /** + * 证书序列号 + */ + @NotBlank(message = "证书序列号不能为空", groups = V3.class) + private String certSerialNo; + + @Deprecated // TODO 芋艿:V2.3.0 进行移除 + private String privateCertContent; + + /** + * 分组校验 v2版本 + */ + public interface V2 { + } + + /** + * 分组校验 v3版本 + */ + public interface V3 { + } + + @Override + public void validate(Validator validator) { + ValidationUtils.validate(validator, this, + API_VERSION_V2.equals(this.getApiVersion()) ? V2.class : V3.class); + } + +} diff --git a/tashow-sdk/tashow-sdk-payment/src/main/java/com/tashow/cloud/sdk/payment/client/impl/weixin/WxPubPayClient.java b/tashow-sdk/tashow-sdk-payment/src/main/java/com/tashow/cloud/sdk/payment/client/impl/weixin/WxPubPayClient.java new file mode 100644 index 0000000..d694052 --- /dev/null +++ b/tashow-sdk/tashow-sdk-payment/src/main/java/com/tashow/cloud/sdk/payment/client/impl/weixin/WxPubPayClient.java @@ -0,0 +1,81 @@ +package com.tashow.cloud.sdk.payment.client.impl.weixin; + +import cn.hutool.core.map.MapUtil; +import cn.hutool.core.util.StrUtil; +import com.github.binarywang.wxpay.bean.order.WxPayMpOrderResult; +import com.github.binarywang.wxpay.bean.request.WxPayUnifiedOrderRequest; +import com.github.binarywang.wxpay.bean.request.WxPayUnifiedOrderV3Request; +import com.github.binarywang.wxpay.bean.result.WxPayUnifiedOrderV3Result; +import com.github.binarywang.wxpay.bean.result.enums.TradeTypeEnum; +import com.github.binarywang.wxpay.constant.WxPayConstants; +import com.github.binarywang.wxpay.exception.WxPayException; +import com.tashow.cloud.sdk.payment.dto.order.PayOrderRespDTO; +import com.tashow.cloud.sdk.payment.dto.order.PayOrderUnifiedReqDTO; +import com.tashow.cloud.sdk.payment.enums.channel.PayChannelEnum; +import com.tashow.cloud.sdk.payment.enums.order.PayOrderDisplayModeEnum; +import lombok.extern.slf4j.Slf4j; + +import static com.tashow.cloud.common.exception.util.ServiceExceptionUtil.invalidParamException; +import static com.tashow.cloud.common.util.json.JsonUtils.toJsonString; + + +/** + * 微信支付(公众号)的 PayClient 实现类 + * + * 文档:JSAPI 下单 + * + * @author 芋道源码 + */ +@Slf4j +public class WxPubPayClient extends AbstractWxPayClient { + + public WxPubPayClient(Long channelId, WxPayClientConfig config) { + super(channelId, PayChannelEnum.WX_PUB.getCode(), config); + } + + protected WxPubPayClient(Long channelId, String channelCode, WxPayClientConfig config) { + super(channelId, channelCode, config); + } + + @Override + protected void doInit() { + super.doInit(WxPayConstants.TradeType.JSAPI); + } + + @Override + protected PayOrderRespDTO doUnifiedOrderV2(PayOrderUnifiedReqDTO reqDTO) throws WxPayException { + // 构建 WxPayUnifiedOrderRequest 对象 + WxPayUnifiedOrderRequest request = buildPayUnifiedOrderRequestV2(reqDTO) + .setOpenid(getOpenid(reqDTO)); + // 执行请求 + WxPayMpOrderResult response = client.createOrder(request); + + // 转换结果 + return PayOrderRespDTO.waitingOf(PayOrderDisplayModeEnum.APP.getMode(), toJsonString(response), + reqDTO.getOutTradeNo(), response); + } + + @Override + protected PayOrderRespDTO doUnifiedOrderV3(PayOrderUnifiedReqDTO reqDTO) throws WxPayException { + // 构建 WxPayUnifiedOrderRequest 对象 + WxPayUnifiedOrderV3Request request = buildPayUnifiedOrderRequestV3(reqDTO) + .setPayer(new WxPayUnifiedOrderV3Request.Payer().setOpenid(getOpenid(reqDTO))); + // 执行请求 + WxPayUnifiedOrderV3Result.JsapiResult response = client.createOrderV3(TradeTypeEnum.JSAPI, request); + + // 转换结果 + return PayOrderRespDTO.waitingOf(PayOrderDisplayModeEnum.APP.getMode(), toJsonString(response), + reqDTO.getOutTradeNo(), response); + } + + // ========== 各种工具方法 ========== + + static String getOpenid(PayOrderUnifiedReqDTO reqDTO) { + String openid = MapUtil.getStr(reqDTO.getChannelExtras(), "openid"); + if (StrUtil.isEmpty(openid)) { + throw invalidParamException("支付请求的 openid 不能为空!"); + } + return openid; + } + +} diff --git a/tashow-sdk/tashow-sdk-payment/src/main/java/com/tashow/cloud/sdk/payment/client/impl/weixin/WxWapPayClient.java b/tashow-sdk/tashow-sdk-payment/src/main/java/com/tashow/cloud/sdk/payment/client/impl/weixin/WxWapPayClient.java new file mode 100644 index 0000000..731f377 --- /dev/null +++ b/tashow-sdk/tashow-sdk-payment/src/main/java/com/tashow/cloud/sdk/payment/client/impl/weixin/WxWapPayClient.java @@ -0,0 +1,62 @@ +package com.tashow.cloud.sdk.payment.client.impl.weixin; + +import com.github.binarywang.wxpay.bean.order.WxPayMwebOrderResult; +import com.github.binarywang.wxpay.bean.request.WxPayUnifiedOrderRequest; +import com.github.binarywang.wxpay.bean.request.WxPayUnifiedOrderV3Request; +import com.github.binarywang.wxpay.bean.result.enums.TradeTypeEnum; +import com.github.binarywang.wxpay.constant.WxPayConstants; +import com.github.binarywang.wxpay.exception.WxPayException; +import com.tashow.cloud.sdk.payment.dto.order.PayOrderRespDTO; +import com.tashow.cloud.sdk.payment.dto.order.PayOrderUnifiedReqDTO; +import com.tashow.cloud.sdk.payment.enums.channel.PayChannelEnum; +import com.tashow.cloud.sdk.payment.enums.order.PayOrderDisplayModeEnum; +import lombok.extern.slf4j.Slf4j; + +/** + * 微信支付(H5 网页)的 PayClient 实现类 + * + * 文档:H5下单API + * + * @author YYQ + */ +@Slf4j +public class WxWapPayClient extends AbstractWxPayClient { + + public WxWapPayClient(Long channelId, WxPayClientConfig config) { + super(channelId, PayChannelEnum.WX_WAP.getCode(), config); + } + + protected WxWapPayClient(Long channelId, String channelCode, WxPayClientConfig config) { + super(channelId, channelCode, config); + } + + @Override + protected void doInit() { + super.doInit(WxPayConstants.TradeType.MWEB); + } + + @Override + protected PayOrderRespDTO doUnifiedOrderV2(PayOrderUnifiedReqDTO reqDTO) throws WxPayException { + // 构建 WxPayUnifiedOrderRequest 对象 + WxPayUnifiedOrderRequest request = buildPayUnifiedOrderRequestV2(reqDTO); + // 执行请求 + WxPayMwebOrderResult response = client.createOrder(request); + + // 转换结果 + return PayOrderRespDTO.waitingOf(PayOrderDisplayModeEnum.URL.getMode(), response.getMwebUrl(), + reqDTO.getOutTradeNo(), response); + } + + @Override + protected PayOrderRespDTO doUnifiedOrderV3(PayOrderUnifiedReqDTO reqDTO) throws WxPayException { + // 构建 WxPayUnifiedOrderRequest 对象 + WxPayUnifiedOrderV3Request request = buildPayUnifiedOrderRequestV3(reqDTO); + // 执行请求 + String response = client.createOrderV3(TradeTypeEnum.H5, request); + + // 转换结果 + return PayOrderRespDTO.waitingOf(PayOrderDisplayModeEnum.URL.getMode(), response, + reqDTO.getOutTradeNo(), response); + } + +} diff --git a/tashow-sdk/tashow-sdk-payment/src/main/java/com/tashow/cloud/sdk/payment/dto/order/PayOrderRespDTO.java b/tashow-sdk/tashow-sdk-payment/src/main/java/com/tashow/cloud/sdk/payment/dto/order/PayOrderRespDTO.java new file mode 100644 index 0000000..8e9411b --- /dev/null +++ b/tashow-sdk/tashow-sdk-payment/src/main/java/com/tashow/cloud/sdk/payment/dto/order/PayOrderRespDTO.java @@ -0,0 +1,141 @@ +package com.tashow.cloud.sdk.payment.dto.order; + +import com.tashow.cloud.sdk.payment.enums.order.PayOrderDisplayModeEnum; +import com.tashow.cloud.sdk.payment.enums.order.PayOrderStatusRespEnum; +import com.tashow.cloud.sdk.payment.exception.PayException; +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * 渠道支付订单 Response DTO + * + * @author 芋道源码 + */ +@Data +public class PayOrderRespDTO { + + /** + * 支付状态 + * + * 枚举:{@link PayOrderStatusRespEnum} + */ + private Integer status; + + /** + * 外部订单号 + * + * 对应 PayOrderExtensionDO 的 no 字段 + */ + private String outTradeNo; + + /** + * 支付渠道编号 + */ + private String channelOrderNo; + /** + * 支付渠道用户编号 + */ + private String channelUserId; + + /** + * 支付成功时间 + */ + private LocalDateTime successTime; + + /** + * 原始的同步/异步通知结果 + */ + private Object rawData; + + // ========== 主动发起支付时,会返回的字段 ========== + + /** + * 展示模式 + * + * 枚举 {@link PayOrderDisplayModeEnum} 类 + */ + private String displayMode; + /** + * 展示内容 + */ + private String displayContent; + + /** + * 调用渠道的错误码 + * + * 注意:这里返回的是业务异常,而是不系统异常。 + * 如果是系统异常,则会抛出 {@link PayException} + */ + private String channelErrorCode; + /** + * 调用渠道报错时,错误信息 + */ + private String channelErrorMsg; + + public PayOrderRespDTO() { + } + + /** + * 创建【WAITING】状态的订单返回 + */ + public static PayOrderRespDTO waitingOf(String displayMode, String displayContent, + String outTradeNo, Object rawData) { + PayOrderRespDTO respDTO = new PayOrderRespDTO(); + respDTO.status = PayOrderStatusRespEnum.WAITING.getStatus(); + respDTO.displayMode = displayMode; + respDTO.displayContent = displayContent; + // 相对通用的字段 + respDTO.outTradeNo = outTradeNo; + respDTO.rawData = rawData; + return respDTO; + } + + /** + * 创建【SUCCESS】状态的订单返回 + */ + public static PayOrderRespDTO successOf(String channelOrderNo, String channelUserId, LocalDateTime successTime, + String outTradeNo, Object rawData) { + PayOrderRespDTO respDTO = new PayOrderRespDTO(); + respDTO.status = PayOrderStatusRespEnum.SUCCESS.getStatus(); + respDTO.channelOrderNo = channelOrderNo; + respDTO.channelUserId = channelUserId; + respDTO.successTime = successTime; + // 相对通用的字段 + respDTO.outTradeNo = outTradeNo; + respDTO.rawData = rawData; + return respDTO; + } + + /** + * 创建指定状态的订单返回,适合支付渠道回调时 + */ + public static PayOrderRespDTO of(Integer status, String channelOrderNo, String channelUserId, LocalDateTime successTime, + String outTradeNo, Object rawData) { + PayOrderRespDTO respDTO = new PayOrderRespDTO(); + respDTO.status = status; + respDTO.channelOrderNo = channelOrderNo; + respDTO.channelUserId = channelUserId; + respDTO.successTime = successTime; + // 相对通用的字段 + respDTO.outTradeNo = outTradeNo; + respDTO.rawData = rawData; + return respDTO; + } + + /** + * 创建【CLOSED】状态的订单返回,适合调用支付渠道失败时 + */ + public static PayOrderRespDTO closedOf(String channelErrorCode, String channelErrorMsg, + String outTradeNo, Object rawData) { + PayOrderRespDTO respDTO = new PayOrderRespDTO(); + respDTO.status = PayOrderStatusRespEnum.CLOSED.getStatus(); + respDTO.channelErrorCode = channelErrorCode; + respDTO.channelErrorMsg = channelErrorMsg; + // 相对通用的字段 + respDTO.outTradeNo = outTradeNo; + respDTO.rawData = rawData; + return respDTO; + } + +} diff --git a/tashow-sdk/tashow-sdk-payment/src/main/java/com/tashow/cloud/sdk/payment/dto/order/PayOrderUnifiedReqDTO.java b/tashow-sdk/tashow-sdk-payment/src/main/java/com/tashow/cloud/sdk/payment/dto/order/PayOrderUnifiedReqDTO.java new file mode 100644 index 0000000..f9aacc5 --- /dev/null +++ b/tashow-sdk/tashow-sdk-payment/src/main/java/com/tashow/cloud/sdk/payment/dto/order/PayOrderUnifiedReqDTO.java @@ -0,0 +1,86 @@ +package com.tashow.cloud.sdk.payment.dto.order; + +import com.tashow.cloud.sdk.payment.enums.order.PayOrderDisplayModeEnum; +import jakarta.validation.constraints.DecimalMin; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +import java.time.LocalDateTime; +import java.util.Map; + +/** + * 统一下单 Request DTO + * + * @author 芋道源码 + */ +@Data +public class PayOrderUnifiedReqDTO { + + /** + * 用户 IP + */ + @NotEmpty(message = "用户 IP 不能为空") + private String userIp; + + // ========== 商户相关字段 ========== + + /** + * 外部订单号 + * + * 对应 PayOrderExtensionDO 的 no 字段 + */ + @NotEmpty(message = "外部订单编号不能为空") + private String outTradeNo; + /** + * 商品标题 + */ + @NotEmpty(message = "商品标题不能为空") + private String subject; + /** + * 商品描述信息 + */ + private String body; + /** + * 支付结果的 notify 回调地址 + */ + @NotEmpty(message = "支付结果的回调地址不能为空") + private String notifyUrl; + /** + * 支付结果的 return 回调地址 + */ + private String returnUrl; + + // ========== 订单相关字段 ========== + + /** + * 支付金额,单位:分 + */ + @NotNull(message = "支付金额不能为空") + @DecimalMin(value = "0", inclusive = false, message = "支付金额必须大于零") + private Integer price; + + /** + * 支付过期时间 + */ + @NotNull(message = "支付过期时间不能为空") + private LocalDateTime expireTime; + + // ========== 拓展参数 ========== + /** + * 支付渠道的额外参数 + * + * 例如说,微信公众号需要传递 openid 参数 + */ + private Map channelExtras; + + /** + * 展示模式 + * + * 如果不传递,则每个支付渠道使用默认的方式 + * + * 枚举 {@link PayOrderDisplayModeEnum} + */ + private String displayMode; + +} diff --git a/tashow-sdk/tashow-sdk-payment/src/main/java/com/tashow/cloud/sdk/payment/dto/refund/PayRefundRespDTO.java b/tashow-sdk/tashow-sdk-payment/src/main/java/com/tashow/cloud/sdk/payment/dto/refund/PayRefundRespDTO.java new file mode 100644 index 0000000..5294b63 --- /dev/null +++ b/tashow-sdk/tashow-sdk-payment/src/main/java/com/tashow/cloud/sdk/payment/dto/refund/PayRefundRespDTO.java @@ -0,0 +1,115 @@ +package com.tashow.cloud.sdk.payment.dto.refund; + +import com.tashow.cloud.sdk.payment.enums.refund.PayRefundStatusRespEnum; +import com.tashow.cloud.sdk.payment.exception.PayException; +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * 渠道退款订单 Response DTO + * + * @author jason + */ +@Data +public class PayRefundRespDTO { + + /** + * 退款状态 + * + * 枚举 {@link PayRefundStatusRespEnum} + */ + private Integer status; + + /** + * 外部退款号 + * + * 对应 PayRefundDO 的 no 字段 + */ + private String outRefundNo; + + /** + * 渠道退款单号 + * + * 对应 PayRefundDO.channelRefundNo 字段 + */ + private String channelRefundNo; + + /** + * 退款成功时间 + */ + private LocalDateTime successTime; + + /** + * 原始的异步通知结果 + */ + private Object rawData; + + /** + * 调用渠道的错误码 + * + * 注意:这里返回的是业务异常,而是不系统异常。 + * 如果是系统异常,则会抛出 {@link PayException} + */ + private String channelErrorCode; + /** + * 调用渠道报错时,错误信息 + */ + private String channelErrorMsg; + + private PayRefundRespDTO() { + } + + /** + * 创建【WAITING】状态的退款返回 + */ + public static PayRefundRespDTO waitingOf(String channelRefundNo, + String outRefundNo, Object rawData) { + PayRefundRespDTO respDTO = new PayRefundRespDTO(); + respDTO.status = PayRefundStatusRespEnum.WAITING.getStatus(); + respDTO.channelRefundNo = channelRefundNo; + // 相对通用的字段 + respDTO.outRefundNo = outRefundNo; + respDTO.rawData = rawData; + return respDTO; + } + + /** + * 创建【SUCCESS】状态的退款返回 + */ + public static PayRefundRespDTO successOf(String channelRefundNo, LocalDateTime successTime, + String outRefundNo, Object rawData) { + PayRefundRespDTO respDTO = new PayRefundRespDTO(); + respDTO.status = PayRefundStatusRespEnum.SUCCESS.getStatus(); + respDTO.channelRefundNo = channelRefundNo; + respDTO.successTime = successTime; + // 相对通用的字段 + respDTO.outRefundNo = outRefundNo; + respDTO.rawData = rawData; + return respDTO; + } + + /** + * 创建【FAILURE】状态的退款返回 + */ + public static PayRefundRespDTO failureOf(String outRefundNo, Object rawData) { + return failureOf(null, null, + outRefundNo, rawData); + } + + /** + * 创建【FAILURE】状态的退款返回 + */ + public static PayRefundRespDTO failureOf(String channelErrorCode, String channelErrorMsg, + String outRefundNo, Object rawData) { + PayRefundRespDTO respDTO = new PayRefundRespDTO(); + respDTO.status = PayRefundStatusRespEnum.FAILURE.getStatus(); + respDTO.channelErrorCode = channelErrorCode; + respDTO.channelErrorMsg = channelErrorMsg; + // 相对通用的字段 + respDTO.outRefundNo = outRefundNo; + respDTO.rawData = rawData; + return respDTO; + } + +} diff --git a/tashow-sdk/tashow-sdk-payment/src/main/java/com/tashow/cloud/sdk/payment/dto/refund/PayRefundUnifiedReqDTO.java b/tashow-sdk/tashow-sdk-payment/src/main/java/com/tashow/cloud/sdk/payment/dto/refund/PayRefundUnifiedReqDTO.java new file mode 100644 index 0000000..7f0b7b5 --- /dev/null +++ b/tashow-sdk/tashow-sdk-payment/src/main/java/com/tashow/cloud/sdk/payment/dto/refund/PayRefundUnifiedReqDTO.java @@ -0,0 +1,67 @@ +package com.tashow.cloud.sdk.payment.dto.refund; + +import jakarta.validation.constraints.DecimalMin; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.Accessors; + +/** + * 统一 退款 Request DTO + * + * @author jason + */ +@Accessors(chain = true) +@Builder +@NoArgsConstructor +@AllArgsConstructor +@Data +public class PayRefundUnifiedReqDTO { + + /** + * 外部订单号 + * + * 对应 PayOrderExtensionDO 的 no 字段 + */ + @NotEmpty(message = "外部订单编号不能为空") + private String outTradeNo; + + /** + * 外部退款号 + * + * 对应 PayRefundDO 的 no 字段 + */ + @NotEmpty(message = "退款请求单号不能为空") + private String outRefundNo; + + /** + * 退款原因 + */ + @NotEmpty(message = "退款原因不能为空") + private String reason; + + /** + * 支付金额,单位:分 + * + * 目前微信支付在退款的时候,必须传递该字段 + */ + @NotNull(message = "支付金额不能为空") + @DecimalMin(value = "0", inclusive = false, message = "支付金额必须大于零") + private Integer payPrice; + /** + * 退款金额,单位:分 + */ + @NotNull(message = "退款金额不能为空") + @DecimalMin(value = "0", inclusive = false, message = "支付金额必须大于零") + private Integer refundPrice; + + /** + * 退款结果的 notify 回调地址 + */ + @NotEmpty(message = "支付结果的回调地址不能为空") + private String notifyUrl; + +} diff --git a/tashow-sdk/tashow-sdk-payment/src/main/java/com/tashow/cloud/sdk/payment/dto/transfer/PayTransferRespDTO.java b/tashow-sdk/tashow-sdk-payment/src/main/java/com/tashow/cloud/sdk/payment/dto/transfer/PayTransferRespDTO.java new file mode 100644 index 0000000..6e35d14 --- /dev/null +++ b/tashow-sdk/tashow-sdk-payment/src/main/java/com/tashow/cloud/sdk/payment/dto/transfer/PayTransferRespDTO.java @@ -0,0 +1,109 @@ +package com.tashow.cloud.sdk.payment.dto.transfer; + +import com.tashow.cloud.sdk.payment.enums.transfer.PayTransferStatusRespEnum; +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * 统一转账 Response DTO + * + * @author jason + */ +@Data +public class PayTransferRespDTO { + + /** + * 转账状态 + * + * 关联 {@link PayTransferStatusRespEnum#getStatus()} + */ + private Integer status; + + /** + * 外部转账单号 + * + */ + private String outTransferNo; + + /** + * 支付渠道编号 + */ + private String channelTransferNo; + + /** + * 支付成功时间 + */ + private LocalDateTime successTime; + + /** + * 原始的返回结果 + */ + private Object rawData; + + /** + * 调用渠道的错误码 + */ + private String channelErrorCode; + /** + * 调用渠道报错时,错误信息 + */ + private String channelErrorMsg; + + /** + * 创建【WAITING】状态的转账返回 + */ + public static PayTransferRespDTO waitingOf(String channelTransferNo, + String outTransferNo, Object rawData) { + PayTransferRespDTO respDTO = new PayTransferRespDTO(); + respDTO.status = PayTransferStatusRespEnum.WAITING.getStatus(); + respDTO.channelTransferNo = channelTransferNo; + respDTO.outTransferNo = outTransferNo; + respDTO.rawData = rawData; + return respDTO; + } + + /** + * 创建【IN_PROGRESS】状态的转账返回 + */ + public static PayTransferRespDTO dealingOf(String channelTransferNo, + String outTransferNo, Object rawData) { + PayTransferRespDTO respDTO = new PayTransferRespDTO(); + respDTO.status = PayTransferStatusRespEnum.IN_PROGRESS.getStatus(); + respDTO.channelTransferNo = channelTransferNo; + respDTO.outTransferNo = outTransferNo; + respDTO.rawData = rawData; + return respDTO; + } + + /** + * 创建【CLOSED】状态的转账返回 + */ + public static PayTransferRespDTO closedOf(String channelErrorCode, String channelErrorMsg, + String outTransferNo, Object rawData) { + PayTransferRespDTO respDTO = new PayTransferRespDTO(); + respDTO.status = PayTransferStatusRespEnum.CLOSED.getStatus(); + respDTO.channelErrorCode = channelErrorCode; + respDTO.channelErrorMsg = channelErrorMsg; + // 相对通用的字段 + respDTO.outTransferNo = outTransferNo; + respDTO.rawData = rawData; + return respDTO; + } + + /** + * 创建【SUCCESS】状态的转账返回 + */ + public static PayTransferRespDTO successOf(String channelTransferNo, LocalDateTime successTime, + String outTransferNo, Object rawData) { + PayTransferRespDTO respDTO = new PayTransferRespDTO(); + respDTO.status = PayTransferStatusRespEnum.SUCCESS.getStatus(); + respDTO.channelTransferNo = channelTransferNo; + respDTO.successTime = successTime; + // 相对通用的字段 + respDTO.outTransferNo = outTransferNo; + respDTO.rawData = rawData; + return respDTO; + } + +} diff --git a/tashow-sdk/tashow-sdk-payment/src/main/java/com/tashow/cloud/sdk/payment/dto/transfer/PayTransferUnifiedReqDTO.java b/tashow-sdk/tashow-sdk-payment/src/main/java/com/tashow/cloud/sdk/payment/dto/transfer/PayTransferUnifiedReqDTO.java new file mode 100644 index 0000000..aec9e5d --- /dev/null +++ b/tashow-sdk/tashow-sdk-payment/src/main/java/com/tashow/cloud/sdk/payment/dto/transfer/PayTransferUnifiedReqDTO.java @@ -0,0 +1,84 @@ +package com.tashow.cloud.sdk.payment.dto.transfer; + +import com.tashow.cloud.common.validation.InEnum; +import com.tashow.cloud.sdk.payment.enums.transfer.PayTransferTypeEnum; +import jakarta.validation.constraints.Min; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import lombok.Data; +import org.hibernate.validator.constraints.URL; + +import java.util.Map; + + +/** + * 统一转账 Request DTO + * + * @author jason + */ +@Data +public class PayTransferUnifiedReqDTO { + + /** + * 转账类型 + * + * 关联 {@link PayTransferTypeEnum#getType()} + */ + @NotNull(message = "转账类型不能为空") + @InEnum(PayTransferTypeEnum.class) + private Integer type; + + /** + * 用户 IP + */ + @NotEmpty(message = "用户 IP 不能为空") + private String userIp; + + @NotEmpty(message = "外部转账单编号不能为空") + private String outTransferNo; + + /** + * 转账金额,单位:分 + */ + @NotNull(message = "转账金额不能为空") + @Min(value = 1, message = "转账金额必须大于零") + private Integer price; + + /** + * 转账标题 + */ + @NotEmpty(message = "转账标题不能为空") + private String subject; + + /** + * 收款人姓名 + */ + @NotBlank(message = "收款人姓名不能为空", groups = {PayTransferTypeEnum.Alipay.class}) + private String userName; + + /** + * 支付宝登录号 + */ + @NotBlank(message = "支付宝登录号不能为空", groups = {PayTransferTypeEnum.Alipay.class}) + private String alipayLogonId; + + /** + * 微信 openId + */ + @NotBlank(message = "微信 openId 不能为空", groups = {PayTransferTypeEnum.WxPay.class}) + private String openid; + + /** + * 支付渠道的额外参数 + */ + private Map channelExtras; + + /** + * 转账结果的 notify 回调地址 + */ + @NotEmpty(message = "转账结果的回调地址不能为空") + @URL(message = "转账结果的 notify 回调地址必须是 URL 格式") + private String notifyUrl; + +} diff --git a/tashow-sdk/tashow-sdk-payment/src/main/java/com/tashow/cloud/sdk/payment/dto/transfer/WxPayTransferPartnerNotifyV3Result.java b/tashow-sdk/tashow-sdk-payment/src/main/java/com/tashow/cloud/sdk/payment/dto/transfer/WxPayTransferPartnerNotifyV3Result.java new file mode 100644 index 0000000..b9e6fbb --- /dev/null +++ b/tashow-sdk/tashow-sdk-payment/src/main/java/com/tashow/cloud/sdk/payment/dto/transfer/WxPayTransferPartnerNotifyV3Result.java @@ -0,0 +1,129 @@ +package com.tashow.cloud.sdk.payment.dto.transfer; + +import com.github.binarywang.wxpay.bean.notify.OriginNotifyResponse; +import com.github.binarywang.wxpay.bean.notify.WxPayBaseNotifyV3Result; +import com.google.gson.annotations.SerializedName; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + +// TODO @luchi:这个可以复用 wxjava 里的类么? +@NoArgsConstructor +public class WxPayTransferPartnerNotifyV3Result implements Serializable, WxPayBaseNotifyV3Result { + + private static final long serialVersionUID = -1L; + + /** + * 源数据 + */ + private OriginNotifyResponse rawData; + + /** + * 解密后的数据 + */ + private TransferNotifyResult result; + + @Override + public void setRawData(OriginNotifyResponse rawData) { + this.rawData = rawData; + } + + @Override + public void setResult(TransferNotifyResult data) { + this.result = data; + } + + public TransferNotifyResult getResult() { + return result; + } + + public OriginNotifyResponse getRawData() { + return rawData; + } + + @Data + @NoArgsConstructor + public static class TransferNotifyResult implements Serializable { + private static final long serialVersionUID = 1L; + + /*********************** 公共字段 ******************** + + /** + * 商家批次单号 + */ + @SerializedName(value = "out_batch_no") + protected String outBatchNo; + + /** + * 微信批次单号 + */ + @SerializedName(value = "batch_id") + protected String batchId; + + /** + * 批次状态 + */ + @SerializedName(value = "batch_status") + protected String batchStatus; + + /** + * 批次总笔数 + */ + @SerializedName(value = "total_num") + protected Integer totalNum; + + /** + * 批次总金额 + */ + @SerializedName(value = "total_amount") + protected Integer totalAmount; + + /** + * 批次更新时间 + */ + @SerializedName(value = "update_time") + private String updateTime; + + /*********************** FINISHED ******************** + + /** + * 转账成功金额 + */ + @SerializedName(value = "success_amount") + protected Integer successAmount; + + /** + * 转账成功笔数 + */ + @SerializedName(value = "success_num") + protected Integer successNum; + + /** + * 转账失败金额 + */ + @SerializedName(value = "fail_amount") + protected Integer failAmount; + + /** + * 转账失败笔数 + */ + @SerializedName(value = "fail_num") + protected Integer failNum; + + /*********************** CLOSED ******************** + + /** + * 商户号 + */ + @SerializedName(value = "mchid") + protected String mchId; + + /** + * 批次关闭原因 + */ + @SerializedName(value = "close_reason") + protected String closeReason; + + } +} diff --git a/tashow-sdk/tashow-sdk-payment/src/main/java/com/tashow/cloud/sdk/payment/enums/channel/PayChannelEnum.java b/tashow-sdk/tashow-sdk-payment/src/main/java/com/tashow/cloud/sdk/payment/enums/channel/PayChannelEnum.java new file mode 100644 index 0000000..efb0eff --- /dev/null +++ b/tashow-sdk/tashow-sdk-payment/src/main/java/com/tashow/cloud/sdk/payment/enums/channel/PayChannelEnum.java @@ -0,0 +1,66 @@ +package com.tashow.cloud.sdk.payment.enums.channel; + +import cn.hutool.core.util.ArrayUtil; +import com.tashow.cloud.sdk.payment.client.PayClientConfig; +import com.tashow.cloud.sdk.payment.client.impl.alipay.AlipayPayClientConfig; +import com.tashow.cloud.sdk.payment.client.impl.weixin.WxPayClientConfig; +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * 支付渠道的编码的枚举 + * + * @author 芋道源码 + */ +@Getter +@AllArgsConstructor +public enum PayChannelEnum { + + WX_PUB("wx_pub", "微信 JSAPI 支付", WxPayClientConfig.class), // 公众号网页 + WX_LITE("wx_lite", "微信小程序支付", WxPayClientConfig.class), + WX_APP("wx_app", "微信 App 支付", WxPayClientConfig.class), + WX_NATIVE("wx_native", "微信 Native 支付", WxPayClientConfig.class), + WX_WAP("wx_wap", "微信 Wap 网站支付", WxPayClientConfig.class), // H5 网页 + WX_BAR("wx_bar", "微信付款码支付", WxPayClientConfig.class), + + ALIPAY_PC("alipay_pc", "支付宝 PC 网站支付", AlipayPayClientConfig.class), + ALIPAY_WAP("alipay_wap", "支付宝 Wap 网站支付", AlipayPayClientConfig.class), + ALIPAY_APP("alipay_app", "支付宝App 支付", AlipayPayClientConfig.class), + ALIPAY_QR("alipay_qr", "支付宝扫码支付", AlipayPayClientConfig.class), + ALIPAY_BAR("alipay_bar", "支付宝条码支付", AlipayPayClientConfig.class), + ; + + /** + * 编码 + * + * 参考 支付渠道属性值 + */ + private final String code; + /** + * 名字 + */ + private final String name; + + /** + * 配置类 + */ + private final Class configClass; + + /** + * 微信支付 + */ + public static final String WECHAT = "WECHAT"; + + /** + * 支付宝支付 + */ + public static final String ALIPAY = "ALIPAY"; + + public static PayChannelEnum getByCode(String code) { + return ArrayUtil.firstMatch(o -> o.getCode().equals(code), values()); + } + + public static boolean isAlipay(String channelCode) { + return channelCode != null && channelCode.startsWith("alipay"); + } +} diff --git a/tashow-sdk/tashow-sdk-payment/src/main/java/com/tashow/cloud/sdk/payment/enums/order/PayOrderDisplayModeEnum.java b/tashow-sdk/tashow-sdk-payment/src/main/java/com/tashow/cloud/sdk/payment/enums/order/PayOrderDisplayModeEnum.java new file mode 100644 index 0000000..de53219 --- /dev/null +++ b/tashow-sdk/tashow-sdk-payment/src/main/java/com/tashow/cloud/sdk/payment/enums/order/PayOrderDisplayModeEnum.java @@ -0,0 +1,29 @@ +package com.tashow.cloud.sdk.payment.enums.order; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * 支付 UI 展示模式 + * + * @author 芋道源码 + */ +@Getter +@AllArgsConstructor +public enum PayOrderDisplayModeEnum { + + URL("url"), // Redirect 跳转链接的方式 + IFRAME("iframe"), // IFrame 内嵌链接的方式【目前暂时用不到】 + FORM("form"), // HTML 表单提交 + QR_CODE("qr_code"), // 二维码的文字内容 + QR_CODE_URL("qr_code_url"), // 二维码的图片链接 + BAR_CODE("bar_code"), // 条形码 + APP("app"), // 应用:Android、iOS、微信小程序、微信公众号等,需要做自定义处理的 + ; + + /** + * 展示模式 + */ + private final String mode; + +} diff --git a/tashow-sdk/tashow-sdk-payment/src/main/java/com/tashow/cloud/sdk/payment/enums/order/PayOrderStatusRespEnum.java b/tashow-sdk/tashow-sdk-payment/src/main/java/com/tashow/cloud/sdk/payment/enums/order/PayOrderStatusRespEnum.java new file mode 100644 index 0000000..4cd1c2f --- /dev/null +++ b/tashow-sdk/tashow-sdk-payment/src/main/java/com/tashow/cloud/sdk/payment/enums/order/PayOrderStatusRespEnum.java @@ -0,0 +1,56 @@ +package com.tashow.cloud.sdk.payment.enums.order; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +import java.util.Objects; + +/** + * 渠道的支付状态枚举 + * + * @author 芋道源码 + */ +@Getter +@AllArgsConstructor +public enum PayOrderStatusRespEnum { + + WAITING(0, "未支付"), + SUCCESS(10, "支付成功"), + REFUND(20, "已退款"), + CLOSED(30, "支付关闭"), + ; + + private final Integer status; + private final String name; + + /** + * 判断是否支付成功 + * + * @param status 状态 + * @return 是否支付成功 + */ + public static boolean isSuccess(Integer status) { + return Objects.equals(status, SUCCESS.getStatus()); + } + + /** + * 判断是否已退款 + * + * @param status 状态 + * @return 是否支付成功 + */ + public static boolean isRefund(Integer status) { + return Objects.equals(status, REFUND.getStatus()); + } + + /** + * 判断是否支付关闭 + * + * @param status 状态 + * @return 是否支付关闭 + */ + public static boolean isClosed(Integer status) { + return Objects.equals(status, CLOSED.getStatus()); + } + +} diff --git a/tashow-sdk/tashow-sdk-payment/src/main/java/com/tashow/cloud/sdk/payment/enums/refund/PayRefundStatusRespEnum.java b/tashow-sdk/tashow-sdk-payment/src/main/java/com/tashow/cloud/sdk/payment/enums/refund/PayRefundStatusRespEnum.java new file mode 100644 index 0000000..e6648cb --- /dev/null +++ b/tashow-sdk/tashow-sdk-payment/src/main/java/com/tashow/cloud/sdk/payment/enums/refund/PayRefundStatusRespEnum.java @@ -0,0 +1,32 @@ +package com.tashow.cloud.sdk.payment.enums.refund; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +import java.util.Objects; + +/** + * 渠道的退款状态枚举 + * + * @author jason + */ +@Getter +@AllArgsConstructor +public enum PayRefundStatusRespEnum { + + WAITING(0, "等待退款"), + SUCCESS(10, "退款成功"), + FAILURE(20, "退款失败"); + + private final Integer status; + private final String name; + + public static boolean isSuccess(Integer status) { + return Objects.equals(status, SUCCESS.getStatus()); + } + + public static boolean isFailure(Integer status) { + return Objects.equals(status, FAILURE.getStatus()); + } + +} diff --git a/tashow-sdk/tashow-sdk-payment/src/main/java/com/tashow/cloud/sdk/payment/enums/transfer/PayTransferStatusRespEnum.java b/tashow-sdk/tashow-sdk-payment/src/main/java/com/tashow/cloud/sdk/payment/enums/transfer/PayTransferStatusRespEnum.java new file mode 100644 index 0000000..c1ce4df --- /dev/null +++ b/tashow-sdk/tashow-sdk-payment/src/main/java/com/tashow/cloud/sdk/payment/enums/transfer/PayTransferStatusRespEnum.java @@ -0,0 +1,45 @@ +package com.tashow.cloud.sdk.payment.enums.transfer; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +import java.util.Objects; + +/** + * 渠道的转账状态枚举 + * + * @author jason + */ +@Getter +@AllArgsConstructor +public enum PayTransferStatusRespEnum { + + WAITING(0, "等待转账"), + + /** + * TODO 转账到银行卡. 会有T+0 T+1 到账的请情况。 还未实现 + * TODO @jason:可以看看其它开源项目,针对这个场景,处理策略是怎么样的?例如说,每天主动轮询?这个状态的单子? + */ + IN_PROGRESS(10, "转账进行中"), + + SUCCESS(20, "转账成功"), + /** + * 转账关闭 (失败,或者其它情况) + */ + CLOSED(30, "转账关闭"); + + private final Integer status; + private final String name; + + public static boolean isSuccess(Integer status) { + return Objects.equals(status, SUCCESS.getStatus()); + } + + public static boolean isClosed(Integer status) { + return Objects.equals(status, CLOSED.getStatus()); + } + + public static boolean isInProgress(Integer status) { + return Objects.equals(status, IN_PROGRESS.getStatus()); + } +} diff --git a/tashow-sdk/tashow-sdk-payment/src/main/java/com/tashow/cloud/sdk/payment/enums/transfer/PayTransferTypeEnum.java b/tashow-sdk/tashow-sdk-payment/src/main/java/com/tashow/cloud/sdk/payment/enums/transfer/PayTransferTypeEnum.java new file mode 100644 index 0000000..b0f9c3d --- /dev/null +++ b/tashow-sdk/tashow-sdk-payment/src/main/java/com/tashow/cloud/sdk/payment/enums/transfer/PayTransferTypeEnum.java @@ -0,0 +1,44 @@ +package com.tashow.cloud.sdk.payment.enums.transfer; + +import cn.hutool.core.util.ArrayUtil; +import com.tashow.cloud.common.core.ArrayValuable; +import lombok.AllArgsConstructor; +import lombok.Getter; + +import java.util.Arrays; + +/** + * 转账类型枚举 + * + * @author jason + */ +@AllArgsConstructor +@Getter +public enum PayTransferTypeEnum implements ArrayValuable { + + ALIPAY_BALANCE(1, "支付宝余额"), + WX_BALANCE(2, "微信余额"), + BANK_CARD(3, "银行卡"), + WALLET_BALANCE(4, "钱包余额"); + + public interface WxPay { + } + + public interface Alipay { + } + + private final Integer type; + private final String name; + + public static final Integer[] ARRAYS = Arrays.stream(values()).map(PayTransferTypeEnum::getType).toArray(Integer[]::new); + + @Override + public Integer[] array() { + return ARRAYS; + } + + public static PayTransferTypeEnum typeOf(Integer type) { + return ArrayUtil.firstMatch(item -> item.getType().equals(type), values()); + } + +} diff --git a/tashow-sdk/tashow-sdk-payment/src/main/java/com/tashow/cloud/sdk/payment/exception/PayException.java b/tashow-sdk/tashow-sdk-payment/src/main/java/com/tashow/cloud/sdk/payment/exception/PayException.java new file mode 100644 index 0000000..a6cfc6d --- /dev/null +++ b/tashow-sdk/tashow-sdk-payment/src/main/java/com/tashow/cloud/sdk/payment/exception/PayException.java @@ -0,0 +1,17 @@ +package com.tashow.cloud.sdk.payment.exception; + +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 支付系统异常 Exception + */ +@Data +@EqualsAndHashCode(callSuper = true) +public class PayException extends RuntimeException { + + public PayException(Throwable cause) { + super(cause); + } + +} diff --git a/tashow-sdk/tashow-sdk-payment/src/main/java/com/tashow/cloud/sdk/payment/package-info.java b/tashow-sdk/tashow-sdk-payment/src/main/java/com/tashow/cloud/sdk/payment/package-info.java new file mode 100644 index 0000000..5b4fda8 --- /dev/null +++ b/tashow-sdk/tashow-sdk-payment/src/main/java/com/tashow/cloud/sdk/payment/package-info.java @@ -0,0 +1 @@ +package com.tashow.cloud.sdk.payment; \ No newline at end of file