Browse Source

添加了dubbo的模块,对接收藏与取消收藏

杨兴卓 1 month ago
parent
commit
cb8f374c7d
69 changed files with 3263 additions and 143 deletions
  1. 13 1
      pom.xml
  2. 25 0
      xyzc-api/pom.xml
  3. 37 0
      xyzc-api/xyzc-api-bom/pom.xml
  4. 29 0
      xyzc-api/xyzc-api-resource/pom.xml
  5. 39 0
      xyzc-api/xyzc-api-resource/src/main/java/com/xyzc/resource/api/RemoteFileService.java
  6. 53 0
      xyzc-api/xyzc-api-resource/src/main/java/com/xyzc/resource/api/RemoteFileServiceMock.java
  7. 44 0
      xyzc-api/xyzc-api-resource/src/main/java/com/xyzc/resource/api/domain/RemoteFile.java
  8. 36 0
      xyzc-api/xyzc-api-resource/src/main/java/com/xyzc/resource/api/domain/RemoteSms.java
  9. 2 0
      xyzc-common/pom.xml
  10. 15 0
      xyzc-common/xyzc-common-bom/pom.xml
  11. 0 4
      xyzc-common/xyzc-common-core/src/main/java/com/xyzc/common/core/config/JacksonConfiguration.java
  12. 92 0
      xyzc-common/xyzc-common-core/src/main/java/com/xyzc/common/core/constant/Const.java
  13. 40 0
      xyzc-common/xyzc-common-core/src/main/java/com/xyzc/common/core/constant/GlobalConst.java
  14. 79 0
      xyzc-common/xyzc-common-dubbo/pom.xml
  15. 576 0
      xyzc-common/xyzc-common-dubbo/src/main/java/com/apache/dubbo/metadata/report/support/AbstractMetadataReport.java
  16. 531 0
      xyzc-common/xyzc-common-dubbo/src/main/java/com/apache/dubbo/metadata/store/redis/RedisMetadataReport.java
  17. 59 0
      xyzc-common/xyzc-common-dubbo/src/main/java/com/xyzc/common/dubbo/config/CustomBeanFactoryPostProcessor.java
  18. 26 0
      xyzc-common/xyzc-common-dubbo/src/main/java/com/xyzc/common/dubbo/config/DubboConfiguration.java
  19. 28 0
      xyzc-common/xyzc-common-dubbo/src/main/java/com/xyzc/common/dubbo/enumd/RequestLogEnum.java
  20. 84 0
      xyzc-common/xyzc-common-dubbo/src/main/java/com/xyzc/common/dubbo/filter/DubboRequestFilter.java
  21. 28 0
      xyzc-common/xyzc-common-dubbo/src/main/java/com/xyzc/common/dubbo/properties/DubboCustomProperties.java
  22. 1 0
      xyzc-common/xyzc-common-dubbo/src/main/resources/META-INF/dubbo/org.apache.dubbo.rpc.Filter
  23. 1 0
      xyzc-common/xyzc-common-dubbo/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
  24. 40 0
      xyzc-common/xyzc-common-dubbo/src/main/resources/common-dubbo.yml
  25. 71 0
      xyzc-common/xyzc-common-oss/pom.xml
  26. 41 0
      xyzc-common/xyzc-common-oss/src/main/java/com/xyzc/common/oss/constant/OssConstant.java
  27. 595 0
      xyzc-common/xyzc-common-oss/src/main/java/com/xyzc/common/oss/core/OssClient.java
  28. 30 0
      xyzc-common/xyzc-common-oss/src/main/java/com/xyzc/common/oss/entity/UploadResult.java
  29. 61 0
      xyzc-common/xyzc-common-oss/src/main/java/com/xyzc/common/oss/enumd/AccessPolicyType.java
  30. 35 0
      xyzc-common/xyzc-common-oss/src/main/java/com/xyzc/common/oss/enumd/PolicyType.java
  31. 19 0
      xyzc-common/xyzc-common-oss/src/main/java/com/xyzc/common/oss/exception/OssException.java
  32. 73 0
      xyzc-common/xyzc-common-oss/src/main/java/com/xyzc/common/oss/factory/OssFactory.java
  33. 63 0
      xyzc-common/xyzc-common-oss/src/main/java/com/xyzc/common/oss/properties/OssProperties.java
  34. 50 0
      xyzc-common/xyzc-common-oss/src/main/java/com/xyzc/common/oss/utils/OssUtils.java
  35. 10 0
      xyzc-start/pom.xml
  36. 2 0
      xyzc-start/src/main/java/com/xyzc/start/XyZcWebApplication.java
  37. 1 1
      xyzc-start/src/main/java/com/xyzc/start/constant/BaseUserConst.java
  38. 0 13
      xyzc-start/src/main/java/com/xyzc/start/controller/HomePageController.java
  39. 4 2
      xyzc-start/src/main/java/com/xyzc/start/controller/MyCollectController.java
  40. 3 1
      xyzc-start/src/main/java/com/xyzc/start/controller/UgcController.java
  41. 16 2
      xyzc-start/src/main/java/com/xyzc/start/controller/UserInfoController.java
  42. 5 0
      xyzc-start/src/main/java/com/xyzc/start/domain/App.java
  43. 4 2
      xyzc-start/src/main/java/com/xyzc/start/domain/BaseUserFuelFlow.java
  44. 3 1
      xyzc-start/src/main/java/com/xyzc/start/domain/DrawGraphics.java
  45. 12 3
      xyzc-start/src/main/java/com/xyzc/start/domain/DrawModel.java
  46. 3 3
      xyzc-start/src/main/java/com/xyzc/start/domain/WxConfig.java
  47. 4 4
      xyzc-start/src/main/java/com/xyzc/start/domain/bo/BaseUserCollectBo.java
  48. 24 0
      xyzc-start/src/main/java/com/xyzc/start/domain/vo/AvatarVo.java
  49. 5 0
      xyzc-start/src/main/java/com/xyzc/start/domain/vo/BaseUserCollectAppVo.java
  50. 5 0
      xyzc-start/src/main/java/com/xyzc/start/domain/vo/BaseUserCollectVo.java
  51. 3 1
      xyzc-start/src/main/java/com/xyzc/start/domain/vo/BaseUserFuelFlowVo.java
  52. 1 0
      xyzc-start/src/main/java/com/xyzc/start/domain/vo/BaseUserSelectAppVo.java
  53. 58 0
      xyzc-start/src/main/java/com/xyzc/start/domain/vo/CollectAppVo.java
  54. 1 1
      xyzc-start/src/main/java/com/xyzc/start/domain/vo/DrawGraphicsVo.java
  55. 3 0
      xyzc-start/src/main/java/com/xyzc/start/domain/vo/DrawModelVo.java
  56. 3 3
      xyzc-start/src/main/java/com/xyzc/start/domain/vo/WxConfigVo.java
  57. 1 1
      xyzc-start/src/main/java/com/xyzc/start/mapper/BaseUserCollectMapper.java
  58. 21 0
      xyzc-start/src/main/java/com/xyzc/start/mapper/DrawGraphicsMapper.java
  59. 13 8
      xyzc-start/src/main/java/com/xyzc/start/mapper/DrawModelMapper.java
  60. 2 1
      xyzc-start/src/main/java/com/xyzc/start/service/IMyCollectService.java
  61. 3 0
      xyzc-start/src/main/java/com/xyzc/start/service/IUserInfoService.java
  62. 5 3
      xyzc-start/src/main/java/com/xyzc/start/service/impl/DiscoverServiceImpl.java
  63. 1 1
      xyzc-start/src/main/java/com/xyzc/start/service/impl/HomePageServiceImpl.java
  64. 2 1
      xyzc-start/src/main/java/com/xyzc/start/service/impl/MyCollectServiceImpl.java
  65. 36 9
      xyzc-start/src/main/java/com/xyzc/start/service/impl/UserInfoServiceImpl.java
  66. 0 5
      xyzc-start/src/main/resources/application.yml
  67. 76 70
      xyzc-start/src/main/resources/mapper/WebStart/BaseUserCollectMapper.xml
  68. 14 0
      xyzc-start/src/main/resources/mapper/WebStart/DrawGraphicsMapper.xml
  69. 3 2
      xyzc-start/src/main/resources/mapper/WebStart/DrawModelMapper.xml

+ 13 - 1
pom.xml

@@ -7,13 +7,14 @@
     <artifactId>XyZc_Web_Start</artifactId>
     <version>${revision}</version>
 
-    <name>XyZc_Web</name>
+    <name>XyZc_Web_Start</name>
     <description>小易智创 web接口系统</description>
 
 
     <modules>
         <module>xyzc-common</module>
         <module>xyzc-start</module>
+        <module>xyzc-api</module>
     </modules>
 
     <packaging>pom</packaging>
@@ -126,6 +127,7 @@
     <dependencyManagement>
         <dependencies>
 
+
             <!-- SpringCloud 微服务 -->
             <dependency>
                 <groupId>org.springframework.cloud</groupId>
@@ -206,6 +208,16 @@
                 <scope>import</scope>
             </dependency>
 
+
+            <!-- api 的依赖配置-->
+            <dependency>
+                <groupId>com.xyzc</groupId>
+                <artifactId>xyzc-api-bom</artifactId>
+                <version>${revision}</version>
+                <type>pom</type>
+                <scope>import</scope>
+            </dependency>
+
             <dependency>
                 <groupId>org.springdoc</groupId>
                 <artifactId>springdoc-openapi-starter-webmvc-api</artifactId>

+ 25 - 0
xyzc-api/pom.xml

@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>com.xyzc</groupId>
+        <artifactId>XyZc_Web_Start</artifactId>
+        <version>${revision}</version>
+    </parent>
+
+    <artifactId>xyzc-api</artifactId>
+    <packaging>pom</packaging>
+
+    <modules>
+        <module>xyzc-api-bom</module>
+        <module>xyzc-api-resource</module>
+    </modules>
+
+    <description>
+        xyzc-api系统接口
+    </description>
+
+
+</project>

+ 37 - 0
xyzc-api/xyzc-api-bom/pom.xml

@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xmlns="http://maven.apache.org/POM/4.0.0"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+
+    <modelVersion>4.0.0</modelVersion>
+
+    <groupId>com.xyzc</groupId>
+    <artifactId>xyzc-api-bom</artifactId>
+    <version>${revision}</version>
+
+    <packaging>pom</packaging>
+
+
+    <description>
+        xyzc-api-bom api依赖项
+    </description>
+
+    <properties>
+        <revision>1.0.0</revision>
+    </properties>
+
+    <dependencyManagement>
+        <dependencies>
+
+            <!-- 资源服务接口 -->
+            <dependency>
+                <groupId>com.xyzc</groupId>
+                <artifactId>xyzc-api-resource</artifactId>
+                <version>${revision}</version>
+            </dependency>
+
+
+
+        </dependencies>
+    </dependencyManagement>
+</project>

+ 29 - 0
xyzc-api/xyzc-api-resource/pom.xml

@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>com.xyzc</groupId>
+        <artifactId>xyzc-api</artifactId>
+        <version>${revision}</version>
+    </parent>
+
+    <artifactId>xyzc-api-resource</artifactId>
+
+    <description>
+        xyzc-api-resource 资源服务接口模块
+    </description>
+
+    <dependencies>
+
+        <!-- XyZc Common Core-->
+        <dependency>
+            <groupId>com.xyzc</groupId>
+            <artifactId>xyzc-common-core</artifactId>
+        </dependency>
+
+    </dependencies>
+
+
+</project>

+ 39 - 0
xyzc-api/xyzc-api-resource/src/main/java/com/xyzc/resource/api/RemoteFileService.java

@@ -0,0 +1,39 @@
+package com.xyzc.resource.api;
+
+
+import com.xyzc.common.core.exception.ServerException;
+import com.xyzc.resource.api.domain.RemoteFile;
+
+import java.util.List;
+
+/**
+ * 文件服务
+ *
+ * @author Lion Li
+ */
+public interface RemoteFileService {
+
+    /**
+     * 上传文件
+     *
+     * @param file 文件信息
+     * @return 结果
+     */
+    RemoteFile upload(String name, String originalFilename, String contentType, byte[] file) throws ServerException;
+
+    /**
+     * 通过ossId查询对应的url
+     *
+     * @param ossIds ossId串逗号分隔
+     * @return url串逗号分隔
+     */
+    String selectUrlByIds(String ossIds);
+
+    /**
+     * 通过ossId查询列表
+     *
+     * @param ossIds ossId串逗号分隔
+     * @return 列表
+     */
+    List<RemoteFile> selectByIds(String ossIds);
+}

+ 53 - 0
xyzc-api/xyzc-api-resource/src/main/java/com/xyzc/resource/api/RemoteFileServiceMock.java

@@ -0,0 +1,53 @@
+package com.xyzc.resource.api;
+
+import com.xyzc.common.core.utils.StringUtils;
+import com.xyzc.resource.api.domain.RemoteFile;
+import lombok.extern.slf4j.Slf4j;
+
+import java.util.List;
+
+/**
+ * 文件服务(降级处理)
+ *
+ * @author Lion Li
+ */
+@Slf4j
+public class RemoteFileServiceMock implements RemoteFileService {
+
+    /**
+     * 上传文件
+     *
+     * @param file 文件信息
+     * @return 结果
+     */
+    @Override
+    public RemoteFile upload(String name, String originalFilename, String contentType, byte[] file) {
+        log.warn("服务调用异常 -> 降级处理");
+        return null;
+    }
+
+    /**
+     * 通过ossId查询对应的url
+     *
+     * @param ossIds ossId串逗号分隔
+     * @return url串逗号分隔
+     */
+    @Override
+    public String selectUrlByIds(String ossIds) {
+        log.warn("服务调用异常 -> 降级处理");
+        return StringUtils.EMPTY;
+    }
+
+    /**
+     * 通过ossId查询列表
+     *
+     * @param ossIds ossId串逗号分隔
+     * @return 列表
+     */
+    @Override
+    public List<RemoteFile> selectByIds(String ossIds) {
+        log.warn("服务调用异常 -> 降级处理");
+        return List.of();
+    }
+
+}

+ 44 - 0
xyzc-api/xyzc-api-resource/src/main/java/com/xyzc/resource/api/domain/RemoteFile.java

@@ -0,0 +1,44 @@
+package com.xyzc.resource.api.domain;
+
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+
+/**
+ * 文件信息
+ *
+ * @author yz
+ */
+@Data
+public class RemoteFile implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * oss主键
+     */
+    private Long ossId;
+
+    /**
+     * 文件名称
+     */
+    private String name;
+
+    /**
+     * 文件地址
+     */
+    private String url;
+
+    /**
+     * 原名
+     */
+    private String originalName;
+
+    /**
+     * 文件后缀名
+     */
+    private String fileSuffix;
+
+}

+ 36 - 0
xyzc-api/xyzc-api-resource/src/main/java/com/xyzc/resource/api/domain/RemoteSms.java

@@ -0,0 +1,36 @@
+package com.xyzc.resource.api.domain;
+
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+
+/**
+ * 文件信息
+ *
+ * @author yz
+ */
+@Data
+public class RemoteSms implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 是否成功
+     */
+    private Boolean success;
+
+    /**
+     * 配置标识名 如未配置取对应渠道名例如 Alibaba
+     */
+    private String configId;
+
+    /**
+     * 厂商原返回体
+     * <p>
+     * 可自行转换为 SDK 对应的 SendSmsResponse
+     */
+    private String response;
+
+}

+ 2 - 0
xyzc-common/pom.xml

@@ -28,6 +28,8 @@
         <module>xyzc-common-nacos</module>
         <module>xyzc-common-alibaba-bom</module>
         <module>xyzc-common-seata</module>
+        <module>xyzc-common-oss</module>
+        <module>xyzc-common-dubbo</module>
     </modules>
 
 </project>

+ 15 - 0
xyzc-common/xyzc-common-bom/pom.xml

@@ -28,6 +28,14 @@
                 <version>${revision}</version>
             </dependency>
 
+            <!-- dubbo服务 -->
+            <dependency>
+                <groupId>com.xyzc</groupId>
+                <artifactId>xyzc-common-dubbo</artifactId>
+                <version>${revision}</version>
+            </dependency>
+
+
             <!-- sms服务 -->
             <dependency>
                 <groupId>com.xyzc</groupId>
@@ -49,6 +57,13 @@
                 <version>${revision}</version>
             </dependency>
 
+            <!-- oss模块 -->
+            <dependency>
+                <groupId>com.xyzc</groupId>
+                <artifactId>xyzc-common-oss</artifactId>
+                <version>${revision}</version>
+            </dependency>
+
             <!-- 数据库服务 -->
             <dependency>
                 <groupId>com.xyzc</groupId>

+ 0 - 4
xyzc-common/xyzc-common-core/src/main/java/com/xyzc/common/core/config/JacksonConfiguration.java

@@ -23,10 +23,6 @@ public class JacksonConfiguration {
             builder.serializerByType(Long.class, ToStringSerializer.instance);
             // 把 bigDecimal 类型序列化为 String
             builder.serializerByType(BigDecimal.class, ToStringSerializer.instance);
-            // 把 bigInteger 类型序列化为 String
-            builder.serializerByType(BigInteger.class, ToStringSerializer.instance);
-            // 把 Integer 类型序列化为 String
-            builder.serializerByType(Integer.class, ToStringSerializer.instance);
             // 把 Double 类型序列化为 String
             builder.serializerByType(Double.class, ToStringSerializer.instance);
             // 把 Float 类型序列化为 String

+ 92 - 0
xyzc-common/xyzc-common-core/src/main/java/com/xyzc/common/core/constant/Const.java

@@ -0,0 +1,92 @@
+package com.xyzc.common.core.constant;
+
+/**
+ * 通用常量信息
+ *
+ * @author yz
+ */
+public interface Const {
+
+    /**
+     * UTF-8 字符集
+     */
+    String UTF8 = "UTF-8";
+
+    /**
+     * GBK 字符集
+     */
+    String GBK = "GBK";
+
+    /**
+     * www主域
+     */
+    String WWW = "www.";
+
+    /**
+     * http请求
+     */
+    String HTTP = "http://";
+
+    /**
+     * https请求
+     */
+    String HTTPS = "https://";
+
+    /**
+     * 通用成功标识
+     */
+    String SUCCESS = "0";
+
+    /**
+     * 通用失败标识
+     */
+    String FAIL = "1";
+
+    /**
+     * 登录成功
+     */
+    String LOGIN_SUCCESS = "Success";
+
+    /**
+     * 注销
+     */
+    String LOGOUT = "Logout";
+
+    /**
+     * 注册
+     */
+    String REGISTER = "Register";
+
+    /**
+     * 登录失败
+     */
+    String LOGIN_FAIL = "Error";
+
+    /**
+     * 验证码有效期(分钟)
+     */
+    Integer CAPTCHA_EXPIRATION = 2;
+
+    /**
+     * 令牌
+     */
+    String TOKEN = "token";
+
+    /**
+     * 顶级部门id
+     */
+    Long TOP_PARENT_ID = 0L;
+
+    /**
+     * json
+     */
+    String JSON = "json";
+
+    String PAGE = "page";
+
+    String BO = "bo";
+
+
+
+}
+

+ 40 - 0
xyzc-common/xyzc-common-core/src/main/java/com/xyzc/common/core/constant/GlobalConst.java

@@ -0,0 +1,40 @@
+package com.xyzc.common.core.constant;
+
+/**
+ * 全局的key常量 (业务无关的key)
+ *
+ * @author 杨兴卓
+ */
+public interface GlobalConst {
+
+    /**
+     * 全局 redis key (业务无关的key)
+     */
+    String GLOBAL_REDIS_KEY = "global:";
+
+    /**
+     * 验证码 redis key
+     */
+    String CAPTCHA_CODE_KEY = GLOBAL_REDIS_KEY + "captcha_codes:";
+
+    /**
+     * 防重提交 redis key
+     */
+    String REPEAT_SUBMIT_KEY = GLOBAL_REDIS_KEY + "repeat_submit:";
+
+    /**
+     * 限流 redis key
+     */
+    String RATE_LIMIT_KEY = GLOBAL_REDIS_KEY + "rate_limit:";
+
+    /**
+     * 登录账户密码错误次数 redis key
+     */
+    String PWD_ERR_CNT_KEY = GLOBAL_REDIS_KEY + "pwd_err_cnt:";
+
+    /**
+     * 三方认证 redis key
+     */
+    String SOCIAL_AUTH_CODE_KEY = GLOBAL_REDIS_KEY + "social_auth_codes:";
+
+}

+ 79 - 0
xyzc-common/xyzc-common-dubbo/pom.xml

@@ -0,0 +1,79 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>com.xyzc</groupId>
+        <artifactId>xyzc-common</artifactId>
+        <version>${revision}</version>
+    </parent>
+
+    <artifactId>xyzc-common-dubbo</artifactId>
+
+    <description>
+        xyzc-common-dubbo RPC服务
+    </description>
+
+    <dependencies>
+
+        <dependency>
+            <groupId>com.xyzc</groupId>
+            <artifactId>xyzc-common-core</artifactId>
+        </dependency>
+
+
+        <!-- JSON工具类 -->
+        <dependency>
+            <groupId>com.fasterxml.jackson.core</groupId>
+            <artifactId>jackson-databind</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>com.fasterxml.jackson.datatype</groupId>
+            <artifactId>jackson-datatype-jsr310</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.springframework.cloud</groupId>
+            <artifactId>spring-cloud-context</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.apache.dubbo</groupId>
+            <artifactId>dubbo-spring-boot-starter</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.apache.dubbo</groupId>
+            <artifactId>dubbo-spring-boot-actuator</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.apache.dubbo</groupId>
+            <artifactId>dubbo-metadata-report-redis</artifactId>
+            <exclusions>
+                <exclusion>
+                    <groupId>redis.clients</groupId>
+                    <artifactId>jedis</artifactId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+        <dependency>
+            <groupId>redis.clients</groupId>
+            <artifactId>jedis</artifactId>
+            <version>5.1.0</version>
+        </dependency>
+        <dependency>
+            <groupId>org.projectlombok</groupId>
+            <artifactId>lombok</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.springframework.cloud</groupId>
+            <artifactId>spring-cloud-commons</artifactId>
+        </dependency>
+
+    </dependencies>
+
+</project>

+ 576 - 0
xyzc-common/xyzc-common-dubbo/src/main/java/com/apache/dubbo/metadata/report/support/AbstractMetadataReport.java

@@ -0,0 +1,576 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.apache.dubbo.metadata.report.support;
+
+import org.apache.dubbo.common.URL;
+import org.apache.dubbo.common.logger.ErrorTypeAwareLogger;
+import org.apache.dubbo.common.logger.LoggerFactory;
+import org.apache.dubbo.common.utils.CollectionUtils;
+import org.apache.dubbo.common.utils.ConfigUtils;
+import org.apache.dubbo.common.utils.JsonUtils;
+import org.apache.dubbo.common.utils.NamedThreadFactory;
+import org.apache.dubbo.metadata.definition.model.FullServiceDefinition;
+import org.apache.dubbo.metadata.definition.model.ServiceDefinition;
+import org.apache.dubbo.metadata.report.MetadataReport;
+import org.apache.dubbo.metadata.report.identifier.KeyTypeEnum;
+import org.apache.dubbo.metadata.report.identifier.MetadataIdentifier;
+import org.apache.dubbo.metadata.report.identifier.ServiceMetadataIdentifier;
+import org.apache.dubbo.metadata.report.identifier.SubscriberMetadataIdentifier;
+import org.apache.dubbo.metrics.event.MetricsEventBus;
+import org.apache.dubbo.metrics.metadata.event.MetadataEvent;
+import org.apache.dubbo.rpc.model.ApplicationModel;
+
+import java.io.*;
+import java.nio.channels.FileChannel;
+import java.nio.channels.FileLock;
+import java.util.*;
+import java.util.concurrent.*;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicLong;
+
+import static org.apache.dubbo.common.constants.CommonConstants.*;
+import static org.apache.dubbo.common.constants.LoggerCodeConstants.COMMON_UNEXPECTED_EXCEPTION;
+import static org.apache.dubbo.common.constants.LoggerCodeConstants.PROXY_FAILED_EXPORT_SERVICE;
+import static org.apache.dubbo.common.utils.StringUtils.replace;
+import static org.apache.dubbo.metadata.report.support.Constants.*;
+
+public abstract class AbstractMetadataReport implements MetadataReport {
+
+    protected static final String DEFAULT_ROOT = "dubbo";
+
+    protected static final int ONE_DAY_IN_MILLISECONDS = 60 * 24 * 60 * 1000;
+    private static final int FOUR_HOURS_IN_MILLISECONDS = 60 * 4 * 60 * 1000;
+    // Log output
+    protected final ErrorTypeAwareLogger logger = LoggerFactory.getErrorTypeAwareLogger(getClass());
+
+    // Local disk cache, where the special key value.registries records the list of metadata centers, and the others are
+    // the list of notified service providers
+    final Properties properties = new Properties();
+    private final ExecutorService reportCacheExecutor =
+        Executors.newFixedThreadPool(1, new NamedThreadFactory("DubboSaveMetadataReport", true));
+    final Map<MetadataIdentifier, Object> allMetadataReports = new ConcurrentHashMap<>(4);
+
+    private final AtomicLong lastCacheChanged = new AtomicLong();
+    final Map<MetadataIdentifier, Object> failedReports = new ConcurrentHashMap<>(4);
+    private URL reportURL;
+    boolean syncReport;
+    // Local disk cache file
+    File file;
+    private AtomicBoolean initialized = new AtomicBoolean(false);
+    public MetadataReportRetry metadataReportRetry;
+    private ScheduledExecutorService reportTimerScheduler;
+
+    private final boolean reportMetadata;
+    private final boolean reportDefinition;
+    protected ApplicationModel applicationModel;
+
+    public AbstractMetadataReport(URL reportServerURL) {
+        setUrl(reportServerURL);
+        applicationModel = reportServerURL.getOrDefaultApplicationModel();
+
+        boolean localCacheEnabled = reportServerURL.getParameter(REGISTRY_LOCAL_FILE_CACHE_ENABLED, true);
+        // Start file save timer
+        String defaultFilename = System.getProperty("user.home") + DUBBO_METADATA
+            + reportServerURL.getApplication()
+            + "-" + replace(reportServerURL.getAddress(), ":", "-")
+            + CACHE;
+        String filename = reportServerURL.getParameter(FILE_KEY, defaultFilename);
+        File file = null;
+        if (localCacheEnabled && ConfigUtils.isNotEmpty(filename)) {
+            file = new File(filename);
+            if (!file.exists()
+                && file.getParentFile() != null
+                && !file.getParentFile().exists()) {
+                if (!file.getParentFile().mkdirs()) {
+                    throw new IllegalArgumentException("Invalid service store file " + file
+                        + ", cause: Failed to create directory " + file.getParentFile() + "!");
+                }
+            }
+            // if this file exists, firstly delete it.
+            if (!initialized.getAndSet(true) && file.exists()) {
+                file.delete();
+            }
+        }
+        this.file = file;
+        loadProperties();
+        syncReport = reportServerURL.getParameter(SYNC_REPORT_KEY, false);
+        metadataReportRetry = new MetadataReportRetry(
+            reportServerURL.getParameter(RETRY_TIMES_KEY, DEFAULT_METADATA_REPORT_RETRY_TIMES),
+            reportServerURL.getParameter(RETRY_PERIOD_KEY, DEFAULT_METADATA_REPORT_RETRY_PERIOD));
+        // cycle report the data switch
+        if (reportServerURL.getParameter(CYCLE_REPORT_KEY, DEFAULT_METADATA_REPORT_CYCLE_REPORT)) {
+            reportTimerScheduler = Executors.newSingleThreadScheduledExecutor(
+                new NamedThreadFactory("DubboMetadataReportTimer", true));
+            reportTimerScheduler.scheduleAtFixedRate(
+                this::publishAll, calculateStartTime(), ONE_DAY_IN_MILLISECONDS, TimeUnit.MILLISECONDS);
+        }
+
+        this.reportMetadata = reportServerURL.getParameter(REPORT_METADATA_KEY, false);
+        this.reportDefinition = reportServerURL.getParameter(REPORT_DEFINITION_KEY, true);
+    }
+
+    public URL getUrl() {
+        return reportURL;
+    }
+
+    protected void setUrl(URL url) {
+        if (url == null) {
+            throw new IllegalArgumentException("metadataReport url == null");
+        }
+        this.reportURL = url;
+    }
+
+    private void doSaveProperties(long version) {
+        if (version < lastCacheChanged.get()) {
+            return;
+        }
+        if (file == null) {
+            return;
+        }
+        // Save
+        try {
+            File lockfile = new File(file.getAbsolutePath() + ".lock");
+            if (!lockfile.exists()) {
+                lockfile.createNewFile();
+            }
+            try (RandomAccessFile raf = new RandomAccessFile(lockfile, "rw");
+                 FileChannel channel = raf.getChannel()) {
+                FileLock lock = channel.tryLock();
+                if (lock == null) {
+                    throw new IOException(
+                        "Can not lock the metadataReport cache file " + file.getAbsolutePath()
+                            + ", ignore and retry later, maybe multi java process use the file, please config: dubbo.metadata.file=xxx.properties");
+                }
+                // Save
+                try {
+                    if (!file.exists()) {
+                        file.createNewFile();
+                    }
+
+                    Properties tmpProperties;
+                    if (!syncReport) {
+                        // When syncReport = false, properties.setProperty and properties.store are called from the same
+                        // thread(reportCacheExecutor), so deep copy is not required
+                        tmpProperties = properties;
+                    } else {
+                        // Using store method and setProperty method of the this.properties will cause lock contention
+                        // under multi-threading, so deep copy a new container
+                        tmpProperties = new Properties();
+                        Set<Map.Entry<Object, Object>> entries = properties.entrySet();
+                        for (Map.Entry<Object, Object> entry : entries) {
+                            tmpProperties.setProperty((String) entry.getKey(), (String) entry.getValue());
+                        }
+                    }
+
+                    try (FileOutputStream outputFile = new FileOutputStream(file)) {
+                        tmpProperties.store(outputFile, "Dubbo metadataReport Cache");
+                    }
+                } finally {
+                    lock.release();
+                }
+            }
+        } catch (Throwable e) {
+            if (version < lastCacheChanged.get()) {
+                return;
+            } else {
+                reportCacheExecutor.execute(new SaveProperties(lastCacheChanged.incrementAndGet()));
+            }
+            logger.warn(
+                COMMON_UNEXPECTED_EXCEPTION,
+                "",
+                "",
+                "Failed to save service store file, cause: " + e.getMessage(),
+                e);
+        }
+    }
+
+    void loadProperties() {
+        if (file != null && file.exists()) {
+            try (InputStream in = new FileInputStream(file)) {
+                properties.load(in);
+                if (logger.isInfoEnabled()) {
+                    logger.info("Load service store file " + file + ", data: " + properties);
+                }
+            } catch (Throwable e) {
+                logger.warn(COMMON_UNEXPECTED_EXCEPTION, "", "", "Failed to load service store file" + file, e);
+            }
+        }
+    }
+
+    private void saveProperties(MetadataIdentifier metadataIdentifier, String value, boolean add, boolean sync) {
+        if (file == null) {
+            return;
+        }
+
+        try {
+            if (add) {
+                properties.setProperty(metadataIdentifier.getUniqueKey(KeyTypeEnum.UNIQUE_KEY), value);
+            } else {
+                properties.remove(metadataIdentifier.getUniqueKey(KeyTypeEnum.UNIQUE_KEY));
+            }
+            long version = lastCacheChanged.incrementAndGet();
+            if (sync) {
+                new SaveProperties(version).run();
+            } else {
+                reportCacheExecutor.execute(new SaveProperties(version));
+            }
+
+        } catch (Throwable t) {
+            logger.warn(COMMON_UNEXPECTED_EXCEPTION, "", "", t.getMessage(), t);
+        }
+    }
+
+    @Override
+    public String toString() {
+        return getUrl().toString();
+    }
+
+    private class SaveProperties implements Runnable {
+        private long version;
+
+        private SaveProperties(long version) {
+            this.version = version;
+        }
+
+        @Override
+        public void run() {
+            doSaveProperties(version);
+        }
+    }
+
+    @Override
+    public void storeProviderMetadata(
+        MetadataIdentifier providerMetadataIdentifier, ServiceDefinition serviceDefinition) {
+        if (syncReport) {
+            storeProviderMetadataTask(providerMetadataIdentifier, serviceDefinition);
+        } else {
+            reportCacheExecutor.execute(() -> storeProviderMetadataTask(providerMetadataIdentifier, serviceDefinition));
+        }
+    }
+
+    private void storeProviderMetadataTask(
+        MetadataIdentifier providerMetadataIdentifier, ServiceDefinition serviceDefinition) {
+
+        MetadataEvent metadataEvent = MetadataEvent.toServiceSubscribeEvent(
+            applicationModel, providerMetadataIdentifier.getUniqueServiceName());
+        MetricsEventBus.post(
+            metadataEvent,
+            () -> {
+                boolean result = true;
+                try {
+                    if (logger.isInfoEnabled()) {
+                        logger.info("store provider metadata. Identifier : " + providerMetadataIdentifier
+                            + "; definition: " + serviceDefinition);
+                    }
+                    allMetadataReports.put(providerMetadataIdentifier, serviceDefinition);
+                    failedReports.remove(providerMetadataIdentifier);
+                    String data = JsonUtils.toJson(serviceDefinition);
+                    doStoreProviderMetadata(providerMetadataIdentifier, data);
+                    saveProperties(providerMetadataIdentifier, data, true, !syncReport);
+                } catch (Exception e) {
+                    // retry again. If failed again, throw exception.
+                    failedReports.put(providerMetadataIdentifier, serviceDefinition);
+                    metadataReportRetry.startRetryTask();
+                    logger.error(
+                        PROXY_FAILED_EXPORT_SERVICE,
+                        "",
+                        "",
+                        "Failed to put provider metadata " + providerMetadataIdentifier + " in  "
+                            + serviceDefinition + ", cause: " + e.getMessage(),
+                        e);
+                    result = false;
+                }
+                return result;
+            },
+            aBoolean -> aBoolean);
+    }
+
+    @Override
+    public void storeConsumerMetadata(
+        MetadataIdentifier consumerMetadataIdentifier, Map<String, String> serviceParameterMap) {
+        if (syncReport) {
+            storeConsumerMetadataTask(consumerMetadataIdentifier, serviceParameterMap);
+        } else {
+            reportCacheExecutor.execute(
+                () -> storeConsumerMetadataTask(consumerMetadataIdentifier, serviceParameterMap));
+        }
+    }
+
+    protected void storeConsumerMetadataTask(
+        MetadataIdentifier consumerMetadataIdentifier, Map<String, String> serviceParameterMap) {
+        try {
+            if (logger.isInfoEnabled()) {
+                logger.info("store consumer metadata. Identifier : " + consumerMetadataIdentifier + "; definition: "
+                    + serviceParameterMap);
+            }
+            allMetadataReports.put(consumerMetadataIdentifier, serviceParameterMap);
+            failedReports.remove(consumerMetadataIdentifier);
+
+            String data = JsonUtils.toJson(serviceParameterMap);
+            doStoreConsumerMetadata(consumerMetadataIdentifier, data);
+            saveProperties(consumerMetadataIdentifier, data, true, !syncReport);
+        } catch (Exception e) {
+            // retry again. If failed again, throw exception.
+            failedReports.put(consumerMetadataIdentifier, serviceParameterMap);
+            metadataReportRetry.startRetryTask();
+            logger.error(
+                PROXY_FAILED_EXPORT_SERVICE,
+                "",
+                "",
+                "Failed to put consumer metadata " + consumerMetadataIdentifier + ";  " + serviceParameterMap
+                    + ", cause: " + e.getMessage(),
+                e);
+        }
+    }
+
+    @Override
+    public void destroy() {
+        if (reportCacheExecutor != null) {
+            reportCacheExecutor.shutdown();
+        }
+        if (reportTimerScheduler != null) {
+            reportTimerScheduler.shutdown();
+        }
+        if (metadataReportRetry != null) {
+            metadataReportRetry.destroy();
+            metadataReportRetry = null;
+        }
+    }
+
+    @Override
+    public void saveServiceMetadata(ServiceMetadataIdentifier metadataIdentifier, URL url) {
+        if (syncReport) {
+            doSaveMetadata(metadataIdentifier, url);
+        } else {
+            reportCacheExecutor.execute(() -> doSaveMetadata(metadataIdentifier, url));
+        }
+    }
+
+    @Override
+    public void removeServiceMetadata(ServiceMetadataIdentifier metadataIdentifier) {
+        if (syncReport) {
+            doRemoveMetadata(metadataIdentifier);
+        } else {
+            reportCacheExecutor.execute(() -> doRemoveMetadata(metadataIdentifier));
+        }
+    }
+
+    @Override
+    public List<String> getExportedURLs(ServiceMetadataIdentifier metadataIdentifier) {
+        // TODO, fallback to local cache
+        return doGetExportedURLs(metadataIdentifier);
+    }
+
+    @Override
+    public void saveSubscribedData(SubscriberMetadataIdentifier subscriberMetadataIdentifier, Set<String> urls) {
+        if (syncReport) {
+            doSaveSubscriberData(subscriberMetadataIdentifier, JsonUtils.toJson(urls));
+        } else {
+            reportCacheExecutor.execute(
+                () -> doSaveSubscriberData(subscriberMetadataIdentifier, JsonUtils.toJson(urls)));
+        }
+    }
+
+    @Override
+    public List<String> getSubscribedURLs(SubscriberMetadataIdentifier subscriberMetadataIdentifier) {
+        String content = doGetSubscribedURLs(subscriberMetadataIdentifier);
+        return JsonUtils.toJavaList(content, String.class);
+    }
+
+    String getProtocol(URL url) {
+        String protocol = url.getSide();
+        protocol = protocol == null ? url.getProtocol() : protocol;
+        return protocol;
+    }
+
+    /**
+     * @return if need to continue
+     */
+    public boolean retry() {
+        return doHandleMetadataCollection(failedReports);
+    }
+
+    @Override
+    public boolean shouldReportDefinition() {
+        return reportDefinition;
+    }
+
+    @Override
+    public boolean shouldReportMetadata() {
+        return reportMetadata;
+    }
+
+    private boolean doHandleMetadataCollection(Map<MetadataIdentifier, Object> metadataMap) {
+        if (metadataMap.isEmpty()) {
+            return true;
+        }
+        Iterator<Map.Entry<MetadataIdentifier, Object>> iterable =
+            metadataMap.entrySet().iterator();
+        while (iterable.hasNext()) {
+            Map.Entry<MetadataIdentifier, Object> item = iterable.next();
+            if (PROVIDER_SIDE.equals(item.getKey().getSide())) {
+                this.storeProviderMetadata(item.getKey(), (FullServiceDefinition) item.getValue());
+            } else if (CONSUMER_SIDE.equals(item.getKey().getSide())) {
+                this.storeConsumerMetadata(item.getKey(), (Map) item.getValue());
+            }
+        }
+        return false;
+    }
+
+    /**
+     * not private. just for unittest.
+     */
+    void publishAll() {
+        logger.info("start to publish all metadata.");
+        this.doHandleMetadataCollection(allMetadataReports);
+    }
+
+    /**
+     * between 2:00 am to 6:00 am, the time is random.
+     *
+     * @return
+     */
+    long calculateStartTime() {
+        Calendar calendar = Calendar.getInstance();
+        long nowMill = calendar.getTimeInMillis();
+        calendar.set(Calendar.HOUR_OF_DAY, 0);
+        calendar.set(Calendar.MINUTE, 0);
+        calendar.set(Calendar.SECOND, 0);
+        calendar.set(Calendar.MILLISECOND, 0);
+        long subtract = calendar.getTimeInMillis() + ONE_DAY_IN_MILLISECONDS - nowMill;
+        return subtract
+            + (FOUR_HOURS_IN_MILLISECONDS / 2)
+            + ThreadLocalRandom.current().nextInt(FOUR_HOURS_IN_MILLISECONDS);
+    }
+
+    class MetadataReportRetry {
+        protected final ErrorTypeAwareLogger logger = LoggerFactory.getErrorTypeAwareLogger(getClass());
+
+        final ScheduledExecutorService retryExecutor =
+            Executors.newScheduledThreadPool(0, new NamedThreadFactory("DubboMetadataReportRetryTimer", true));
+        volatile ScheduledFuture retryScheduledFuture;
+        final AtomicInteger retryCounter = new AtomicInteger(0);
+        // retry task schedule period
+        long retryPeriod;
+        // if no failed report, wait how many times to run retry task.
+        int retryTimesIfNonFail = 600;
+
+        int retryLimit;
+
+        public MetadataReportRetry(int retryTimes, int retryPeriod) {
+            this.retryPeriod = retryPeriod;
+            this.retryLimit = retryTimes;
+        }
+
+        void startRetryTask() {
+            if (retryScheduledFuture == null) {
+                synchronized (retryCounter) {
+                    if (retryScheduledFuture == null) {
+                        retryScheduledFuture = retryExecutor.scheduleWithFixedDelay(
+                            () -> {
+                                // Check and connect to the metadata
+                                try {
+                                    int times = retryCounter.incrementAndGet();
+                                    logger.info("start to retry task for metadata report. retry times:" + times);
+                                    if (retry() && times > retryTimesIfNonFail) {
+                                        cancelRetryTask();
+                                    }
+                                    if (times > retryLimit) {
+                                        cancelRetryTask();
+                                    }
+                                } catch (Throwable t) { // Defensive fault tolerance
+                                    logger.error(
+                                        COMMON_UNEXPECTED_EXCEPTION,
+                                        "",
+                                        "",
+                                        "Unexpected error occur at failed retry, cause: " + t.getMessage(),
+                                        t);
+                                }
+                            },
+                            500,
+                            retryPeriod,
+                            TimeUnit.MILLISECONDS);
+                    }
+                }
+            }
+        }
+
+        void cancelRetryTask() {
+            if (retryScheduledFuture != null) {
+                retryScheduledFuture.cancel(false);
+            }
+            retryExecutor.shutdown();
+        }
+
+        void destroy() {
+            cancelRetryTask();
+        }
+
+        /**
+         * @deprecated only for test
+         */
+        @Deprecated
+        ScheduledExecutorService getRetryExecutor() {
+            return retryExecutor;
+        }
+    }
+
+    private void doSaveSubscriberData(SubscriberMetadataIdentifier subscriberMetadataIdentifier, List<String> urls) {
+        if (CollectionUtils.isEmpty(urls)) {
+            return;
+        }
+        List<String> encodedUrlList = new ArrayList<>(urls.size());
+        for (String url : urls) {
+            encodedUrlList.add(URL.encode(url));
+        }
+        doSaveSubscriberData(subscriberMetadataIdentifier, encodedUrlList);
+    }
+
+    protected abstract void doStoreProviderMetadata(
+        MetadataIdentifier providerMetadataIdentifier, String serviceDefinitions);
+
+    protected abstract void doStoreConsumerMetadata(
+        MetadataIdentifier consumerMetadataIdentifier, String serviceParameterString);
+
+    protected abstract void doSaveMetadata(ServiceMetadataIdentifier metadataIdentifier, URL url);
+
+    protected abstract void doRemoveMetadata(ServiceMetadataIdentifier metadataIdentifier);
+
+    protected abstract List<String> doGetExportedURLs(ServiceMetadataIdentifier metadataIdentifier);
+
+    protected abstract void doSaveSubscriberData(
+        SubscriberMetadataIdentifier subscriberMetadataIdentifier, String urlListStr);
+
+    protected abstract String doGetSubscribedURLs(SubscriberMetadataIdentifier subscriberMetadataIdentifier);
+
+    /**
+     * @deprecated only for unit test
+     */
+    @Deprecated
+    protected ExecutorService getReportCacheExecutor() {
+        return reportCacheExecutor;
+    }
+
+    /**
+     * @deprecated only for unit test
+     */
+    @Deprecated
+    protected MetadataReportRetry getMetadataReportRetry() {
+        return metadataReportRetry;
+    }
+}

+ 531 - 0
xyzc-common/xyzc-common-dubbo/src/main/java/com/apache/dubbo/metadata/store/redis/RedisMetadataReport.java

@@ -0,0 +1,531 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.apache.dubbo.metadata.store.redis;
+
+import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
+import org.apache.dubbo.common.URL;
+import org.apache.dubbo.common.config.configcenter.ConfigItem;
+import org.apache.dubbo.common.logger.ErrorTypeAwareLogger;
+import org.apache.dubbo.common.logger.LoggerFactory;
+import org.apache.dubbo.common.utils.ConcurrentHashMapUtils;
+import org.apache.dubbo.common.utils.ConcurrentHashSet;
+import org.apache.dubbo.common.utils.JsonUtils;
+import org.apache.dubbo.common.utils.StringUtils;
+import org.apache.dubbo.metadata.MappingChangedEvent;
+import org.apache.dubbo.metadata.MappingListener;
+import org.apache.dubbo.metadata.MetadataInfo;
+import org.apache.dubbo.metadata.ServiceNameMapping;
+import org.apache.dubbo.metadata.report.identifier.*;
+import com.apache.dubbo.metadata.report.support.AbstractMetadataReport;
+import org.apache.dubbo.rpc.RpcException;
+import redis.clients.jedis.*;
+import redis.clients.jedis.params.SetParams;
+import redis.clients.jedis.util.JedisClusterCRC16;
+
+import java.util.*;
+import java.util.concurrent.ConcurrentHashMap;
+
+import static org.apache.dubbo.common.constants.CommonConstants.*;
+import static org.apache.dubbo.common.constants.LoggerCodeConstants.TRANSPORT_FAILED_RESPONSE;
+import static org.apache.dubbo.metadata.MetadataConstants.META_DATA_STORE_TAG;
+import static org.apache.dubbo.metadata.ServiceNameMapping.DEFAULT_MAPPING_GROUP;
+import static org.apache.dubbo.metadata.ServiceNameMapping.getAppNames;
+import static org.apache.dubbo.metadata.report.support.Constants.DEFAULT_METADATA_REPORT_CYCLE_REPORT;
+
+/**
+ * RedisMetadataReport
+ */
+public class RedisMetadataReport extends AbstractMetadataReport {
+
+    private static final String REDIS_DATABASE_KEY = "database";
+    private static final ErrorTypeAwareLogger logger = LoggerFactory.getErrorTypeAwareLogger(RedisMetadataReport.class);
+
+    // protected , for test
+    protected JedisPool pool;
+    private Set<HostAndPort> jedisClusterNodes;
+    private int timeout;
+    private String password;
+    private final String root;
+    private final ConcurrentHashMap<String, MappingDataListener> mappingDataListenerMap = new ConcurrentHashMap<>();
+    private SetParams jedisParams = SetParams.setParams();
+
+    public RedisMetadataReport(URL url) {
+        super(url);
+        timeout = url.getParameter(TIMEOUT_KEY, DEFAULT_TIMEOUT);
+        password = url.getPassword();
+        this.root = url.getGroup(DEFAULT_ROOT);
+        if (url.getParameter(CYCLE_REPORT_KEY, DEFAULT_METADATA_REPORT_CYCLE_REPORT)) {
+            // ttl default is twice the cycle-report time
+            jedisParams.ex(ONE_DAY_IN_MILLISECONDS * 2);
+        }
+        if (url.getParameter(CLUSTER_KEY, false)) {
+            jedisClusterNodes = new HashSet<>();
+            List<URL> urls = url.getBackupUrls();
+            for (URL tmpUrl : urls) {
+                jedisClusterNodes.add(new HostAndPort(tmpUrl.getHost(), tmpUrl.getPort()));
+            }
+        } else {
+            int database = url.getParameter(REDIS_DATABASE_KEY, 0);
+            pool = new JedisPool(new JedisPoolConfig(), url.getHost(), url.getPort(), timeout, password, database);
+        }
+    }
+
+    @Override
+    protected void doStoreProviderMetadata(MetadataIdentifier providerMetadataIdentifier, String serviceDefinitions) {
+        this.storeMetadata(providerMetadataIdentifier, serviceDefinitions);
+    }
+
+    @Override
+    protected void doStoreConsumerMetadata(MetadataIdentifier consumerMetadataIdentifier, String value) {
+        this.storeMetadata(consumerMetadataIdentifier, value);
+    }
+
+    @Override
+    protected void doSaveMetadata(ServiceMetadataIdentifier serviceMetadataIdentifier, URL url) {
+        this.storeMetadata(serviceMetadataIdentifier, URL.encode(url.toFullString()));
+    }
+
+    @Override
+    protected void doRemoveMetadata(ServiceMetadataIdentifier serviceMetadataIdentifier) {
+        this.deleteMetadata(serviceMetadataIdentifier);
+    }
+
+    @Override
+    protected List<String> doGetExportedURLs(ServiceMetadataIdentifier metadataIdentifier) {
+        String content = getMetadata(metadataIdentifier);
+        if (StringUtils.isEmpty(content)) {
+            return Collections.emptyList();
+        }
+        return new ArrayList<>(Arrays.asList(URL.decode(content)));
+    }
+
+    @Override
+    protected void doSaveSubscriberData(SubscriberMetadataIdentifier subscriberMetadataIdentifier, String urlListStr) {
+        this.storeMetadata(subscriberMetadataIdentifier, urlListStr);
+    }
+
+    @Override
+    protected String doGetSubscribedURLs(SubscriberMetadataIdentifier subscriberMetadataIdentifier) {
+        return this.getMetadata(subscriberMetadataIdentifier);
+    }
+
+    @Override
+    public String getServiceDefinition(MetadataIdentifier metadataIdentifier) {
+        return this.getMetadata(metadataIdentifier);
+    }
+
+    private void storeMetadata(BaseMetadataIdentifier metadataIdentifier, String v) {
+        if (pool != null) {
+            storeMetadataStandalone(metadataIdentifier, v);
+        } else {
+            storeMetadataInCluster(metadataIdentifier, v);
+        }
+    }
+
+    private void storeMetadataInCluster(BaseMetadataIdentifier metadataIdentifier, String v) {
+        try (JedisCluster jedisCluster =
+                 new JedisCluster(jedisClusterNodes, timeout, timeout, 2, password, new GenericObjectPoolConfig<>())) {
+            jedisCluster.set(metadataIdentifier.getIdentifierKey() + META_DATA_STORE_TAG, v, jedisParams);
+        } catch (Throwable e) {
+            String msg =
+                "Failed to put " + metadataIdentifier + " to redis cluster " + v + ", cause: " + e.getMessage();
+            logger.error(TRANSPORT_FAILED_RESPONSE, "", "", msg, e);
+            throw new RpcException(msg, e);
+        }
+    }
+
+    private void storeMetadataStandalone(BaseMetadataIdentifier metadataIdentifier, String v) {
+        try (Jedis jedis = pool.getResource()) {
+            jedis.set(metadataIdentifier.getUniqueKey(KeyTypeEnum.UNIQUE_KEY), v, jedisParams);
+        } catch (Throwable e) {
+            String msg = "Failed to put " + metadataIdentifier + " to redis " + v + ", cause: " + e.getMessage();
+            logger.error(TRANSPORT_FAILED_RESPONSE, "", "", msg, e);
+            throw new RpcException(msg, e);
+        }
+    }
+
+    private void deleteMetadata(BaseMetadataIdentifier metadataIdentifier) {
+        if (pool != null) {
+            deleteMetadataStandalone(metadataIdentifier);
+        } else {
+            deleteMetadataInCluster(metadataIdentifier);
+        }
+    }
+
+    private void deleteMetadataInCluster(BaseMetadataIdentifier metadataIdentifier) {
+        try (JedisCluster jedisCluster =
+                 new JedisCluster(jedisClusterNodes, timeout, timeout, 2, password, new GenericObjectPoolConfig<>())) {
+            jedisCluster.del(metadataIdentifier.getIdentifierKey() + META_DATA_STORE_TAG);
+        } catch (Throwable e) {
+            String msg = "Failed to delete " + metadataIdentifier + " from redis cluster , cause: " + e.getMessage();
+            logger.error(TRANSPORT_FAILED_RESPONSE, "", "", msg, e);
+            throw new RpcException(msg, e);
+        }
+    }
+
+    private void deleteMetadataStandalone(BaseMetadataIdentifier metadataIdentifier) {
+        try (Jedis jedis = pool.getResource()) {
+            jedis.del(metadataIdentifier.getUniqueKey(KeyTypeEnum.UNIQUE_KEY));
+        } catch (Throwable e) {
+            String msg = "Failed to delete " + metadataIdentifier + " from redis , cause: " + e.getMessage();
+            logger.error(TRANSPORT_FAILED_RESPONSE, "", "", msg, e);
+            throw new RpcException(msg, e);
+        }
+    }
+
+    private String getMetadata(BaseMetadataIdentifier metadataIdentifier) {
+        if (pool != null) {
+            return getMetadataStandalone(metadataIdentifier);
+        } else {
+            return getMetadataInCluster(metadataIdentifier);
+        }
+    }
+
+    private String getMetadataInCluster(BaseMetadataIdentifier metadataIdentifier) {
+        try (JedisCluster jedisCluster =
+                 new JedisCluster(jedisClusterNodes, timeout, timeout, 2, password, new GenericObjectPoolConfig<>())) {
+            return jedisCluster.get(metadataIdentifier.getIdentifierKey() + META_DATA_STORE_TAG);
+        } catch (Throwable e) {
+            String msg = "Failed to get " + metadataIdentifier + " from redis cluster , cause: " + e.getMessage();
+            logger.error(TRANSPORT_FAILED_RESPONSE, "", "", msg, e);
+            throw new RpcException(msg, e);
+        }
+    }
+
+    private String getMetadataStandalone(BaseMetadataIdentifier metadataIdentifier) {
+        try (Jedis jedis = pool.getResource()) {
+            return jedis.get(metadataIdentifier.getUniqueKey(KeyTypeEnum.UNIQUE_KEY));
+        } catch (Throwable e) {
+            String msg = "Failed to get " + metadataIdentifier + " from redis , cause: " + e.getMessage();
+            logger.error(TRANSPORT_FAILED_RESPONSE, "", "", msg, e);
+            throw new RpcException(msg, e);
+        }
+    }
+
+    /**
+     * Store class and application names using Redis hashes
+     * key: default 'dubbo:mapping'
+     * field: class (serviceInterface)
+     * value: application_names
+     * @param serviceInterface field(class)
+     * @param defaultMappingGroup  {@link ServiceNameMapping#DEFAULT_MAPPING_GROUP}
+     * @param newConfigContent new application_names
+     * @param ticket previous application_names
+     * @return
+     */
+    @Override
+    public boolean registerServiceAppMapping(
+        String serviceInterface, String defaultMappingGroup, String newConfigContent, Object ticket) {
+        try {
+            if (null != ticket && !(ticket instanceof String)) {
+                throw new IllegalArgumentException("redis publishConfigCas requires stat type ticket");
+            }
+            String pathKey = buildMappingKey(defaultMappingGroup);
+
+            return storeMapping(pathKey, serviceInterface, newConfigContent, (String) ticket);
+        } catch (Exception e) {
+            logger.warn(TRANSPORT_FAILED_RESPONSE, "", "", "redis publishConfigCas failed.", e);
+            return false;
+        }
+    }
+
+    private boolean storeMapping(String key, String field, String value, String ticket) {
+        if (pool != null) {
+            return storeMappingStandalone(key, field, value, ticket);
+        } else {
+            return storeMappingInCluster(key, field, value, ticket);
+        }
+    }
+
+    /**
+     * use 'watch' to implement cas.
+     * Find information about slot distribution by key.
+     */
+    private boolean storeMappingInCluster(String key, String field, String value, String ticket) {
+        try (JedisCluster jedisCluster =
+                 new JedisCluster(jedisClusterNodes, timeout, timeout, 2, password, new GenericObjectPoolConfig<>())) {
+            Jedis jedis = new Jedis(jedisCluster.getConnectionFromSlot(JedisClusterCRC16.getSlot(key)));
+            jedis.watch(key);
+            String oldValue = jedis.hget(key, field);
+            if (null == oldValue || null == ticket || oldValue.equals(ticket)) {
+                Transaction transaction = jedis.multi();
+                transaction.hset(key, field, value);
+                List<Object> result = transaction.exec();
+                if (null != result) {
+                    jedisCluster.publish(buildPubSubKey(), field);
+                    return true;
+                }
+            } else {
+                jedis.unwatch();
+            }
+            jedis.close();
+        } catch (Throwable e) {
+            String msg = "Failed to put " + key + ":" + field + " to redis " + value + ", cause: " + e.getMessage();
+            logger.error(TRANSPORT_FAILED_RESPONSE, "", "", msg, e);
+            throw new RpcException(msg, e);
+        }
+        return false;
+    }
+
+    /**
+     * use 'watch' to implement cas.
+     * Find information about slot distribution by key.
+     */
+    private boolean storeMappingStandalone(String key, String field, String value, String ticket) {
+        try (Jedis jedis = pool.getResource()) {
+            jedis.watch(key);
+            String oldValue = jedis.hget(key, field);
+            if (null == oldValue || null == ticket || oldValue.equals(ticket)) {
+                Transaction transaction = jedis.multi();
+                transaction.hset(key, field, value);
+                List<Object> result = transaction.exec();
+                if (null != result) {
+                    jedis.publish(buildPubSubKey(), field);
+                    return true;
+                }
+            }
+            jedis.unwatch();
+        } catch (Throwable e) {
+            String msg = "Failed to put " + key + ":" + field + " to redis " + value + ", cause: " + e.getMessage();
+            logger.error(TRANSPORT_FAILED_RESPONSE, "", "", msg, e);
+            throw new RpcException(msg, e);
+        }
+        return false;
+    }
+
+    /**
+     * build mapping key
+     * @param defaultMappingGroup {@link ServiceNameMapping#DEFAULT_MAPPING_GROUP}
+     * @return
+     */
+    private String buildMappingKey(String defaultMappingGroup) {
+        return this.root + GROUP_CHAR_SEPARATOR + defaultMappingGroup;
+    }
+
+    /**
+     * build pub/sub key
+     */
+    private String buildPubSubKey() {
+        return buildMappingKey(DEFAULT_MAPPING_GROUP) + GROUP_CHAR_SEPARATOR + QUEUES_KEY;
+    }
+
+    /**
+     * get content and use content to complete cas
+     * @param serviceKey class
+     * @param group {@link ServiceNameMapping#DEFAULT_MAPPING_GROUP}
+     */
+    @Override
+    public ConfigItem getConfigItem(String serviceKey, String group) {
+        String key = buildMappingKey(group);
+        String content = getMappingData(key, serviceKey);
+
+        return new ConfigItem(content, content);
+    }
+
+    /**
+     * get current application_names
+     */
+    private String getMappingData(String key, String field) {
+        if (pool != null) {
+            return getMappingDataStandalone(key, field);
+        } else {
+            return getMappingDataInCluster(key, field);
+        }
+    }
+
+    private String getMappingDataInCluster(String key, String field) {
+        try (JedisCluster jedisCluster =
+                 new JedisCluster(jedisClusterNodes, timeout, timeout, 2, password, new GenericObjectPoolConfig<>())) {
+            return jedisCluster.hget(key, field);
+        } catch (Throwable e) {
+            String msg = "Failed to get " + key + ":" + field + " from redis cluster , cause: " + e.getMessage();
+            logger.error(TRANSPORT_FAILED_RESPONSE, "", "", msg, e);
+            throw new RpcException(msg, e);
+        }
+    }
+
+    private String getMappingDataStandalone(String key, String field) {
+        try (Jedis jedis = pool.getResource()) {
+            return jedis.hget(key, field);
+        } catch (Throwable e) {
+            String msg = "Failed to get " + key + ":" + field + " from redis , cause: " + e.getMessage();
+            logger.error(TRANSPORT_FAILED_RESPONSE, "", "", msg, e);
+            throw new RpcException(msg, e);
+        }
+    }
+
+    /**
+     * remove listener. If have no listener,thread will dead
+     */
+    @Override
+    public void removeServiceAppMappingListener(String serviceKey, MappingListener listener) {
+        MappingDataListener mappingDataListener = mappingDataListenerMap.get(buildPubSubKey());
+        if (null != mappingDataListener) {
+            NotifySub notifySub = mappingDataListener.getNotifySub();
+            notifySub.removeListener(serviceKey, listener);
+            if (notifySub.isEmpty()) {
+                mappingDataListener.shutdown();
+            }
+        }
+    }
+
+    /**
+     * Start a thread and subscribe to {@link this#buildPubSubKey()}.
+     * Notify {@link MappingListener} if there is a change in the 'application_names' message.
+     */
+    @Override
+    public Set<String> getServiceAppMapping(String serviceKey, MappingListener listener, URL url) {
+        MappingDataListener mappingDataListener =
+            ConcurrentHashMapUtils.computeIfAbsent(mappingDataListenerMap, buildPubSubKey(), k -> {
+                MappingDataListener dataListener = new MappingDataListener(buildPubSubKey());
+                dataListener.start();
+                return dataListener;
+            });
+        mappingDataListener.getNotifySub().addListener(serviceKey, listener);
+        return this.getServiceAppMapping(serviceKey, url);
+    }
+
+    @Override
+    public Set<String> getServiceAppMapping(String serviceKey, URL url) {
+        String key = buildMappingKey(DEFAULT_MAPPING_GROUP);
+        return getAppNames(getMappingData(key, serviceKey));
+    }
+
+    @Override
+    public MetadataInfo getAppMetadata(SubscriberMetadataIdentifier identifier, Map<String, String> instanceMetadata) {
+        String content = this.getMetadata(identifier);
+        return JsonUtils.toJavaObject(content, MetadataInfo.class);
+    }
+
+    @Override
+    public void publishAppMetadata(SubscriberMetadataIdentifier identifier, MetadataInfo metadataInfo) {
+        this.storeMetadata(identifier, metadataInfo.getContent());
+    }
+
+    @Override
+    public void unPublishAppMetadata(SubscriberMetadataIdentifier identifier, MetadataInfo metadataInfo) {
+        this.deleteMetadata(identifier);
+    }
+
+    // for test
+    public MappingDataListener getMappingDataListener() {
+        return mappingDataListenerMap.get(buildPubSubKey());
+    }
+
+    /**
+     * Listen for changes in the 'application_names' message and notify the listener.
+     */
+    class NotifySub extends JedisPubSub {
+
+        private final Map<String, Set<MappingListener>> listeners = new ConcurrentHashMap<>();
+
+        public void addListener(String key, MappingListener listener) {
+            Set<MappingListener> listenerSet = listeners.computeIfAbsent(key, k -> new ConcurrentHashSet<>());
+            listenerSet.add(listener);
+        }
+
+        public void removeListener(String serviceKey, MappingListener listener) {
+            Set<MappingListener> listenerSet = this.listeners.get(serviceKey);
+            if (listenerSet != null) {
+                listenerSet.remove(listener);
+                if (listenerSet.isEmpty()) {
+                    this.listeners.remove(serviceKey);
+                }
+            }
+        }
+
+        public Boolean isEmpty() {
+            return this.listeners.isEmpty();
+        }
+
+        @Override
+        public void onMessage(String key, String msg) {
+            logger.info("sub from redis:" + key + " message:" + msg);
+            String applicationNames = getMappingData(buildMappingKey(DEFAULT_MAPPING_GROUP), msg);
+            MappingChangedEvent mappingChangedEvent = new MappingChangedEvent(msg, getAppNames(applicationNames));
+            if (!listeners.get(msg).isEmpty()) {
+                for (MappingListener mappingListener : listeners.get(msg)) {
+                    mappingListener.onEvent(mappingChangedEvent);
+                }
+            }
+        }
+
+        @Override
+        public void onPMessage(String pattern, String key, String msg) {
+            onMessage(key, msg);
+        }
+
+        @Override
+        public void onPSubscribe(String pattern, int subscribedChannels) {
+            super.onPSubscribe(pattern, subscribedChannels);
+        }
+    }
+
+    /**
+     * Subscribe application names change message.
+     */
+    class MappingDataListener extends Thread {
+
+        private String path;
+
+        private final NotifySub notifySub = new NotifySub();
+        // for test
+        protected volatile boolean running = true;
+
+        public MappingDataListener(String path) {
+            this.path = path;
+        }
+
+        public NotifySub getNotifySub() {
+            return notifySub;
+        }
+
+        @Override
+        public void run() {
+            while (running) {
+                if (pool != null) {
+                    try (Jedis jedis = pool.getResource()) {
+                        jedis.subscribe(notifySub, path);
+                    } catch (Throwable e) {
+                        String msg = "Failed to subscribe " + path + ", cause: " + e.getMessage();
+                        logger.error(TRANSPORT_FAILED_RESPONSE, "", "", msg, e);
+                        throw new RpcException(msg, e);
+                    }
+                } else {
+                    try (JedisCluster jedisCluster = new JedisCluster(
+                        jedisClusterNodes, timeout, timeout, 2, password, new GenericObjectPoolConfig<>())) {
+                        jedisCluster.subscribe(notifySub, path);
+                    } catch (Throwable e) {
+                        String msg = "Failed to subscribe " + path + ", cause: " + e.getMessage();
+                        logger.error(TRANSPORT_FAILED_RESPONSE, "", "", msg, e);
+                        throw new RpcException(msg, e);
+                    }
+                }
+            }
+        }
+
+        public void shutdown() {
+            try {
+                running = false;
+                notifySub.unsubscribe(path);
+            } catch (Throwable e) {
+                String msg = "Failed to unsubscribe " + path + ", cause: " + e.getMessage();
+                logger.error(TRANSPORT_FAILED_RESPONSE, "", "", msg, e);
+            }
+        }
+    }
+}

+ 59 - 0
xyzc-common/xyzc-common-dubbo/src/main/java/com/xyzc/common/dubbo/config/CustomBeanFactoryPostProcessor.java

@@ -0,0 +1,59 @@
+package com.xyzc.common.dubbo.config;
+
+import org.springframework.beans.BeansException;
+import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
+import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
+import org.springframework.cloud.commons.util.InetUtils;
+import org.springframework.core.Ordered;
+
+import java.net.Inet6Address;
+import java.net.InetAddress;
+
+/**
+ * dubbo自定义IP注入(避免IP不正确问题)
+ *
+ * @author Lion Li
+ */
+public class CustomBeanFactoryPostProcessor implements BeanFactoryPostProcessor, Ordered {
+
+    /**
+     * 获取该 BeanFactoryPostProcessor 的顺序,确保它在容器初始化过程中具有最高优先级
+     *
+     * @return 优先级顺序值,越小优先级越高
+     */
+    @Override
+    public int getOrder() {
+        return Ordered.HIGHEST_PRECEDENCE;
+    }
+
+    /**
+     * 在 Spring 容器初始化过程中对 Bean 工厂进行后置处理
+     *
+     * @param beanFactory 可配置的 Bean 工厂
+     * @throws BeansException 如果在处理过程中发生错误
+     */
+    @Override
+    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
+        // 获取 InetUtils bean,用于获取 IP 地址
+        InetUtils inetUtils = beanFactory.getBean(InetUtils.class);
+        String ip = "127.0.0.1";
+        // 获取第一个非回环地址
+        InetAddress address = inetUtils.findFirstNonLoopbackAddress();
+        if (address != null) {
+            if (address instanceof Inet6Address) {
+                // 处理 IPv6 地址
+                String ipv6AddressString = address.getHostAddress();
+                if (ipv6AddressString.contains("%")) {
+                    // 去掉可能存在的范围 ID
+                    ipv6AddressString = ipv6AddressString.substring(0, ipv6AddressString.indexOf("%"));
+                }
+                ip = ipv6AddressString;
+            } else {
+                // 处理 IPv4 地址
+                ip = inetUtils.findFirstNonLoopbackHostInfo().getIpAddress();
+            }
+        }
+        // 设置系统属性 DUBBO_IP_TO_REGISTRY 为获取到的 IP 地址
+        System.setProperty("DUBBO_IP_TO_REGISTRY", ip);
+    }
+}

+ 26 - 0
xyzc-common/xyzc-common-dubbo/src/main/java/com/xyzc/common/dubbo/config/DubboConfiguration.java

@@ -0,0 +1,26 @@
+package com.xyzc.common.dubbo.config;
+
+import com.xyzc.common.core.YmlPropertySourceFactory;
+import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
+import org.springframework.boot.autoconfigure.AutoConfiguration;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.PropertySource;
+import com.xyzc.common.dubbo.properties.DubboCustomProperties;
+
+/**
+ * dubbo 配置类
+ */
+@AutoConfiguration
+@EnableConfigurationProperties(DubboCustomProperties.class)
+@PropertySource(value = "classpath:common-dubbo.yml", factory = YmlPropertySourceFactory.class)
+public class DubboConfiguration {
+
+    /**
+     * dubbo自定义IP注入(避免IP不正确问题)
+     */
+    @Bean
+    public BeanFactoryPostProcessor customBeanFactoryPostProcessor() {
+        return new CustomBeanFactoryPostProcessor();
+    }
+}

+ 28 - 0
xyzc-common/xyzc-common-dubbo/src/main/java/com/xyzc/common/dubbo/enumd/RequestLogEnum.java

@@ -0,0 +1,28 @@
+package com.xyzc.common.dubbo.enumd;
+
+import lombok.AllArgsConstructor;
+
+/**
+ * 请求日志泛型
+ *
+ * @author Lion Li
+ */
+@AllArgsConstructor
+public enum RequestLogEnum {
+
+    /**
+     * info 基础信息
+     */
+    INFO,
+
+    /**
+     * param 参数信息
+     */
+    PARAM,
+
+    /**
+     * full 全部
+     */
+    FULL;
+
+}

+ 84 - 0
xyzc-common/xyzc-common-dubbo/src/main/java/com/xyzc/common/dubbo/filter/DubboRequestFilter.java

@@ -0,0 +1,84 @@
+package com.xyzc.common.dubbo.filter;
+
+import com.xyzc.common.core.utils.JsonUtils;
+import com.xyzc.common.core.utils.SpringUtils;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.dubbo.common.constants.CommonConstants;
+import org.apache.dubbo.common.extension.Activate;
+import org.apache.dubbo.rpc.*;
+import org.apache.dubbo.rpc.service.GenericService;
+import com.xyzc.common.dubbo.enumd.RequestLogEnum;
+import com.xyzc.common.dubbo.properties.DubboCustomProperties;
+
+/**
+ * Dubbo 日志过滤器
+ * <p>
+ * 该过滤器通过实现 Dubbo 的 Filter 接口,在服务调用前后记录日志信息
+ * 可根据配置开关和日志级别输出不同详细程度的日志信息
+ * <p>
+ * 激活条件:
+ * - 在 Provider 和 Consumer 端都生效
+ * - 执行顺序设置为最大值,确保在所有其他过滤器之后执行
+ * <p>
+ * 使用 SpringUtils 获取配置信息,根据配置决定是否记录日志及日志详细程度
+ * <p>
+ * 使用 Lombok 的 @Slf4j 注解简化日志记录
+ *
+ * @author Lion Li
+ */
+@Slf4j
+@Activate(group = {CommonConstants.PROVIDER, CommonConstants.CONSUMER}, order = Integer.MAX_VALUE)
+public class DubboRequestFilter implements Filter {
+
+    /**
+     * Dubbo Filter 接口实现方法,处理服务调用逻辑并记录日志
+     *
+     * @param invoker    Dubbo 服务调用者实例
+     * @param invocation 调用的具体方法信息
+     * @return 调用结果
+     * @throws RpcException 如果调用过程中发生异常
+     */
+    @Override
+    public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
+        DubboCustomProperties properties = SpringUtils.getBean(DubboCustomProperties.class);
+        // 如果未开启请求日志记录,则直接执行服务调用并返回结果
+        if (!properties.getRequestLog()) {
+            return invoker.invoke(invocation);
+        }
+
+        // 判断是 Provider 还是 Consumer
+        String client = CommonConstants.PROVIDER;
+        if (RpcContext.getServiceContext().isConsumerSide()) {
+            client = CommonConstants.CONSUMER;
+        }
+
+        // 构建基础日志信息
+        String baselog = "Client[" + client + "],InterfaceName=[" + invocation.getInvoker().getInterface().getSimpleName() + "],MethodName=[" + invocation.getMethodName() + "]";
+        // 根据日志级别输出不同详细程度的日志信息
+        if (properties.getLogLevel() == RequestLogEnum.INFO) {
+            log.info("DUBBO - 服务调用: {}", baselog);
+        } else {
+            log.info("DUBBO - 服务调用: {},Parameter={}", baselog, invocation.getArguments());
+        }
+
+        // 记录调用开始时间
+        long startTime = System.currentTimeMillis();
+        // 执行接口调用逻辑
+        Result result = invoker.invoke(invocation);
+        // 计算调用耗时
+        long elapsed = System.currentTimeMillis() - startTime;
+        // 如果发生异常且调用的是泛化服务,则记录异常日志
+        if (result.hasException() && invoker.getInterface().equals(GenericService.class)) {
+            log.error("DUBBO - 服务异常: {},Exception={}", baselog, result.getException().toString());
+        } else {
+            // 根据日志级别输出服务响应信息
+            if (properties.getLogLevel() == RequestLogEnum.INFO) {
+                log.info("DUBBO - 服务响应: {},SpendTime=[{}ms]", baselog, elapsed);
+            } else if (properties.getLogLevel() == RequestLogEnum.FULL) {
+                log.info("DUBBO - 服务响应: {},SpendTime=[{}ms],Response={}", baselog, elapsed, JsonUtils.toJsonString(new Object[]{result.getValue()}));
+            }
+        }
+        return result;
+    }
+
+}

+ 28 - 0
xyzc-common/xyzc-common-dubbo/src/main/java/com/xyzc/common/dubbo/properties/DubboCustomProperties.java

@@ -0,0 +1,28 @@
+package com.xyzc.common.dubbo.properties;
+
+import com.xyzc.common.dubbo.enumd.RequestLogEnum;
+import lombok.Data;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.cloud.context.config.annotation.RefreshScope;
+
+/**
+ * 自定义配置
+ *
+ * @author Lion Li
+ */
+@Data
+@RefreshScope
+@ConfigurationProperties(prefix = "dubbo.custom")
+public class DubboCustomProperties {
+
+    /**
+     * 是否开启请求日志记录
+     */
+    private Boolean requestLog;
+
+    /**
+     * 日志级别
+     */
+    private RequestLogEnum logLevel;
+
+}

+ 1 - 0
xyzc-common/xyzc-common-dubbo/src/main/resources/META-INF/dubbo/org.apache.dubbo.rpc.Filter

@@ -0,0 +1 @@
+dubboRequestFilter=com.xyzc.common.dubbo.filter.DubboRequestFilter

+ 1 - 0
xyzc-common/xyzc-common-dubbo/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports

@@ -0,0 +1 @@
+com.xyzc.common.dubbo.config.DubboConfiguration

+ 40 - 0
xyzc-common/xyzc-common-dubbo/src/main/resources/common-dubbo.yml

@@ -0,0 +1,40 @@
+# 内置配置 不允许修改 如需修改请在 nacos 上写相同配置覆盖
+dubbo:
+  application:
+    logger: slf4j
+    # 元数据中心 local 本地 remote 远程 这里使用远程便于其他服务获取
+    metadataType: remote
+    # 可选值 interface、instance、all,默认是 all,即接口级地址、应用级地址都注册
+    register-mode: instance
+    service-discovery:
+      # FORCE_INTERFACE,只消费接口级地址,如无地址则报错,单订阅 2.x 地址
+      # APPLICATION_FIRST,智能决策接口级/应用级地址,双订阅
+      # FORCE_APPLICATION,只消费应用级地址,如无地址则报错,单订阅 3.x 地址
+      migration: FORCE_APPLICATION
+  # 注册中心配置
+  registry:
+    address: nacos://${spring.cloud.nacos.server-addr}
+    group: DUBBO_GROUP
+    username: ${spring.cloud.nacos.username}
+    password: ${spring.cloud.nacos.password}
+    parameters:
+      namespace: ${spring.profiles.active}
+  metadata-report:
+    address: redis://${spring.data.redis.host}:${spring.data.redis.port}
+    group: DUBBO_GROUP
+    username: dubbo
+    password: ${spring.data.redis.password}
+    parameters:
+      namespace: ${spring.profiles.active}
+      database: ${spring.data.redis.database}
+  # 消费者相关配置
+  consumer:
+    # 结果缓存(LRU算法)
+    # 会有数据不一致问题 建议在注解局部开启
+    cache: false
+    # 支持校验注解
+    validation: jvalidationNew
+    # 调用重试 不包括第一次 0为不需要重试
+    retries: 0
+    # 初始化检查
+    check: false

+ 71 - 0
xyzc-common/xyzc-common-oss/pom.xml

@@ -0,0 +1,71 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>com.xyzc</groupId>
+        <artifactId>xyzc-common</artifactId>
+        <version>${revision}</version>
+    </parent>
+
+    <artifactId>xyzc-common-oss</artifactId>
+
+
+    <description>
+        xyzc-common-oss oss服务
+    </description>
+
+    <dependencies>
+
+        <dependency>
+            <groupId>com.xyzc</groupId>
+            <artifactId>xyzc-common-core</artifactId>
+        </dependency>
+
+        <!--  AWS SDK for Java 2.x  -->
+        <dependency>
+            <groupId>software.amazon.awssdk</groupId>
+            <artifactId>s3</artifactId>
+            <exclusions>
+                <!-- 将基于 Netty 的 HTTP 客户端从类路径中移除 -->
+                <exclusion>
+                    <groupId>software.amazon.awssdk</groupId>
+                    <artifactId>netty-nio-client</artifactId>
+                </exclusion>
+                <!-- 将基于 CRT 的 HTTP 客户端从类路径中移除 -->
+                <exclusion>
+                    <groupId>software.amazon.awssdk</groupId>
+                    <artifactId>aws-crt-client</artifactId>
+                </exclusion>
+                <!-- 将基于 Apache 的 HTTP 客户端从类路径中移除 -->
+                <exclusion>
+                    <groupId>software.amazon.awssdk</groupId>
+                    <artifactId>apache-client</artifactId>
+                </exclusion>
+                <!-- 将配置基于 URL 连接的 HTTP 客户端从类路径中移除 -->
+                <exclusion>
+                    <groupId>software.amazon.awssdk</groupId>
+                    <artifactId>url-connection-client</artifactId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+
+        <!-- 使用AWS基于 CRT 的 S3 客户端 -->
+        <dependency>
+            <groupId>software.amazon.awssdk.crt</groupId>
+            <artifactId>aws-crt</artifactId>
+        </dependency>
+
+        <!-- 基于 AWS CRT 的 S3 客户端的性能增强的 S3 传输管理器 -->
+        <dependency>
+            <groupId>software.amazon.awssdk</groupId>
+            <artifactId>s3-transfer-manager</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.xyzc</groupId>
+            <artifactId>xyzc-common-redis</artifactId>
+        </dependency>
+
+    </dependencies>
+</project>

+ 41 - 0
xyzc-common/xyzc-common-oss/src/main/java/com/xyzc/common/oss/constant/OssConstant.java

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

+ 595 - 0
xyzc-common/xyzc-common-oss/src/main/java/com/xyzc/common/oss/core/OssClient.java

@@ -0,0 +1,595 @@
+package com.xyzc.common.oss.core;
+
+import cn.hutool.core.io.IoUtil;
+import cn.hutool.core.util.IdUtil;
+import com.xyzc.common.core.constant.Const;
+import com.xyzc.common.core.utils.DateUtils;
+import com.xyzc.common.core.utils.StringUtils;
+import com.xyzc.common.core.utils.file.FileUtils;
+import com.xyzc.common.oss.constant.OssConstant;
+import com.xyzc.common.oss.entity.UploadResult;
+import com.xyzc.common.oss.enumd.AccessPolicyType;
+import com.xyzc.common.oss.enumd.PolicyType;
+import com.xyzc.common.oss.exception.OssException;
+import com.xyzc.common.oss.properties.OssProperties;
+import software.amazon.awssdk.auth.credentials.AwsBasicCredentials;
+import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider;
+import software.amazon.awssdk.core.ResponseInputStream;
+import software.amazon.awssdk.core.async.AsyncRequestBody;
+import software.amazon.awssdk.core.async.AsyncResponseTransformer;
+import software.amazon.awssdk.core.async.BlockingInputStreamAsyncRequestBody;
+import software.amazon.awssdk.regions.Region;
+import software.amazon.awssdk.services.s3.S3AsyncClient;
+import software.amazon.awssdk.services.s3.S3Configuration;
+import software.amazon.awssdk.services.s3.model.GetObjectResponse;
+import software.amazon.awssdk.services.s3.model.NoSuchBucketException;
+import software.amazon.awssdk.services.s3.model.S3Exception;
+import software.amazon.awssdk.services.s3.presigner.S3Presigner;
+import software.amazon.awssdk.transfer.s3.S3TransferManager;
+import software.amazon.awssdk.transfer.s3.model.*;
+import software.amazon.awssdk.transfer.s3.progress.LoggingTransferListener;
+
+import java.io.*;
+import java.net.URI;
+import java.net.URL;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.time.Duration;
+
+/**
+ * S3 存储协议 所有兼容S3协议的云厂商均支持
+ * 阿里云 腾讯云 七牛云 minio
+ *
+ * @author AprilWind
+ */
+public class OssClient {
+
+    /**
+     * 服务商
+     */
+    private final String configKey;
+
+    /**
+     * 配置属性
+     */
+    private final OssProperties properties;
+
+    /**
+     * Amazon S3 异步客户端
+     */
+    private final S3AsyncClient client;
+
+    /**
+     * 用于管理 S3 数据传输的高级工具
+     */
+    private final S3TransferManager transferManager;
+
+    /**
+     * AWS S3 预签名 URL 的生成器
+     */
+    private final S3Presigner presigner;
+
+    /**
+     * 构造方法
+     *
+     * @param configKey     配置键
+     * @param ossProperties Oss配置属性
+     */
+    public OssClient(String configKey, OssProperties ossProperties) {
+        this.configKey = configKey;
+        this.properties = ossProperties;
+        try {
+            // 创建 AWS 认证信息
+            StaticCredentialsProvider credentialsProvider = StaticCredentialsProvider.create(
+                AwsBasicCredentials.create(properties.getAccessKey(), properties.getSecretKey()));
+
+            //MinIO 使用 HTTPS 限制使用域名访问,站点填域名。需要启用路径样式访问
+            boolean isStyle = !StringUtils.containsAny(properties.getEndpoint(), OssConstant.CLOUD_SERVICE);
+
+            //创建AWS基于 CRT 的 S3 客户端
+            this.client = S3AsyncClient.crtBuilder()
+                .credentialsProvider(credentialsProvider)
+                .endpointOverride(URI.create(getEndpoint()))
+                .region(of())
+                .targetThroughputInGbps(20.0)
+                .minimumPartSizeInBytes(10 * 1025 * 1024L)
+                .checksumValidationEnabled(false)
+                .forcePathStyle(isStyle)
+                .build();
+
+            //AWS基于 CRT 的 S3 AsyncClient 实例用作 S3 传输管理器的底层客户端
+            this.transferManager = S3TransferManager.builder().s3Client(this.client).build();
+
+            // 创建 S3 配置对象
+            S3Configuration config = S3Configuration.builder().chunkedEncodingEnabled(false)
+                .pathStyleAccessEnabled(isStyle).build();
+
+            // 创建 预签名 URL 的生成器 实例,用于生成 S3 预签名 URL
+            this.presigner = S3Presigner.builder()
+                .region(of())
+                .credentialsProvider(credentialsProvider)
+                .endpointOverride(URI.create(getDomain()))
+                .serviceConfiguration(config)
+                .build();
+
+            // 创建存储桶
+            createBucket();
+        } catch (Exception e) {
+            if (e instanceof OssException) {
+                throw e;
+            }
+            throw new OssException("配置错误! 请检查系统配置:[" + e.getMessage() + "]");
+        }
+    }
+
+    /**
+     * 同步创建存储桶
+     * 如果存储桶不存在,会进行创建;如果存储桶存在,不执行任何操作
+     *
+     * @throws OssException 当创建存储桶时发生异常时抛出
+     */
+    public void createBucket() {
+        String bucketName = properties.getBucketName();
+        try {
+            // 尝试获取存储桶的信息
+            client.headBucket(
+                    x -> x.bucket(bucketName)
+                        .build())
+                .join();
+        } catch (Exception ex) {
+            if (ex.getCause() instanceof NoSuchBucketException) {
+                try {
+                    // 存储桶不存在,尝试创建存储桶
+                    client.createBucket(
+                            x -> x.bucket(bucketName))
+                        .join();
+
+                    // 设置存储桶的访问策略(Bucket Policy)
+                    client.putBucketPolicy(
+                            x -> x.bucket(bucketName)
+                                .policy(getPolicy(bucketName, getAccessPolicy().getPolicyType())))
+                        .join();
+                } catch (S3Exception e) {
+                    // 存储桶创建或策略设置失败
+                    throw new OssException("创建Bucket失败, 请核对配置信息:[" + e.getMessage() + "]");
+                }
+            } else {
+                throw new OssException("判断Bucket是否存在失败,请核对配置信息:[" + ex.getMessage() + "]");
+            }
+        }
+    }
+
+    /**
+     * 上传文件到 Amazon S3,并返回上传结果
+     *
+     * @param filePath    本地文件路径
+     * @param key         在 Amazon S3 中的对象键
+     * @param md5Digest   本地文件的 MD5 哈希值(可选)
+     * @param contentType 文件内容类型
+     * @return UploadResult 包含上传后的文件信息
+     * @throws OssException 如果上传失败,抛出自定义异常
+     */
+    public UploadResult upload(Path filePath, String key, String md5Digest, String contentType) {
+        try {
+            // 构建上传请求对象
+            FileUpload fileUpload = transferManager.uploadFile(
+                x -> x.putObjectRequest(
+                        y -> y.bucket(properties.getBucketName())
+                            .key(key)
+                            .contentMD5(StringUtils.isNotEmpty(md5Digest) ? md5Digest : null)
+                            .contentType(contentType)
+                            .acl(getAccessPolicy().getObjectCannedACL())
+                            .build())
+                    .addTransferListener(LoggingTransferListener.create())
+                    .source(filePath).build());
+
+            // 等待上传完成并获取上传结果
+            CompletedFileUpload uploadResult = fileUpload.completionFuture().join();
+            String eTag = uploadResult.response().eTag();
+
+            // 提取上传结果中的 ETag,并构建一个自定义的 UploadResult 对象
+            return UploadResult.builder().url(getUrl() + StringUtils.SLASH + key).filename(key).eTag(eTag).build();
+        } catch (Exception e) {
+            // 捕获异常并抛出自定义异常
+            throw new OssException("上传文件失败,请检查配置信息:[" + e.getMessage() + "]");
+        } finally {
+            // 无论上传是否成功,最终都会删除临时文件
+            FileUtils.del(filePath);
+        }
+    }
+
+    /**
+     * 上传 InputStream 到 Amazon S3
+     *
+     * @param inputStream 要上传的输入流
+     * @param key         在 Amazon S3 中的对象键
+     * @param length      输入流的长度
+     * @param contentType 文件内容类型
+     * @return UploadResult 包含上传后的文件信息
+     * @throws OssException 如果上传失败,抛出自定义异常
+     */
+    public UploadResult upload(InputStream inputStream, String key, Long length, String contentType) {
+        // 如果输入流不是 ByteArrayInputStream,则将其读取为字节数组再创建 ByteArrayInputStream
+        if (!(inputStream instanceof ByteArrayInputStream)) {
+            inputStream = new ByteArrayInputStream(IoUtil.readBytes(inputStream));
+        }
+        try {
+            // 创建异步请求体(length如果为空会报错)
+            BlockingInputStreamAsyncRequestBody body = AsyncRequestBody.forBlockingInputStream(length);
+
+            // 使用 transferManager 进行上传
+            Upload upload = transferManager.upload(
+                x -> x.requestBody(body)
+                    .putObjectRequest(
+                        y -> y.bucket(properties.getBucketName())
+                            .key(key)
+                            .contentType(contentType)
+                            .acl(getAccessPolicy().getObjectCannedACL())
+                            .build())
+                    .build());
+
+            // 将输入流写入请求体
+            body.writeInputStream(inputStream);
+
+            // 等待文件上传操作完成
+            CompletedUpload uploadResult = upload.completionFuture().join();
+            String eTag = uploadResult.response().eTag();
+
+            // 提取上传结果中的 ETag,并构建一个自定义的 UploadResult 对象
+            return UploadResult.builder().url(getUrl() + StringUtils.SLASH + key).filename(key).eTag(eTag).build();
+        } catch (Exception e) {
+            throw new OssException("上传文件失败,请检查配置信息:[" + e.getMessage() + "]");
+        }
+    }
+
+    /**
+     * 下载文件从 Amazon S3 到临时目录
+     *
+     * @param path 文件在 Amazon S3 中的对象键
+     * @return 下载后的文件在本地的临时路径
+     * @throws OssException 如果下载失败,抛出自定义异常
+     */
+    public Path fileDownload(String path) {
+        // 构建临时文件
+        Path tempFilePath = FileUtils.createTempFile().toPath();
+        // 使用 S3TransferManager 下载文件
+        FileDownload downloadFile = transferManager.downloadFile(
+            x -> x.getObjectRequest(
+                    y -> y.bucket(properties.getBucketName())
+                        .key(removeBaseUrl(path))
+                        .build())
+                .addTransferListener(LoggingTransferListener.create())
+                .destination(tempFilePath)
+                .build());
+        // 等待文件下载操作完成
+        downloadFile.completionFuture().join();
+        return tempFilePath;
+    }
+
+    /**
+     * 下载文件从 Amazon S3 到 输出流
+     *
+     * @param key 文件在 Amazon S3 中的对象键
+     * @param out 输出流
+     * @return 输出流中写入的字节数(长度)
+     * @throws OssException 如果下载失败,抛出自定义异常
+     */
+    public long download(String key, OutputStream out) {
+        try {
+            // 构建下载请求
+            DownloadRequest<ResponseInputStream<GetObjectResponse>> downloadRequest = DownloadRequest.builder()
+                // 文件对象
+                .getObjectRequest(y -> y.bucket(properties.getBucketName())
+                    .key(key)
+                    .build())
+                .addTransferListener(LoggingTransferListener.create())
+                // 使用订阅转换器
+                .responseTransformer(AsyncResponseTransformer.toBlockingInputStream())
+                .build();
+            // 使用 S3TransferManager 下载文件
+            Download<ResponseInputStream<GetObjectResponse>> responseFuture = transferManager.download(downloadRequest);
+            // 输出到流中
+            try (ResponseInputStream<GetObjectResponse> responseStream = responseFuture.completionFuture().join().result()) { // auto-closeable stream
+                return responseStream.transferTo(out); // 阻塞调用线程 blocks the calling thread
+            }
+        } catch (Exception e) {
+            throw new OssException("文件下载失败,错误信息:[" + e.getMessage() + "]");
+        }
+    }
+
+    /**
+     * 删除云存储服务中指定路径下文件
+     *
+     * @param path 指定路径
+     */
+    public void delete(String path) {
+        try {
+            client.deleteObject(
+                x -> x.bucket(properties.getBucketName())
+                    .key(removeBaseUrl(path))
+                    .build());
+        } catch (Exception e) {
+            throw new OssException("删除文件失败,请检查配置信息:[" + e.getMessage() + "]");
+        }
+    }
+
+    /**
+     * 获取私有URL链接
+     *
+     * @param objectKey 对象KEY
+     * @param second    授权时间
+     */
+    public String getPrivateUrl(String objectKey, Integer second) {
+        // 使用 AWS S3 预签名 URL 的生成器 获取对象的预签名 URL
+        URL url = presigner.presignGetObject(
+                x -> x.signatureDuration(Duration.ofSeconds(second))
+                    .getObjectRequest(
+                        y -> y.bucket(properties.getBucketName())
+                            .key(objectKey)
+                            .build())
+                    .build())
+            .url();
+        return url.toString();
+    }
+
+    /**
+     * 上传 byte[] 数据到 Amazon S3,使用指定的后缀构造对象键。
+     *
+     * @param data   要上传的 byte[] 数据
+     * @param suffix 对象键的后缀
+     * @return UploadResult 包含上传后的文件信息
+     * @throws OssException 如果上传失败,抛出自定义异常
+     */
+    public UploadResult uploadSuffix(byte[] data, String suffix) {
+        return upload(new ByteArrayInputStream(data), getPath(properties.getPrefix(), suffix), Long.valueOf(data.length), FileUtils.getMimeType(suffix));
+    }
+
+    /**
+     * 上传 InputStream 到 Amazon S3,使用指定的后缀构造对象键。
+     *
+     * @param inputStream 要上传的输入流
+     * @param suffix      对象键的后缀
+     * @param length      输入流的长度
+     * @return UploadResult 包含上传后的文件信息
+     * @throws OssException 如果上传失败,抛出自定义异常
+     */
+    public UploadResult uploadSuffix(InputStream inputStream, String suffix, Long length) {
+        return upload(inputStream, getPath(properties.getPrefix(), suffix), length, FileUtils.getMimeType(suffix));
+    }
+
+    /**
+     * 上传文件到 Amazon S3,使用指定的后缀构造对象键
+     *
+     * @param file   要上传的文件
+     * @param suffix 对象键的后缀
+     * @return UploadResult 包含上传后的文件信息
+     * @throws OssException 如果上传失败,抛出自定义异常
+     */
+    public UploadResult uploadSuffix(File file, String suffix) {
+        return upload(file.toPath(), getPath(properties.getPrefix(), suffix), null, FileUtils.getMimeType(suffix));
+    }
+
+    /**
+     * 获取文件输入流
+     *
+     * @param path 完整文件路径
+     * @return 输入流
+     */
+    public InputStream getObjectContent(String path) throws IOException {
+        // 下载文件到临时目录
+        Path tempFilePath = fileDownload(path);
+        // 创建输入流
+        InputStream inputStream = Files.newInputStream(tempFilePath);
+        // 删除临时文件
+        FileUtils.del(tempFilePath);
+        // 返回对象内容的输入流
+        return inputStream;
+    }
+
+    /**
+     * 获取 S3 客户端的终端点 URL
+     *
+     * @return 终端点 URL
+     */
+    public String getEndpoint() {
+        // 根据配置文件中的是否使用 HTTPS,设置协议头部
+        String header = getIsHttps();
+        // 拼接协议头部和终端点,得到完整的终端点 URL
+        return header + properties.getEndpoint();
+    }
+
+    /**
+     * 获取 S3 客户端的终端点 URL(自定义域名)
+     *
+     * @return 终端点 URL
+     */
+    public String getDomain() {
+        // 从配置中获取域名、终端点、是否使用 HTTPS 等信息
+        String domain = properties.getDomain();
+        String endpoint = properties.getEndpoint();
+        String header = getIsHttps();
+
+        // 如果是云服务商,直接返回域名或终端点
+        if (StringUtils.containsAny(endpoint, OssConstant.CLOUD_SERVICE)) {
+            return StringUtils.isNotEmpty(domain) ? header + domain : header + endpoint;
+        }
+
+        // 如果是 MinIO,处理域名并返回
+        if (StringUtils.isNotEmpty(domain)) {
+            return domain.startsWith(Const.HTTPS) || domain.startsWith(Const.HTTP) ? domain : header + domain;
+        }
+
+        // 返回终端点
+        return header + endpoint;
+    }
+
+    /**
+     * 根据传入的 region 参数返回相应的 AWS 区域
+     * 如果 region 参数非空,使用 Region.of 方法创建并返回对应的 AWS 区域对象
+     * 如果 region 参数为空,返回一个默认的 AWS 区域(例如,us-east-1),作为广泛支持的区域
+     *
+     * @return 对应的 AWS 区域对象,或者默认的广泛支持的区域(us-east-1)
+     */
+    public Region of() {
+        //AWS 区域字符串
+        String region = properties.getRegion();
+        // 如果 region 参数非空,使用 Region.of 方法创建对应的 AWS 区域对象,否则返回默认区域
+        return StringUtils.isNotEmpty(region) ? Region.of(region) : Region.US_EAST_1;
+    }
+
+    /**
+     * 获取云存储服务的URL
+     *
+     * @return 文件路径
+     */
+    public String getUrl() {
+        String domain = properties.getDomain();
+        String endpoint = properties.getEndpoint();
+        String header = getIsHttps();
+        // 云服务商直接返回
+        if (StringUtils.containsAny(endpoint, OssConstant.CLOUD_SERVICE)) {
+            return header + (StringUtils.isNotEmpty(domain) ? domain : properties.getBucketName() + "." + endpoint);
+        }
+        // MinIO 单独处理
+        if (StringUtils.isNotEmpty(domain)) {
+            // 如果 domain 以 "https://" 或 "http://" 开头
+            return (domain.startsWith(Const.HTTPS) || domain.startsWith(Const.HTTP)) ?
+                domain + StringUtils.SLASH + properties.getBucketName() : header + domain + StringUtils.SLASH + properties.getBucketName();
+        }
+        return header + endpoint + StringUtils.SLASH + properties.getBucketName();
+    }
+
+    /**
+     * 生成一个符合特定规则的、唯一的文件路径。通过使用日期、UUID、前缀和后缀等元素的组合,确保了文件路径的独一无二性
+     *
+     * @param prefix 前缀
+     * @param suffix 后缀
+     * @return 文件路径
+     */
+    public String getPath(String prefix, String suffix) {
+        // 生成uuid
+        String uuid = IdUtil.fastSimpleUUID();
+        // 生成日期路径
+        String datePath = DateUtils.datePath();
+        // 拼接路径
+        String path = StringUtils.isNotEmpty(prefix) ?
+            prefix + StringUtils.SLASH + datePath + StringUtils.SLASH + uuid : datePath + StringUtils.SLASH + uuid;
+        return path + suffix;
+    }
+
+    /**
+     * 移除路径中的基础URL部分,得到相对路径
+     *
+     * @param path 完整的路径,包括基础URL和相对路径
+     * @return 去除基础URL后的相对路径
+     */
+    public String removeBaseUrl(String path) {
+        return path.replace(getUrl() + StringUtils.SLASH, "");
+    }
+
+    /**
+     * 服务商
+     */
+    public String getConfigKey() {
+        return configKey;
+    }
+
+    /**
+     * 获取是否使用 HTTPS 的配置,并返回相应的协议头部。
+     *
+     * @return 协议头部,根据是否使用 HTTPS 返回 "https://" 或 "http://"
+     */
+    public String getIsHttps() {
+        return OssConstant.IS_HTTPS.equals(properties.getIsHttps()) ? Const.HTTPS : Const.HTTP;
+    }
+
+    /**
+     * 检查配置是否相同
+     */
+    public boolean checkPropertiesSame(OssProperties properties) {
+        return this.properties.equals(properties);
+    }
+
+    /**
+     * 获取当前桶权限类型
+     *
+     * @return 当前桶权限类型code
+     */
+    public AccessPolicyType getAccessPolicy() {
+        return AccessPolicyType.getByType(properties.getAccessPolicy());
+    }
+
+    /**
+     * 生成 AWS S3 存储桶访问策略
+     *
+     * @param bucketName 存储桶
+     * @param policyType 桶策略类型
+     * @return 符合 AWS S3 存储桶访问策略格式的字符串
+     */
+    private static String getPolicy(String bucketName, PolicyType policyType) {
+        String policy = switch (policyType) {
+            case WRITE -> """
+                {
+                  "Version": "2012-10-17",
+                  "Statement": []
+                }
+                """;
+            case READ_WRITE -> """
+                {
+                  "Version": "2012-10-17",
+                  "Statement": [
+                    {
+                      "Effect": "Allow",
+                      "Principal": "*",
+                      "Action": [
+                        "s3:GetBucketLocation",
+                        "s3:ListBucket",
+                        "s3:ListBucketMultipartUploads"
+                      ],
+                      "Resource": "arn:aws:s3:::bucketName"
+                    },
+                    {
+                      "Effect": "Allow",
+                      "Principal": "*",
+                      "Action": [
+                        "s3:AbortMultipartUpload",
+                        "s3:DeleteObject",
+                        "s3:GetObject",
+                        "s3:ListMultipartUploadParts",
+                        "s3:PutObject"
+                      ],
+                      "Resource": "arn:aws:s3:::bucketName/*"
+                    }
+                  ]
+                }
+                """;
+            case READ -> """
+                {
+                  "Version": "2012-10-17",
+                  "Statement": [
+                    {
+                      "Effect": "Allow",
+                      "Principal": "*",
+                      "Action": ["s3:GetBucketLocation"],
+                      "Resource": "arn:aws:s3:::bucketName"
+                    },
+                    {
+                      "Effect": "Deny",
+                      "Principal": "*",
+                      "Action": ["s3:ListBucket"],
+                      "Resource": "arn:aws:s3:::bucketName"
+                    },
+                    {
+                      "Effect": "Allow",
+                      "Principal": "*",
+                      "Action": "s3:GetObject",
+                      "Resource": "arn:aws:s3:::bucketName/*"
+                    }
+                  ]
+                }
+                """;
+        };
+        return policy.replaceAll("bucketName", bucketName);
+    }
+
+}

+ 30 - 0
xyzc-common/xyzc-common-oss/src/main/java/com/xyzc/common/oss/entity/UploadResult.java

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

+ 61 - 0
xyzc-common/xyzc-common-oss/src/main/java/com/xyzc/common/oss/enumd/AccessPolicyType.java

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

+ 35 - 0
xyzc-common/xyzc-common-oss/src/main/java/com/xyzc/common/oss/enumd/PolicyType.java

@@ -0,0 +1,35 @@
+package com.xyzc.common.oss.enumd;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * minio策略配置
+ *
+ * @author Lion Li
+ */
+@Getter
+@AllArgsConstructor
+public enum PolicyType {
+
+    /**
+     * 只读
+     */
+    READ("read-only"),
+
+    /**
+     * 只写
+     */
+    WRITE("write-only"),
+
+    /**
+     * 读写
+     */
+    READ_WRITE("read-write");
+
+    /**
+     * 类型
+     */
+    private final String type;
+
+}

+ 19 - 0
xyzc-common/xyzc-common-oss/src/main/java/com/xyzc/common/oss/exception/OssException.java

@@ -0,0 +1,19 @@
+package com.xyzc.common.oss.exception;
+
+import java.io.Serial;
+
+/**
+ * OSS异常类
+ *
+ * @author Lion Li
+ */
+public class OssException extends RuntimeException {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    public OssException(String msg) {
+        super(msg);
+    }
+
+}

+ 73 - 0
xyzc-common/xyzc-common-oss/src/main/java/com/xyzc/common/oss/factory/OssFactory.java

@@ -0,0 +1,73 @@
+package com.xyzc.common.oss.factory;
+
+import com.xyzc.common.core.constant.CacheNames;
+import com.xyzc.common.core.utils.JsonUtils;
+import com.xyzc.common.core.utils.StringUtils;
+import com.xyzc.common.oss.constant.OssConstant;
+import com.xyzc.common.oss.core.OssClient;
+import com.xyzc.common.oss.exception.OssException;
+import com.xyzc.common.oss.properties.OssProperties;
+import com.xyzc.common.redis.CacheUtils;
+import com.xyzc.common.redis.RedisUtils;
+import lombok.extern.slf4j.Slf4j;
+
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.locks.ReentrantLock;
+
+/**
+ * 文件上传Factory
+ *
+ * @author Lion Li
+ */
+@Slf4j
+public class OssFactory {
+
+    private static final Map<String, OssClient> CLIENT_CACHE = new ConcurrentHashMap<>();
+    private static final ReentrantLock LOCK = new ReentrantLock();
+
+    /**
+     * 获取默认实例
+     */
+    public static OssClient instance() {
+        // 获取redis 默认类型
+        String configKey = RedisUtils.getCacheObject(OssConstant.DEFAULT_CONFIG_KEY);
+        if (StringUtils.isEmpty(configKey)) {
+            throw new OssException("文件存储服务类型无法找到!");
+        }
+        return instance(configKey);
+    }
+
+    /**
+     * 根据类型获取实例
+     */
+    public static synchronized OssClient instance(String configKey) {
+        String json = CacheUtils.get(CacheNames.SYS_OSS_CONFIG, configKey);
+        if (json == null) {
+            throw new OssException("系统异常, '" + configKey + "'配置信息不存在!");
+        }
+        OssProperties properties = JsonUtils.parseObject(json, OssProperties.class);
+        // 使用租户标识避免多个租户相同key实例覆盖
+        String key = configKey;
+        if (StringUtils.isNotBlank(properties.getTenantId())) {
+            key = properties.getTenantId() + ":" + configKey;
+        }
+        OssClient client = CLIENT_CACHE.get(key);
+        // 客户端不存在或配置不相同则重新构建
+        if (client == null || !client.checkPropertiesSame(properties)) {
+            LOCK.lock();
+            try {
+                client = CLIENT_CACHE.get(key);
+                if (client == null || !client.checkPropertiesSame(properties)) {
+                    CLIENT_CACHE.put(key, new OssClient(configKey, properties));
+                    log.info("创建OSS实例 key => {}", configKey);
+                    return CLIENT_CACHE.get(key);
+                }
+            } finally {
+                LOCK.unlock();
+            }
+        }
+        return client;
+    }
+
+}

+ 63 - 0
xyzc-common/xyzc-common-oss/src/main/java/com/xyzc/common/oss/properties/OssProperties.java

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

+ 50 - 0
xyzc-common/xyzc-common-oss/src/main/java/com/xyzc/common/oss/utils/OssUtils.java

@@ -0,0 +1,50 @@
+package com.xyzc.common.oss.utils;
+
+import cn.hutool.core.util.StrUtil;
+import com.xyzc.common.core.exception.ServerException;
+import com.xyzc.common.core.utils.StringUtils;
+import com.xyzc.common.oss.entity.UploadResult;
+import com.xyzc.common.oss.factory.OssFactory;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.io.IOException;
+
+/**
+ * oss 工具类
+ *
+ * @author yz
+ * @date 2024/7/12 上午11:07
+ */
+public class OssUtils {
+
+    /**
+     * 上传文件 并返回 ossUrl
+     *
+     * @param file 文件
+     * @return ossUrl
+     */
+    public static String upload(MultipartFile file) {
+        String fileName = file.getOriginalFilename();
+        if (StrUtil.isBlank(fileName)) {
+            throw new ServerException("文件名不能为空");
+        }
+        String suffix = StringUtils.substring(fileName, fileName.lastIndexOf(StrUtil.DOT), fileName.length());
+        UploadResult uploadResult;
+        try {
+            uploadResult = OssFactory.instance().uploadSuffix(file.getBytes(), suffix);
+        } catch (IOException e) {
+            throw new ServerException(e.getMessage());
+        }
+        return uploadResult.getUrl();
+    }
+
+    /**
+     * 删除文件
+     *
+     * @param path oss路径
+     */
+    public static void deleteOss(String path) {
+        OssFactory.instance().delete(path);
+    }
+
+}

+ 10 - 0
xyzc-start/pom.xml

@@ -19,6 +19,12 @@
 
     <dependencies>
 
+        <!-- XyZc Api Pay -->
+        <dependency>
+            <groupId>com.xyzc</groupId>
+            <artifactId>xyzc-api-resource</artifactId>
+        </dependency>
+
         <!-- sms服务 -->
         <dependency>
             <groupId>com.xyzc</groupId>
@@ -84,6 +90,10 @@
             <artifactId>xyzc-common-doc</artifactId>
         </dependency>
 
+        <dependency>
+            <groupId>com.xyzc</groupId>
+            <artifactId>xyzc-common-dubbo</artifactId>
+        </dependency>
 
 
         <dependency>

+ 2 - 0
xyzc-start/src/main/java/com/xyzc/start/XyZcWebApplication.java

@@ -2,6 +2,7 @@ package com.xyzc.start;
 
 import com.xyzc.common.core.utils.StringUtils;
 import lombok.extern.slf4j.Slf4j;
+import org.apache.dubbo.config.spring.context.annotation.EnableDubbo;
 import org.mybatis.spring.annotation.MapperScan;
 import org.springframework.boot.SpringApplication;
 import org.springframework.boot.autoconfigure.SpringBootApplication;
@@ -14,6 +15,7 @@ import java.net.UnknownHostException;
 
 @Slf4j
 @MapperScan(basePackages = {"com.xyzc.start.mapper"})
+@EnableDubbo
 @SpringBootApplication
 public class XyZcWebApplication {
 

+ 1 - 1
xyzc-start/src/main/java/com/xyzc/start/constant/BaseUserConst.java

@@ -30,7 +30,7 @@ public interface BaseUserConst {
     /**
      * 收藏
      */
-    Integer COLLECT = 1;
+    Integer IS_COLLECT = 1;
 
     /**
      * 取消收藏

+ 0 - 13
xyzc-start/src/main/java/com/xyzc/start/controller/HomePageController.java

@@ -79,17 +79,4 @@ public class HomePageController {
         return Result.ok();
     }
 
-    /**
-     * 绘画
-     */
-
-
-    /**
-     * AI修图
-     */
-
-
-    /**
-     * 文案创作
-     */
 }

+ 4 - 2
xyzc-start/src/main/java/com/xyzc/start/controller/MyCollectController.java

@@ -2,6 +2,7 @@ package com.xyzc.start.controller;
 
 import com.xyzc.common.core.utils.Result;
 import com.xyzc.start.domain.vo.AppVo;
+import com.xyzc.start.domain.vo.CollectAppVo;
 import com.xyzc.start.domain.vo.DrawGraphicsVo;
 import com.xyzc.start.domain.vo.DrawModelCollectVo;
 import com.xyzc.start.service.IMyCollectService;
@@ -48,8 +49,9 @@ public class MyCollectController {
      * 收藏的应用
      */
     @GetMapping("/collectApp")
-    public Result<List<AppVo>> getCollectApp(Long id) {
-        List<AppVo> appList = myCollectService.getCollectApp(id);
+    public Result<List<CollectAppVo>> getCollectApp(Long id) {
+        List<CollectAppVo> appList = myCollectService.getCollectApp(id);
+
         return Result.ok(appList);
     }
 

+ 3 - 1
xyzc-start/src/main/java/com/xyzc/start/controller/UgcController.java

@@ -1,6 +1,8 @@
 package com.xyzc.start.controller;
 
 import com.xyzc.common.core.utils.Result;
+import com.xyzc.start.domain.DrawGraphics;
+import com.xyzc.start.domain.vo.DrawGraphicsVo;
 import com.xyzc.start.domain.vo.UgcUserVo;
 import com.xyzc.start.service.IUgcService;
 import lombok.RequiredArgsConstructor;
@@ -35,7 +37,7 @@ public class UgcController {
      * ugc发布的画作
      */
     @GetMapping("/ugcRelease")
-    public Result<List<Void>> ugcRelease(Long id) {
+    public Result<List<DrawGraphicsVo>> ugcRelease(Long id) {
          ugcService.ugcRelease();
         return Result.ok();
     }

+ 16 - 2
xyzc-start/src/main/java/com/xyzc/start/controller/UserInfoController.java

@@ -1,18 +1,30 @@
 package com.xyzc.start.controller;
 
+import cn.hutool.core.io.FileUtil;
 import com.xyzc.common.core.utils.Result;
+import com.xyzc.common.core.utils.StringUtils;
+import com.xyzc.common.core.utils.file.MimeTypeUtils;
+import com.xyzc.common.web.security.user.SecurityUser;
+import com.xyzc.resource.api.RemoteFileService;
+import com.xyzc.resource.api.domain.RemoteFile;
 import com.xyzc.start.constant.FuelDeduct;
 import com.xyzc.start.domain.bo.AgentCardSecretBo;
 import com.xyzc.start.domain.bo.BaseUserCollectBo;
 import com.xyzc.start.domain.bo.HobbyBo;
+import com.xyzc.start.domain.vo.AvatarVo;
 import com.xyzc.start.domain.vo.BaseUserFuelFlowVo;
 import com.xyzc.start.domain.vo.BaseUserVo;
 import com.xyzc.start.domain.vo.IndustryVo;
 import com.xyzc.start.service.IUserInfoService;
 import jakarta.validation.Valid;
 import lombok.RequiredArgsConstructor;
+import org.apache.dubbo.config.annotation.DubboReference;
+import org.springframework.http.MediaType;
 import org.springframework.web.bind.annotation.*;
+import org.springframework.web.multipart.MultipartFile;
 
+import java.io.IOException;
+import java.util.Arrays;
 import java.util.List;
 
 /**
@@ -28,6 +40,8 @@ public class UserInfoController {
 
     private final IUserInfoService userInfoService;
 
+    @DubboReference
+    private RemoteFileService remoteFileService;
 
     /**
      * 登录用户获取用户信息
@@ -109,10 +123,10 @@ public class UserInfoController {
      * 用户收藏或取消
      */
     @PostMapping("/collectOrCancel")
-    public Result<Void> collectOrCancel(@RequestBody @Valid BaseUserCollectBo collectBo) {
+    public Result<String> collectOrCancel(@RequestBody @Valid BaseUserCollectBo collectBo) {
         userInfoService.collectOrCancel(collectBo);
 
-        return Result.ok();
+        return Result.ok("操作成功");
     }
 
 }

+ 5 - 0
xyzc-start/src/main/java/com/xyzc/start/domain/App.java

@@ -41,6 +41,11 @@ public class App extends BaseEntity {
     private String appName;
 
     /**
+     * 使用次数
+     */
+    private Integer useCount;
+
+    /**
      * 应用英文名称
      */
     private String appNameEn;

+ 4 - 2
xyzc-start/src/main/java/com/xyzc/start/domain/BaseUserFuelFlow.java

@@ -5,6 +5,8 @@ import com.baomidou.mybatisplus.annotation.TableField;
 import com.baomidou.mybatisplus.annotation.TableName;
 import lombok.Data;
 
+import java.util.Date;
+
 @Data
 @TableName("xy_base_user_fuel_flow")
 public class BaseUserFuelFlow {
@@ -25,6 +27,6 @@ public class BaseUserFuelFlow {
 
     private Long fuelCount;
 
-    @TableField(fill = FieldFill.INSERT)
-    private Long createTime;
+    private Date createTime;
+
 }

+ 3 - 1
xyzc-start/src/main/java/com/xyzc/start/domain/DrawGraphics.java

@@ -1,12 +1,14 @@
 package com.xyzc.start.domain;
 
 import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
 
 /**
  * DrawGraphics
  *
  * @author 杨兴卓
  */
+@Data
 @TableName("xy_draw_graphics")
 public class DrawGraphics {
 
@@ -30,6 +32,6 @@ public class DrawGraphics {
 
     private String promptWord;
 
-    private String reservePromptWord;
+    private String reversePromptWord;
 
 }

+ 12 - 3
xyzc-start/src/main/java/com/xyzc/start/domain/DrawModel.java

@@ -26,14 +26,23 @@ public class DrawModel extends BaseEntity {
     private Long id;
 
     /**
+     * 模型名称
+     */
+    private String modelName;
+
+    /**
      * ugc用户Id
      */
-    private Long ugcId;
+    private Long ugcUserId;
+
+
+    private Integer useCount;
+
+    private Integer use_false_count;
 
 
-    private Integer collectFalse;
-    private Integer collectTrue;
     private String description;
+
     private Long ossId;
 
     @TableLogic

+ 3 - 3
xyzc-start/src/main/java/com/xyzc/start/domain/WxConfig.java

@@ -50,7 +50,7 @@ public class WxConfig extends BaseEntity {
     /**
      * 微信txt文件url
      */
-    private String txtFileOssUrl;
+    private String txtFileUrl;
 
     /**
      * 微信商户id
@@ -70,12 +70,12 @@ public class WxConfig extends BaseEntity {
     /**
      * 证书文件ossUrl
      */
-    private String certFileOssUrl;
+    private String certFileUrl;
 
     /**
      * 证书key文件ossUrl
      */
-    private String keyFileOssUrl;
+    private String keyFileUrl;
 
 
 }

+ 4 - 4
xyzc-start/src/main/java/com/xyzc/start/domain/bo/BaseUserCollectBo.java

@@ -20,15 +20,15 @@ public class BaseUserCollectBo {
     /**
      * 是否收藏,0-最近使用,1-收藏
      */
-    @NotNull(message = "收藏状态不能为空")
-    @Range(min = 0, max = 1, message = "收藏类型错误,在{min}和{max}之间")
+    @NotNull(message = "isCollect不能为空")
+    @Range(min = 0, max = 1, message = "isCollect错误,在{min}和{max}之间")
     private Integer isCollect;
 
     /**
      * 收藏类型,1-绘画模型, 2-图像, 3-应用
      */
-    @NotNull(message = "收藏类型不能为空")
-    @Range(min = 1, max = 3, message = "收藏类型错误,在{min}和{max}之间")
+    @NotNull(message = "type不能为空")
+    @Range(min = 1, max = 3, message = "type错误,在{min}和{max}之间")
     private Integer type;
 
 

+ 24 - 0
xyzc-start/src/main/java/com/xyzc/start/domain/vo/AvatarVo.java

@@ -0,0 +1,24 @@
+package com.xyzc.start.domain.vo;
+
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+
+/**
+ * 用户头像信息
+ *
+ * @author Michelle.Chung
+ */
+@Data
+public class AvatarVo implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 头像地址
+     */
+    private String imgUrl;
+
+}

+ 5 - 0
xyzc-start/src/main/java/com/xyzc/start/domain/vo/BaseUserCollectAppVo.java

@@ -25,4 +25,9 @@ public class BaseUserCollectAppVo {
      * 应用图标
      */
     private String appIcon;
+
+    /**
+     * 收藏类型,1-绘画模型, 2-图像, 3-应用
+     */
+    private Integer type;
 }

+ 5 - 0
xyzc-start/src/main/java/com/xyzc/start/domain/vo/BaseUserCollectVo.java

@@ -15,6 +15,11 @@ public class BaseUserCollectVo {
     private Long id;
 
     /**
+     * 收藏类型,1-绘画模型, 2-图像, 3-应用
+     */
+    private Integer type;
+
+    /**
      * 应用的id
      */
     private Long abilityId;

+ 3 - 1
xyzc-start/src/main/java/com/xyzc/start/domain/vo/BaseUserFuelFlowVo.java

@@ -4,6 +4,8 @@ import com.xyzc.start.domain.BaseUserFuelFlow;
 import io.github.linpeilie.annotations.AutoMapper;
 import lombok.Data;
 
+import java.util.Date;
+
 
 @Data
 @AutoMapper(target = BaseUserFuelFlow.class)
@@ -37,6 +39,6 @@ public class BaseUserFuelFlowVo {
     /**
      * 燃料变动时间
      */
-    private Long createTime;
+    private Date createTime;
 
 }

+ 1 - 0
xyzc-start/src/main/java/com/xyzc/start/domain/vo/BaseUserSelectAppVo.java

@@ -47,4 +47,5 @@ public class BaseUserSelectAppVo {
      * 该用户是否使收藏
      */
     private Integer isCollect;
+
 }

+ 58 - 0
xyzc-start/src/main/java/com/xyzc/start/domain/vo/CollectAppVo.java

@@ -0,0 +1,58 @@
+package com.xyzc.start.domain.vo;
+
+import lombok.Data;
+
+@Data
+public class CollectAppVo {
+
+    /**
+     * 应用收藏id
+     */
+    private Long id;
+
+    /**
+     * 应用logo地址
+     */
+    private String appLogo;
+
+    /**
+     * 应用名称
+     */
+    private String appName;
+
+    /**
+     * 应用描述
+     */
+    private String appDesc;
+
+    /**
+     * 应用精选,0-未精选,1-已精选
+     */
+    private Integer isChoiceness;
+
+    /**
+     * ai模型logo
+     */
+    private String aiModelLogo;
+
+    /**
+     * ai模型名称
+     */
+    private String aiModelName;
+
+    /**
+     * 共有多少人使用
+     */
+    private Integer useCount;
+
+    /**
+     * 应用类型
+     */
+    private String type;
+
+    /**
+     * 收藏状态,0-未收藏,1-已收藏
+     */
+    private Integer isCollect;
+
+}

+ 1 - 1
xyzc-start/src/main/java/com/xyzc/start/domain/vo/DrawGraphicsVo.java

@@ -66,7 +66,7 @@ public class DrawGraphicsVo {
     /**
      * 反向提示词
      */
-    private String reservePromptWord;
+    private String reversePromptWord;
 
     /**
      * ugc用户的昵称

+ 3 - 0
xyzc-start/src/main/java/com/xyzc/start/domain/vo/DrawModelVo.java

@@ -1,9 +1,12 @@
 package com.xyzc.start.domain.vo;
 
+import com.xyzc.start.domain.DrawModel;
+import io.github.linpeilie.annotations.AutoMapper;
 import lombok.Data;
 
 
 @Data
+@AutoMapper(target = DrawModel.class)
 public class DrawModelVo {
 
     /**

+ 3 - 3
xyzc-start/src/main/java/com/xyzc/start/domain/vo/WxConfigVo.java

@@ -49,7 +49,7 @@ public class WxConfigVo implements Serializable {
     /**
      * 微信txt文件url
      */
-    private String txtFileOssUrl;
+    private String txtFileUrl;
 
     /**
      * 微信商户id
@@ -69,11 +69,11 @@ public class WxConfigVo implements Serializable {
     /**
      * 证书文件ossUrl
      */
-    private String certFileOssUrl;
+    private String certFileUrl;
 
     /**
      * 证书key文件ossUrl
      */
-    private String keyFileOssUrl;
+    private String keyFileUrl;
 
 }

+ 1 - 1
xyzc-start/src/main/java/com/xyzc/start/mapper/BaseUserCollectMapper.java

@@ -70,7 +70,7 @@ public interface BaseUserCollectMapper extends BaseMapperPlus<BaseUserCollect, B
      * @param id
      * @return
      */
-    List<AppVo> queryAppById(Long userId, Long id);
+    List<CollectAppVo> queryAppById(Long userId, Long id);
 
     /**
      * 根据用户id查询收藏的应用

+ 21 - 0
xyzc-start/src/main/java/com/xyzc/start/mapper/DrawGraphicsMapper.java

@@ -0,0 +1,21 @@
+package com.xyzc.start.mapper;
+
+
+import com.xyzc.common.mybatis.core.mapper.BaseMapperPlus;
+import com.xyzc.start.domain.DrawGraphics;
+import com.xyzc.start.domain.vo.DrawGraphicsVo;
+
+import java.util.List;
+
+public interface DrawGraphicsMapper extends BaseMapperPlus<DrawGraphics, DrawGraphicsVo> {
+
+    /**
+     * 根据图像id分页查询
+     *
+     * @param graphicsId
+     * @return
+     */
+    List<DrawGraphicsVo> queryGraphics(Long graphicsId);
+
+
+}

+ 13 - 8
xyzc-start/src/main/java/com/xyzc/start/mapper/DrawModelMapper.java

@@ -10,20 +10,25 @@ import org.apache.ibatis.annotations.Mapper;
 import java.util.List;
 
 /**
- * @description: TODO
- * @author: 墨凡
- * @date: 2024/7/21 17:28
- * @version: 1.0
+ * 绘画模型
+ *
+ * @author 墨凡
+ * date: 2024/7/21 17:28
  */
 @Mapper
 public interface DrawModelMapper extends BaseMapperPlus<DrawModel, DrawModelVo> {
 
     /**
-     * 根据图像id分页查询
-     *
-     * @param graphicsId
+     * 查询绘画模型列表
+     * @param drawModelId
      * @return
      */
-    List<DrawGraphicsVo> queryGraphics(Long graphicsId);
+    List<DrawModelVo> queryDrawModelList(Long drawModelId);
+
+
+    /**
+     *
+     */
+
 
 }

+ 2 - 1
xyzc-start/src/main/java/com/xyzc/start/service/IMyCollectService.java

@@ -1,6 +1,7 @@
 package com.xyzc.start.service;
 
 import com.xyzc.start.domain.vo.AppVo;
+import com.xyzc.start.domain.vo.CollectAppVo;
 import com.xyzc.start.domain.vo.DrawGraphicsVo;
 import com.xyzc.start.domain.vo.DrawModelCollectVo;
 
@@ -23,7 +24,7 @@ public interface IMyCollectService {
      * @param id
      * @return
      */
-    List<AppVo> getCollectApp(Long id);
+    List<CollectAppVo> getCollectApp(Long id);
 
     /**
      * 根据上一个id 获取收藏的图片列表

+ 3 - 0
xyzc-start/src/main/java/com/xyzc/start/service/IUserInfoService.java

@@ -5,6 +5,7 @@ import com.xyzc.start.domain.bo.AgentCardSecretBo;
 import com.xyzc.start.domain.bo.BaseUserCollectBo;
 import com.xyzc.start.domain.bo.HobbyBo;
 import com.xyzc.start.domain.vo.*;
+import org.springframework.web.multipart.MultipartFile;
 
 import java.util.List;
 
@@ -95,4 +96,6 @@ public interface IUserInfoService {
      * @param collectBo
      */
     void collectOrCancel(BaseUserCollectBo collectBo);
+
+
 }

+ 5 - 3
xyzc-start/src/main/java/com/xyzc/start/service/impl/DiscoverServiceImpl.java

@@ -2,6 +2,7 @@ package com.xyzc.start.service.impl;
 
 import com.xyzc.start.domain.vo.DrawGraphicsVo;
 import com.xyzc.start.domain.vo.DrawModelVo;
+import com.xyzc.start.mapper.DrawGraphicsMapper;
 import com.xyzc.start.mapper.DrawModelMapper;
 import com.xyzc.start.service.IDiscoverService;
 import lombok.RequiredArgsConstructor;
@@ -20,6 +21,8 @@ public class DiscoverServiceImpl implements IDiscoverService {
 
     private final DrawModelMapper drawModelMapper;
 
+    private final DrawGraphicsMapper drawGraphicsMapper;
+
     /**
      * 根据id返回分页绘画
      *
@@ -29,7 +32,7 @@ public class DiscoverServiceImpl implements IDiscoverService {
     @Override
     public List<DrawGraphicsVo> queryGraphics(Long graphicsId) {
 
-        return drawModelMapper.queryGraphics(graphicsId);
+        return drawGraphicsMapper.queryGraphics(graphicsId);
     }
 
     /**
@@ -40,8 +43,7 @@ public class DiscoverServiceImpl implements IDiscoverService {
      */
     @Override
     public List<DrawModelVo> queryDrawModelList(Long drawModelId) {
-
-        return null;
+        return drawModelMapper.queryDrawModelList(drawModelId);
     }
 
 

+ 1 - 1
xyzc-start/src/main/java/com/xyzc/start/service/impl/HomePageServiceImpl.java

@@ -42,7 +42,7 @@ public class HomePageServiceImpl implements IHomePageService {
 
         // 获取精选应用 前4个
         List<BaseUserSelectAppVo> carefulSelectAppList = baseUserCollectMapper.queryCarefulSelectAppBy(null, 4);
-        // 以精选应用idkey,精选应用为value,生成一个map,并且去重
+        // 以精选应用idkey,精选应用为value,生成一个map,并且去重
         Map<Long, BaseUserSelectAppVo> collect = carefulSelectAppList
                 .stream()
                 .collect(Collectors.toMap(BaseUserSelectAppVo::getAppId, Function.identity(), (k1, k2) -> k1));

+ 2 - 1
xyzc-start/src/main/java/com/xyzc/start/service/impl/MyCollectServiceImpl.java

@@ -3,6 +3,7 @@ package com.xyzc.start.service.impl;
 
 import com.xyzc.common.web.security.user.SecurityUser;
 import com.xyzc.start.domain.vo.AppVo;
+import com.xyzc.start.domain.vo.CollectAppVo;
 import com.xyzc.start.domain.vo.DrawGraphicsVo;
 import com.xyzc.start.domain.vo.DrawModelCollectVo;
 import com.xyzc.start.mapper.BaseUserCollectMapper;
@@ -34,7 +35,7 @@ public class MyCollectServiceImpl implements IMyCollectService {
     }
 
     @Override
-    public List<AppVo> getCollectApp(Long id) {
+    public List<CollectAppVo> getCollectApp(Long id) {
         return baseUserCollectMapper.queryAppById(SecurityUser.getUserId(), id);
     }
 

+ 36 - 9
xyzc-start/src/main/java/com/xyzc/start/service/impl/UserInfoServiceImpl.java

@@ -6,9 +6,7 @@ import com.xyzc.common.core.utils.AssertUtils;
 import com.xyzc.common.core.utils.MapstructUtils;
 import com.xyzc.common.web.security.user.SecurityUser;
 import com.xyzc.start.constant.BaseUserConst;
-import com.xyzc.start.domain.App;
-import com.xyzc.start.domain.BaseUser;
-import com.xyzc.start.domain.BaseUserCollect;
+import com.xyzc.start.domain.*;
 import com.xyzc.start.domain.bo.AgentCardSecretBo;
 import com.xyzc.start.domain.bo.BaseUserCollectBo;
 import com.xyzc.start.domain.bo.HobbyBo;
@@ -18,6 +16,7 @@ import com.xyzc.start.service.IUserInfoService;
 import lombok.RequiredArgsConstructor;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
+import org.springframework.web.multipart.MultipartFile;
 
 import java.util.List;
 
@@ -35,6 +34,9 @@ public class UserInfoServiceImpl implements IUserInfoService {
     private final AppMapper appMapper;
     private final BaseUserCollectMapper baseUserCollectMapper;
 
+    private final DrawGraphicsMapper drawGraphicsMapper;
+    private final DrawModelMapper drawModelMapper;
+
     /**
      * 获取当前用户信息
      *
@@ -44,6 +46,7 @@ public class UserInfoServiceImpl implements IUserInfoService {
     public BaseUserVo queryCurrent() {
         BaseUserVo baseUser = baseUserMapper.queryCurrentInfo(SecurityUser.getUserId());
         // todo 后期加入次数
+
         return baseUser;
     }
 
@@ -101,6 +104,7 @@ public class UserInfoServiceImpl implements IUserInfoService {
      */
     @Override
     public BaseUserPhotoFolderVo queryPhotoById(Long id) {
+
         return photoFolderMapper.queryPhotoFolderById(SecurityUser.getUserId(), id);
     }
 
@@ -112,6 +116,7 @@ public class UserInfoServiceImpl implements IUserInfoService {
      */
     @Override
     public List<BaseUserPhotoFolderVo> photoFolder(Long id) {
+
         return photoFolderMapper.queryPhotoFolder(SecurityUser.getUserId(), id);
     }
 
@@ -172,32 +177,54 @@ public class UserInfoServiceImpl implements IUserInfoService {
      * @param collectBo 收藏对象
      */
     @Override
+    @Transactional(rollbackFor = Exception.class)
     public void collectOrCancel(BaseUserCollectBo collectBo) {
         BaseUserCollect baseUserCollect = baseUserCollectMapper.queryCollectByIdAndCollectId(SecurityUser.getUserId(), collectBo.getCollectId(), collectBo.getType());
         if (baseUserCollect == null) {
+            Object obj = null;
             // 查询收藏对象是否存在,不存在抛出异常
-            App app = null;
             if (BaseUserConst.COLLECT_DRAW_MODEL.equals(collectBo.getType())) {
-
-            } else if (collectBo.getType() == 2) {
-
+                obj = (Object) drawModelMapper.selectById(collectBo.getCollectId());
+            } else if (BaseUserConst.COLLECT_IMAGE.equals(collectBo.getType())) {
+                obj = (Object) drawGraphicsMapper.selectById(collectBo.getCollectId());
             } else if (BaseUserConst.COLLECT_APP.equals(collectBo.getType())) {
-                app = appMapper.selectById(collectBo.getCollectId());
+                obj = (Object) appMapper.selectById(collectBo.getCollectId());
             }
-            AssertUtils.notNull(app, ErrorCode.COLLECT_NOT_EXIST);
+            AssertUtils.notNull(obj, ErrorCode.COLLECT_NOT_EXIST);
 
             // 转换用户收藏
             BaseUserCollect convert = MapstructUtils.convert(collectBo, BaseUserCollect.class);
             convert.setBaseUserId(SecurityUser.getUserId());
 
             baseUserCollectMapper.insert(convert);
+            // 更新对应的收藏量
+            if (BaseUserConst.COLLECT_DRAW_MODEL.equals(collectBo.getType()) &&
+                    BaseUserConst.IS_COLLECT.equals(collectBo.getType())) {
+                DrawModel drawModel = (DrawModel) obj;
+                drawModel.setUseCount(drawModel.getUseCount() + 1);
+                drawModelMapper.updateById(drawModel);
+            } else if (BaseUserConst.COLLECT_IMAGE.equals(collectBo.getType()) &&
+                    BaseUserConst.IS_COLLECT.equals(collectBo.getType())) {
+                DrawGraphics graphics = (DrawGraphics) obj;
+                graphics.setUseCount(graphics.getUseCount() + 1);
+                drawGraphicsMapper.updateById(graphics);
+            } else if (BaseUserConst.COLLECT_APP.equals(collectBo.getType())) {
+                App app = (App) obj;
+                app.setUseCount(app.getUseCount() + 1);
+                appMapper.updateById(app);
+            }
+
             return;
         }
 
         // 删除用户收藏
         baseUserCollectMapper.deleteById(baseUserCollect.getId());
+
         // 插入用户收藏
         baseUserCollect.setId(null);
+        baseUserCollect.setIsCollect(collectBo.getIsCollect());
+
+
         baseUserCollectMapper.insert(baseUserCollect);
     }
 

+ 0 - 5
xyzc-start/src/main/resources/application.yml

@@ -33,8 +33,3 @@ spring:
       - optional:nacos:datasource.yml
       - optional:nacos:${spring.application.name}.yml
 
-logging:
-  level:
-    org:
-      springframework:
-        security: DEBUG

+ 76 - 70
xyzc-start/src/main/resources/mapper/WebStart/BaseUserCollectMapper.xml

@@ -6,14 +6,14 @@
 
     <select id="queryDrawModelByBo" resultType="com.xyzc.start.domain.vo.BaseUserCollectVo">
         SELECT
-            xdm.oss_id AS pic_url,
-            xdm.model_name AS ai_model_name,
-            xdm.description AS description
+        xdm.oss_id AS pic_url,
+        xdm.model_name AS ai_model_name,
+        xdm.description AS description
         FROM
-            xy_base_user_collect xbuc
+        xy_base_user_collect xbuc
         LEFT JOIN xy_draw_model xdm ON xbuc.collect_id = xdm.id
         WHERE
-            xbuc.base_user_id = #{baseUserId}
+        xbuc.base_user_id = #{baseUserId}
         AND xbuc.id > #{bo.id}
         AND xbuc.is_collect = #{bo.isCollect}
         <if test="bo.likeName != null">
@@ -34,89 +34,92 @@
     </select>
 
     <select id="queryDrawModelById" resultType="com.xyzc.start.domain.vo.DrawModelCollectVo">
-        SELECT
-            xdm.id          AS id,
-            xbuc.id         AS collect_id,
-            xdm.oss_id,
-            xdm.model_name,
-            xdm.description,
-            xuu.ugc_nick    AS nick_name
-        FROM
-            xy_draw_model xdm
-        LEFT JOIN xy_base_user_collect xbuc ON xdm.id = xbuc.collect_id
-                AND `type` = 1
-        LEFT JOIN xy_ugc_user xuu ON xbuc.base_user_id = xuu.id
-        WHERE
-            xdm.id = xbuc.collect_id
-            AND xbuc.base_user_id = #{userId}
+        SELECT xdm.id       AS id,
+               xbuc.id      AS collect_id,
+               xdm.oss_id,
+               xdm.model_name,
+               xdm.description,
+               xuu.ugc_nick AS nick_name
+        FROM xy_draw_model xdm
+                 LEFT JOIN xy_base_user_collect xbuc ON xdm.id = xbuc.collect_id
+            AND `type` = 1
+                 LEFT JOIN xy_ugc_user xuu ON xbuc.base_user_id = xuu.id
+        WHERE xdm.id = xbuc.collect_id
+          AND xbuc.base_user_id = #{userId}
         ORDER BY xbuc.create_time DESC
         LIMIT 10
     </select>
 
-    <select id="queryAppById" resultType="com.xyzc.start.domain.vo.AppVo">
-        SELECT xa.id,
-               xa.app_name,
-               xa.app_type,
-               xa.description,
-               xa.oss_id AS app_oss_id,
-               xa.is_choiceness,
-               xa.use_false_count,
-               xa.use_count
-            FROM xy_base_user bu
-            LEFT JOIN xy_base_user_collect xbuc ON bu.id = xbuc.base_user_id
-                AND xbuc.is_collect = 1 AND type = 3
-            LEFT JOIN xy_app xa ON xa.id = xbuc.collect_id
-            WHERE bu.id = #{userId}
+    <select id="queryAppById" resultType="com.xyzc.start.domain.vo.CollectAppVo">
+        SELECT xbuc.id,
+               app.app_name,
+               app.description         AS app_desc,
+               oss.url                 AS app_logo,
+               app.is_choiceness,
+               app.use_count,
+               xbuc.type               AS type,
+               am.ai_model_logo_oss_id AS ai_model_logo,
+               am.ai_model_name        AS ai_model_name
+        FROM xy_base_user bu
+                 LEFT JOIN xy_base_user_collect xbuc ON bu.id = xbuc.base_user_id
+            AND xbuc.is_collect = 1 AND xbuc.type = 3
+                 LEFT JOIN xy_app app ON app.id = xbuc.collect_id
+                 LEFT JOIN sys_oss oss ON oss.oss_id = app.oss_id
+                 LEFT JOIN xy_ai_model_app xama ON app.id = xama.app_id
+                 LEFT JOIN xy_ai_model am ON xama.ai_model_id = am.id
+                 LEFT JOIN sys_oss oss2 ON oss2.oss_id = am.ai_model_logo_oss_id
+        WHERE bu.id = #{userId}
     </select>
 
     <select id="queryDrawGraphicsById" resultType="com.xyzc.start.domain.vo.DrawGraphicsVo">
-        SELECT
-            xbuc.id             AS collect_id,
-            xbuc.base_user_id,
-            xbuc.collect_id     AS graphics_id,
-            xbuc.is_collect,
-            xbuc.create_time,
-            oss.url             AS oss_id,
-            xg.title
-        FROM
-            xy_base_user_collect xbuc
-        LEFT JOIN xy_draw_graphics xg ON xbuc.collect_id = xg.id
-        LEFT JOIN sys_oss oss ON oss.oss_id = xg.oss_id
-        WHERE
-            xbuc.base_user_id = #{userId} and xbuc.type = 2
+        SELECT xbuc.id         AS collect_id,
+               xbuc.base_user_id,
+               xbuc.collect_id AS graphics_id,
+               xbuc.is_collect,
+               xbuc.create_time,
+               oss.url         AS oss_id,
+               xg.title
+        FROM xy_base_user_collect xbuc
+                 LEFT JOIN xy_draw_graphics xg ON xbuc.collect_id = xg.id
+                 LEFT JOIN sys_oss oss ON oss.oss_id = xg.oss_id
+        WHERE xbuc.base_user_id = #{userId}
+          and xbuc.type = 2
         ORDER BY xbuc.create_time DESC
         LIMIT 10
     </select>
 
     <select id="queryCarefulSelectAppBy" resultType="com.xyzc.start.domain.vo.BaseUserSelectAppVo">
-        SELECT app.id               as app_id,
-               app.app_name         as app_name,
-               app.description      as app_desc,
-               oss.url              as app_icon,
-               oss2.url             as ai_model_icon,
-               am.ai_model_name     as ai_model_name,
-               app.use_count        as use_count
+        SELECT app.id as app_id,
+        app.app_name as app_name,
+        app.description as app_desc,
+        oss.url as app_icon,
+        oss2.url as ai_model_icon,
+        am.ai_model_name as ai_model_name,
+        app.use_count as use_count
         from xy_app app
-            left join sys_oss oss on app.oss_id = oss.oss_id
-            left join xy_ai_model_app ama on ama.app_id = app.id
-            left join xy_ai_model am on ama.ai_model_id = am.id
-            left join sys_oss oss2 on am.ai_model_logo_oss_id = oss2.oss_id
+        left join sys_oss oss on app.oss_id = oss.oss_id
+        left join xy_ai_model_app ama on ama.app_id = app.id
+        left join xy_ai_model am on ama.ai_model_id = am.id
+        left join sys_oss oss2 on am.ai_model_logo_oss_id = oss2.oss_id
         where app.is_choiceness = 1
-          <if test="appId != null">
-              and app.id > #{appId}
-          </if>
+            and app.del_flag = '0'
+        <if test="appId != null">
+            and app.id > #{appId}
+        </if>
         limit #{length}
     </select>
 
     <select id="queryHistoryAppBy" resultType="com.xyzc.start.domain.vo.BaseUserCollectAppVo">
         SELECT app.id       as app_id,
                app.app_name as app_name,
-               oss.url      as app_icon
+               oss.url      as app_icon,
+               buc.type     AS type
         from xy_base_user_collect buc
                  left join xy_app app on buc.collect_id = app.id
                  left join sys_oss oss on app.oss_id = oss.oss_id
         where buc.base_user_id = #{userId}
           and buc.type = 3
+          and app.del_flag = '0'
         order by buc.create_time desc
         limit 8
 
@@ -126,13 +129,15 @@
     <select id="queryCollectAppBy" resultType="com.xyzc.start.domain.vo.BaseUserCollectAppVo">
         SELECT app.id       as app_id,
                app.app_name as app_name,
-               oss.url      as app_icon
+               oss.url      as app_icon,
+               buc.type     AS type
         from xy_base_user_collect buc
                  left join xy_app app on buc.collect_id = app.id
                  left join sys_oss oss on app.oss_id = oss.oss_id
         where buc.base_user_id = #{userId}
           and buc.type = 3
           and buc.is_collect = 1
+          and app.del_flag = '0'
         order by buc.create_time desc
         limit 8
     </select>
@@ -142,15 +147,16 @@
         SELECT
             id,
             collect_id,
-            is_collect
+            is_collect,
+            type
         FROM
             xy_base_user_collect
         WHERE base_user_id = #{userId}
-          AND type = #{type}
-          AND collect_id in
-            <foreach collection="appIdList" item="appId" open="(" separator="," close=")">
-                #{appId}
-            </foreach>
+            AND type = #{type}
+            AND collect_id in
+        <foreach collection="appIdList" item="appId" open="(" separator="," close=")">
+            #{appId}
+        </foreach>
 
     </select>
 

+ 14 - 0
xyzc-start/src/main/resources/mapper/WebStart/DrawGraphicsMapper.xml

@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper
+        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
+        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.xyzc.start.mapper.DrawGraphicsMapper">
+
+
+    <select id="queryGraphics" resultType="com.xyzc.start.domain.vo.DrawGraphicsVo">
+        SELECT * FROM xy_draw_graphics
+    </select>
+
+
+
+</mapper>

+ 3 - 2
xyzc-start/src/main/resources/mapper/WebStart/DrawModelMapper.xml

@@ -5,8 +5,9 @@
 <mapper namespace="com.xyzc.start.mapper.DrawModelMapper">
 
 
-    <select id="queryGraphics" resultType="com.xyzc.start.domain.vo.DrawGraphicsVo">
-
+    <select id="queryDrawModelList" resultType="com.xyzc.start.domain.vo.DrawModelVo">
 
     </select>
+
+
 </mapper>