Переглянути джерело

1.将 mall-service 迁移到 content-service 的 mall 模块
2.迁移 mall 也给 content-service 引入了 sharding-jdbc 实现读写分离
3.引入 sharding-jdbc 使得 actuator 无法获取 jdbc 连接信息, 后面来解决
4.统一将 tnb 中各服务的 mybatis 版本升级到 2.1.1

reghao 1 рік тому
батько
коміт
f8ceec61c3
58 змінених файлів з 2153 додано та 20 видалено
  1. 1 1
      account/account-service/pom.xml
  2. 15 5
      content/content-service/pom.xml
  3. 37 0
      content/content-service/src/main/java/cn/reghao/tnb/content/app/aop/DataSourceAspect.java
  4. 23 0
      content/content-service/src/main/java/cn/reghao/tnb/content/app/config/DataSourceType.java
  5. 351 0
      content/content-service/src/main/java/cn/reghao/tnb/content/app/config/mybatis/CustomSqlSessionTemplate.java
  6. 3 3
      content/content-service/src/main/java/cn/reghao/tnb/content/app/config/mybatis/DataSourceConfig.java
  7. 34 0
      content/content-service/src/main/java/cn/reghao/tnb/content/app/config/mybatis/DynamicDataSource.java
  8. 37 0
      content/content-service/src/main/java/cn/reghao/tnb/content/app/config/mybatis/DynamicDataSourceConfig.java
  9. 5 3
      content/content-service/src/main/java/cn/reghao/tnb/content/app/config/mybatis/PageListInterceptor.java
  10. 90 0
      content/content-service/src/main/java/cn/reghao/tnb/content/app/config/mybatis/SqlSessionConfig.java
  11. 37 0
      content/content-service/src/main/java/cn/reghao/tnb/content/app/config/shard/DataSourceUtil.java
  12. 17 0
      content/content-service/src/main/java/cn/reghao/tnb/content/app/config/shard/ModuloShardingTableAlgorithm.java
  13. 43 0
      content/content-service/src/main/java/cn/reghao/tnb/content/app/config/shard/ShardingDataSourceConfig.java
  14. 59 0
      content/content-service/src/main/java/cn/reghao/tnb/content/app/mall/controller/CartController.java
  15. 51 0
      content/content-service/src/main/java/cn/reghao/tnb/content/app/mall/controller/DeliveryController.java
  16. 55 0
      content/content-service/src/main/java/cn/reghao/tnb/content/app/mall/controller/LogisticsController.java
  17. 79 0
      content/content-service/src/main/java/cn/reghao/tnb/content/app/mall/controller/OrderController.java
  18. 57 0
      content/content-service/src/main/java/cn/reghao/tnb/content/app/mall/controller/ProductController.java
  19. 16 0
      content/content-service/src/main/java/cn/reghao/tnb/content/app/mall/db/mapper/DeliveryMapper.java
  20. 15 0
      content/content-service/src/main/java/cn/reghao/tnb/content/app/mall/db/mapper/LogisticsMapper.java
  21. 16 0
      content/content-service/src/main/java/cn/reghao/tnb/content/app/mall/db/mapper/LogisticsProgressMapper.java
  22. 20 0
      content/content-service/src/main/java/cn/reghao/tnb/content/app/mall/db/mapper/OrderMapper.java
  23. 16 0
      content/content-service/src/main/java/cn/reghao/tnb/content/app/mall/db/mapper/ProductMapper.java
  24. 14 0
      content/content-service/src/main/java/cn/reghao/tnb/content/app/mall/db/mapper/ProductSnapshotMapper.java
  25. 27 0
      content/content-service/src/main/java/cn/reghao/tnb/content/app/mall/db/repository/MallRepository.java
  26. 33 0
      content/content-service/src/main/java/cn/reghao/tnb/content/app/mall/job/JobService.java
  27. 32 0
      content/content-service/src/main/java/cn/reghao/tnb/content/app/mall/job/task/OrderTask.java
  28. 43 0
      content/content-service/src/main/java/cn/reghao/tnb/content/app/mall/model/constant/LogisticsStatus.java
  29. 42 0
      content/content-service/src/main/java/cn/reghao/tnb/content/app/mall/model/constant/OrderStatus.java
  30. 17 0
      content/content-service/src/main/java/cn/reghao/tnb/content/app/mall/model/dto/CartDto.java
  31. 19 0
      content/content-service/src/main/java/cn/reghao/tnb/content/app/mall/model/dto/OrderDto.java
  32. 23 0
      content/content-service/src/main/java/cn/reghao/tnb/content/app/mall/model/dto/OrderItem.java
  33. 18 0
      content/content-service/src/main/java/cn/reghao/tnb/content/app/mall/model/dto/ProductAddDto.java
  34. 21 0
      content/content-service/src/main/java/cn/reghao/tnb/content/app/mall/model/po/Delivery.java
  35. 19 0
      content/content-service/src/main/java/cn/reghao/tnb/content/app/mall/model/po/Logistics.java
  36. 31 0
      content/content-service/src/main/java/cn/reghao/tnb/content/app/mall/model/po/LogisticsProgress.java
  37. 38 0
      content/content-service/src/main/java/cn/reghao/tnb/content/app/mall/model/po/Order.java
  38. 11 0
      content/content-service/src/main/java/cn/reghao/tnb/content/app/mall/model/po/OrderProduct.java
  39. 49 0
      content/content-service/src/main/java/cn/reghao/tnb/content/app/mall/model/po/Product.java
  40. 29 0
      content/content-service/src/main/java/cn/reghao/tnb/content/app/mall/model/po/ProductSnapshot.java
  41. 27 0
      content/content-service/src/main/java/cn/reghao/tnb/content/app/mall/model/vo/CartCard.java
  42. 34 0
      content/content-service/src/main/java/cn/reghao/tnb/content/app/mall/model/vo/OrderDetail.java
  43. 55 0
      content/content-service/src/main/java/cn/reghao/tnb/content/app/mall/service/CartService.java
  44. 24 0
      content/content-service/src/main/java/cn/reghao/tnb/content/app/mall/service/DeliveryService.java
  45. 59 0
      content/content-service/src/main/java/cn/reghao/tnb/content/app/mall/service/LogisticsService.java
  46. 125 0
      content/content-service/src/main/java/cn/reghao/tnb/content/app/mall/service/OrderService.java
  47. 54 0
      content/content-service/src/main/java/cn/reghao/tnb/content/app/mall/service/PayService.java
  48. 68 0
      content/content-service/src/main/java/cn/reghao/tnb/content/app/mall/service/ProductService.java
  49. 20 5
      content/content-service/src/main/resources/application-dev.yml
  50. 17 0
      content/content-service/src/main/resources/mapper/mall/DeliveryMapper.xml
  51. 22 0
      content/content-service/src/main/resources/mapper/mall/LogisticsMapper.xml
  52. 18 0
      content/content-service/src/main/resources/mapper/mall/LogisticsProgressMapper.xml
  53. 38 0
      content/content-service/src/main/resources/mapper/mall/OrderMapper.xml
  54. 29 0
      content/content-service/src/main/resources/mapper/mall/ProductMapper.xml
  55. 17 0
      content/content-service/src/main/resources/mapper/mall/ProductSnapshotMapper.xml
  56. 1 1
      file/file-service/pom.xml
  57. 1 1
      message/message-service/pom.xml
  58. 1 1
      user/user-service/pom.xml

+ 1 - 1
account/account-service/pom.xml

@@ -105,7 +105,7 @@
         <dependency>
             <groupId>org.mybatis.spring.boot</groupId>
             <artifactId>mybatis-spring-boot-starter</artifactId>
-            <version>1.3.2</version>
+            <version>2.1.1</version>
         </dependency>
 
         <dependency>

+ 15 - 5
content/content-service/pom.xml

@@ -72,11 +72,6 @@
             <version>2.1.1</version>
         </dependency>
 
-        <dependency>
-            <groupId>org.mybatis.spring.boot</groupId>
-            <artifactId>mybatis-spring-boot-starter</artifactId>
-            <version>1.3.2</version>
-        </dependency>
         <dependency>
             <groupId>mysql</groupId>
             <artifactId>mysql-connector-java</artifactId>
@@ -87,6 +82,21 @@
             <artifactId>HikariCP</artifactId>
             <version>3.3.1</version>
         </dependency>
+        <dependency>
+            <groupId>org.mybatis.spring.boot</groupId>
+            <artifactId>mybatis-spring-boot-starter</artifactId>
+            <version>2.1.1</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.shardingsphere</groupId>
+            <artifactId>sharding-jdbc-spring-boot-starter</artifactId>
+            <version>4.1.1</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.shardingsphere</groupId>
+            <artifactId>sharding-core-common</artifactId>
+            <version>4.1.1</version>
+        </dependency>
 
         <dependency>
             <groupId>org.springframework.boot</groupId>

+ 37 - 0
content/content-service/src/main/java/cn/reghao/tnb/content/app/aop/DataSourceAspect.java

@@ -0,0 +1,37 @@
+package cn.reghao.tnb.content.app.aop;
+
+import cn.reghao.tnb.content.app.config.DataSourceType;
+import cn.reghao.tnb.content.app.config.mybatis.DynamicDataSource;
+import lombok.extern.slf4j.Slf4j;
+import org.aspectj.lang.JoinPoint;
+import org.aspectj.lang.annotation.Aspect;
+import org.aspectj.lang.annotation.Before;
+import org.aspectj.lang.annotation.Pointcut;
+import org.springframework.context.annotation.EnableAspectJAutoProxy;
+import org.springframework.stereotype.Component;
+
+/**
+ * @author reghao
+ * @date 2023-12-31 03:01:16
+ */
+@Slf4j
+@Aspect
+@Component
+@EnableAspectJAutoProxy(proxyTargetClass = true)
+public class DataSourceAspect {
+    @Pointcut("execution(* cn.reghao.tnb.content.app.mall.db.mapper.*.*(..))")
+    public void aspect() {
+    }
+
+    @Before("aspect()")
+    public void before(JoinPoint point) {
+        String method = point.getSignature().getName();
+        if (method.startsWith("find")) {
+            log.info("method {} hit slave DataSource", method);
+            DynamicDataSource.name.set(DataSourceType.slave.getName());
+        } else {
+            log.info("method {} hit master DataSource", method);
+            DynamicDataSource.name.set(DataSourceType.master.getName());
+        }
+    }
+}

+ 23 - 0
content/content-service/src/main/java/cn/reghao/tnb/content/app/config/DataSourceType.java

@@ -0,0 +1,23 @@
+package cn.reghao.tnb.content.app.config;
+
+/**
+ * @author reghao
+ * @date 2023-12-31 03:08:06
+ */
+public enum DataSourceType {
+    master("write"),
+    slave("read");
+
+    private final String value;
+    DataSourceType(String value) {
+        this.value = value;
+    }
+
+    public String getName() {
+        return this.name();
+    }
+
+    public String getValue() {
+        return value;
+    }
+}

+ 351 - 0
content/content-service/src/main/java/cn/reghao/tnb/content/app/config/mybatis/CustomSqlSessionTemplate.java

@@ -0,0 +1,351 @@
+package cn.reghao.tnb.content.app.config.mybatis;
+
+import org.apache.ibatis.exceptions.PersistenceException;
+import org.apache.ibatis.executor.BatchResult;
+import org.apache.ibatis.session.*;
+import org.mybatis.spring.MyBatisExceptionTranslator;
+import org.mybatis.spring.SqlSessionTemplate;
+import org.springframework.dao.support.PersistenceExceptionTranslator;
+import org.springframework.util.Assert;
+
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.Method;
+import java.sql.Connection;
+import java.util.List;
+import java.util.Map;
+
+import static java.lang.reflect.Proxy.newProxyInstance;
+import static org.apache.ibatis.reflection.ExceptionUtil.unwrapThrowable;
+import static org.mybatis.spring.SqlSessionUtils.getSqlSession;
+import static org.mybatis.spring.SqlSessionUtils.isSqlSessionTransactional;
+
+/**
+ * @author reghao
+ * @date 2023-12-30 19:26:21
+ */
+public class CustomSqlSessionTemplate extends SqlSessionTemplate {
+    private final SqlSessionFactory sqlSessionFactory;
+    private final ExecutorType executorType;
+    private final SqlSession sqlSessionProxy;
+    private final PersistenceExceptionTranslator exceptionTranslator;
+
+    private Map<Object, SqlSessionFactory> targetSqlSessionFactories;
+    private SqlSessionFactory defaultTargetSqlSessionFactory;
+
+    public CustomSqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
+        this(sqlSessionFactory, sqlSessionFactory.getConfiguration().getDefaultExecutorType());
+    }
+
+    private CustomSqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType) {
+        this(sqlSessionFactory, executorType, new MyBatisExceptionTranslator(sqlSessionFactory.getConfiguration()
+                .getEnvironment().getDataSource(), true));
+    }
+
+    private CustomSqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType,
+                                     PersistenceExceptionTranslator exceptionTranslator) {
+
+        super(sqlSessionFactory, executorType, exceptionTranslator);
+
+        this.sqlSessionFactory = sqlSessionFactory;
+        this.executorType = executorType;
+        this.exceptionTranslator = exceptionTranslator;
+
+        this.sqlSessionProxy = (SqlSession) newProxyInstance(
+                SqlSessionFactory.class.getClassLoader(),
+                new Class[] { SqlSession.class },
+                new SqlSessionInterceptor());
+
+        this.defaultTargetSqlSessionFactory = sqlSessionFactory;
+    }
+
+    public void setTargetSqlSessionFactories(Map<Object, SqlSessionFactory> targetSqlSessionFactories) {
+        this.targetSqlSessionFactories = targetSqlSessionFactories;
+    }
+
+    public void setDefaultTargetSqlSessionFactory(SqlSessionFactory defaultTargetSqlSessionFactory) {
+        this.defaultTargetSqlSessionFactory = defaultTargetSqlSessionFactory;
+    }
+
+    @Override
+    public SqlSessionFactory getSqlSessionFactory() {
+        String databaseId = DynamicDataSource.name.get();
+        if (databaseId == null) {
+            databaseId = "master";
+        }
+
+        SqlSessionFactory targetSqlSessionFactory = targetSqlSessionFactories.get(databaseId);
+        if (targetSqlSessionFactory != null) {
+            return targetSqlSessionFactory;
+        } else if (defaultTargetSqlSessionFactory != null) {
+            return defaultTargetSqlSessionFactory;
+        } else {
+            Assert.notNull(targetSqlSessionFactories, "Property 'targetSqlSessionFactories' or 'defaultTargetSqlSessionFactory' are required");
+            Assert.notNull(defaultTargetSqlSessionFactory, "Property 'defaultTargetSqlSessionFactory' or 'targetSqlSessionFactories' are required");
+        }
+        return this.sqlSessionFactory;
+    }
+
+    @Override
+    public Configuration getConfiguration() {
+        return this.getSqlSessionFactory().getConfiguration();
+    }
+
+    @Override
+    public ExecutorType getExecutorType() {
+        return this.executorType;
+    }
+
+    @Override
+    public PersistenceExceptionTranslator getPersistenceExceptionTranslator() {
+        return this.exceptionTranslator;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public <T> T selectOne(String statement) {
+        return this.sqlSessionProxy.<T> selectOne(statement);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public <T> T selectOne(String statement, Object parameter) {
+        return this.sqlSessionProxy.<T> selectOne(statement, parameter);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public <K, V> Map<K, V> selectMap(String statement, String mapKey) {
+        return this.sqlSessionProxy.<K, V> selectMap(statement, mapKey);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public <K, V> Map<K, V> selectMap(String statement, Object parameter, String mapKey) {
+        return this.sqlSessionProxy.<K, V> selectMap(statement, parameter, mapKey);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public <K, V> Map<K, V> selectMap(String statement, Object parameter, String mapKey, RowBounds rowBounds) {
+        return this.sqlSessionProxy.<K, V> selectMap(statement, parameter, mapKey, rowBounds);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public <E> List<E> selectList(String statement) {
+        return this.sqlSessionProxy.<E> selectList(statement);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+
+    @Override
+    public <E> List<E> selectList(String statement, Object parameter) {
+        return this.sqlSessionProxy.<E> selectList(statement, parameter);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
+        return this.sqlSessionProxy.<E> selectList(statement, parameter, rowBounds);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void select(String statement, ResultHandler handler) {
+        this.sqlSessionProxy.select(statement, handler);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void select(String statement, Object parameter, ResultHandler handler) {
+        this.sqlSessionProxy.select(statement, parameter, handler);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void select(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler) {
+        this.sqlSessionProxy.select(statement, parameter, rowBounds, handler);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public int insert(String statement) {
+        return this.sqlSessionProxy.insert(statement);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public int insert(String statement, Object parameter) {
+        return this.sqlSessionProxy.insert(statement, parameter);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public int update(String statement) {
+        return this.sqlSessionProxy.update(statement);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public int update(String statement, Object parameter) {
+        return this.sqlSessionProxy.update(statement, parameter);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public int delete(String statement) {
+        return this.sqlSessionProxy.delete(statement);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public int delete(String statement, Object parameter) {
+        return this.sqlSessionProxy.delete(statement, parameter);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public <T> T getMapper(Class<T> type) {
+        return getConfiguration().getMapper(type, this);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void commit() {
+        throw new UnsupportedOperationException("Manual commit is not allowed over a Spring managed SqlSession");
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void commit(boolean force) {
+        throw new UnsupportedOperationException("Manual commit is not allowed over a Spring managed SqlSession");
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void rollback() {
+        throw new UnsupportedOperationException("Manual rollback is not allowed over a Spring managed SqlSession");
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void rollback(boolean force) {
+        throw new UnsupportedOperationException("Manual rollback is not allowed over a Spring managed SqlSession");
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void close() {
+        throw new UnsupportedOperationException("Manual close is not allowed over a Spring managed SqlSession");
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void clearCache() {
+        this.sqlSessionProxy.clearCache();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public Connection getConnection() {
+        return this.sqlSessionProxy.getConnection();
+    }
+
+    /**
+     * {@inheritDoc}
+     * @since 1.0.2
+     */
+    @Override
+    public List<BatchResult> flushStatements() {
+        return this.sqlSessionProxy.flushStatements();
+    }
+
+    /**
+     * Proxy needed to route MyBatis method calls to the proper SqlSession got from Spring's Transaction Manager It also
+     * unwraps exceptions thrown by {@code Method#invoke(Object, Object...)} to pass a {@code PersistenceException} to
+     * the {@code PersistenceExceptionTranslator}.
+     */
+    private class SqlSessionInterceptor implements InvocationHandler {
+        @Override
+        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
+            final SqlSession sqlSession = getSqlSession(
+                    CustomSqlSessionTemplate.this.getSqlSessionFactory(),
+                    CustomSqlSessionTemplate.this.executorType,
+                    CustomSqlSessionTemplate.this.exceptionTranslator);
+            try {
+                Object result = method.invoke(sqlSession, args);
+                if (!isSqlSessionTransactional(sqlSession, CustomSqlSessionTemplate.this.getSqlSessionFactory())) {
+                    // force commit even on non-dirty sessions because some databases require
+                    // a commit/rollback before calling close()
+                    sqlSession.commit(true);
+                }
+                return result;
+            } catch (Throwable t) {
+                Throwable unwrapped = unwrapThrowable(t);
+                if (CustomSqlSessionTemplate.this.exceptionTranslator != null && unwrapped instanceof PersistenceException) {
+                    Throwable translated = CustomSqlSessionTemplate.this.exceptionTranslator
+                            .translateExceptionIfPossible((PersistenceException) unwrapped);
+                    if (translated != null) {
+                        unwrapped = translated;
+                    }
+                }
+                throw unwrapped;
+            } finally {
+                closeSqlSession(sqlSession, CustomSqlSessionTemplate.this.getSqlSessionFactory());
+            }
+        }
+    }
+
+    private void closeSqlSession(SqlSession sqlSession, SqlSessionFactory sqlSessionFactory) {
+    }
+}

+ 3 - 3
content/content-service/src/main/java/cn/reghao/tnb/content/app/config/mybatis/DataSourceConfig.java

@@ -4,8 +4,8 @@ import org.apache.ibatis.plugin.Interceptor;
 import org.apache.ibatis.session.SqlSessionFactory;
 import org.mybatis.spring.SqlSessionFactoryBean;
 import org.mybatis.spring.SqlSessionTemplate;
+import org.springframework.beans.factory.annotation.Qualifier;
 import org.springframework.context.annotation.Bean;
-import org.springframework.context.annotation.Configuration;
 import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
 import org.springframework.jdbc.datasource.DataSourceTransactionManager;
 import org.springframework.transaction.PlatformTransactionManager;
@@ -19,11 +19,11 @@ import javax.sql.DataSource;
  * @author reghao
  * @date 2021-04-26 17:48:29
  */
-@Configuration
+//@Configuration
 public class DataSourceConfig {
     private final DataSource dataSource;
 
-    public DataSourceConfig(DataSource dataSource) {
+    public DataSourceConfig(@Qualifier("dynamicDataSource") DataSource dataSource) {
         this.dataSource = dataSource;
     }
 

+ 34 - 0
content/content-service/src/main/java/cn/reghao/tnb/content/app/config/mybatis/DynamicDataSource.java

@@ -0,0 +1,34 @@
+package cn.reghao.tnb.content.app.config.mybatis;
+
+import cn.reghao.tnb.content.app.config.DataSourceType;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
+
+import javax.sql.DataSource;
+import java.util.Map;
+
+/**
+ * @author reghao
+ * @date 2023-12-30 22:32:40
+ */
+@Slf4j
+public class DynamicDataSource extends AbstractRoutingDataSource {
+    public static final ThreadLocal<String> name = new ThreadLocal<>();
+
+    public DynamicDataSource(DataSource defaultTargetDataSource, Map<Object, Object> targetDataSources) {
+        super.setDefaultTargetDataSource(defaultTargetDataSource);
+        super.setTargetDataSources(targetDataSources);
+        super.afterPropertiesSet();
+    }
+
+    @Override
+    protected Object determineCurrentLookupKey() {
+        String type = name.get();
+        if (type == null) {
+            type = DataSourceType.master.getName();
+        }
+
+        //log.info("use DataSource -> {}", type);
+        return type;
+    }
+}

+ 37 - 0
content/content-service/src/main/java/cn/reghao/tnb/content/app/config/mybatis/DynamicDataSourceConfig.java

@@ -0,0 +1,37 @@
+package cn.reghao.tnb.content.app.config.mybatis;
+
+import cn.reghao.tnb.content.app.config.DataSourceType;
+import org.apache.shardingsphere.shardingjdbc.jdbc.core.datasource.ShardingDataSource;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+import javax.sql.DataSource;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * @author reghao
+ * @date 2023-12-31 01:02:45
+ */
+@Configuration
+public class DynamicDataSourceConfig {
+    @Bean(name = "dynamicDataSource")
+    public DynamicDataSource dynamicDataSource(DataSource dataSource) throws Exception {
+        if (dataSource instanceof ShardingDataSource) {
+            ShardingDataSource shardingDataSource = (ShardingDataSource) dataSource;
+            Map<String, DataSource> map = shardingDataSource.getDataSourceMap();
+            DataSource dataSourceMaster = map.get(DataSourceType.master.getName());
+            DataSource dataSourceSlave = map.get(DataSourceType.slave.getName());
+            return getDynamicDataSource(dataSourceMaster, dataSourceSlave);
+        }
+
+        throw new Exception("DataSource is not ShardingDataSource");
+    }
+
+    private DynamicDataSource getDynamicDataSource(DataSource master, DataSource slave) {
+        Map<Object, Object> targetDataSources = new HashMap<>();
+        targetDataSources.put(DataSourceType.master.getName(), master);
+        targetDataSources.put(DataSourceType.slave.getName(), slave);
+        return new DynamicDataSource(master, targetDataSources);
+    }
+}

+ 5 - 3
content/content-service/src/main/java/cn/reghao/tnb/content/app/config/mybatis/PageListInterceptor.java

@@ -20,7 +20,7 @@ import java.util.Properties;
  */
 @Intercepts({@Signature(type=StatementHandler.class,method="prepare",args={Connection.class,Integer.class})})
 public class PageListInterceptor implements Interceptor {
-    private final String methodSuffix = "Page";
+    private final String methodSuffix = "ByPage";
     @Deprecated
     private int page;
     @Deprecated
@@ -43,6 +43,8 @@ public class PageListInterceptor implements Interceptor {
 
         MappedStatement mappedStatement = (MappedStatement)metaObject.getValue("delegate.mappedStatement");
         String mapId = mappedStatement.getId();
+        /*String databaseId = mappedStatement.getDatabaseId();
+        DynamicDataSource.name.set(databaseId);*/
         if (mapId.matches(String.format(".+%s$", methodSuffix))){
             ParameterHandler parameterHandler = (ParameterHandler)metaObject.getValue("delegate.parameterHandler");
             Object object = parameterHandler.getParameterObject();
@@ -55,14 +57,14 @@ public class PageListInterceptor implements Interceptor {
                 if (param1 instanceof Page) {
                     page = (Page) param1;
                 } else {
-                    throw new Exception("ByPage 方法的第一个参数不是 Page 类型");
+                    throw new Exception("byPage 方法的第一个参数不是 Page 类型");
                 }
             } else {
                 throw new Exception("没有 Page 类型的参数");
             }
 
             String sql = (String) metaObject.getValue("delegate.boundSql.sql");
-            // sql += " limit "+(page-1)*size +","+size;
+            //sql += " limit "+(page-1)*size +","+size;
             sql += String.format(" limit %s,%s", (page.getPage()-1)*page.getSize(), page.getSize());
             metaObject.setValue("delegate.boundSql.sql", sql);
         }

+ 90 - 0
content/content-service/src/main/java/cn/reghao/tnb/content/app/config/mybatis/SqlSessionConfig.java

@@ -0,0 +1,90 @@
+package cn.reghao.tnb.content.app.config.mybatis;
+
+import org.apache.ibatis.session.SqlSessionFactory;
+import org.apache.shardingsphere.shardingjdbc.jdbc.core.datasource.ShardingDataSource;
+import org.mybatis.spring.SqlSessionFactoryBean;
+import org.mybatis.spring.SqlSessionTemplate;
+import org.mybatis.spring.annotation.MapperScan;
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
+
+import javax.sql.DataSource;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * @author reghao
+ * @date 2023-12-31 01:04:32
+ */
+@Configuration
+@MapperScan(basePackages = {
+        "cn.reghao.tnb.content.app.vod.db.mapper",
+        "cn.reghao.tnb.content.app.data.db.mapper",
+        "cn.reghao.tnb.content.app.exam.db.mapper",
+        "cn.reghao.tnb.content.app.mall.db.mapper",
+        "cn.reghao.tnb.content.app.geo.db.mapper"
+},
+        sqlSessionFactoryRef = "sqlSessionFactory")
+public class SqlSessionConfig {
+    //@Bean(name = "sqlSessionTemplate")
+    public SqlSessionTemplate sqlSessionTemplate(@Qualifier("shardingDataSource") DataSource dataSource) throws Exception {
+        if (dataSource instanceof ShardingDataSource) {
+            ShardingDataSource shardingDataSource = (ShardingDataSource) dataSource;
+            Map<String, DataSource> map = shardingDataSource.getDataSourceMap();
+            DataSource dataSourceMaster = map.get("master");
+            DataSource dataSourceSlave = map.get("slave");
+
+            SqlSessionFactory master = getSqlSessionFactory("master", dataSourceMaster);
+            SqlSessionFactory slave = getSqlSessionFactory("slave", dataSourceSlave);
+            Map<Object, SqlSessionFactory> factoryMap = new HashMap<>();
+            factoryMap.put("master", master);
+            factoryMap.put("slave", slave);
+
+            CustomSqlSessionTemplate sqlSessionTemplate = new CustomSqlSessionTemplate(master);
+            sqlSessionTemplate.setTargetSqlSessionFactories(factoryMap);
+            return sqlSessionTemplate;
+        }
+
+        throw new Exception("DataSource is not ShardingDataSource");
+    }
+
+    private SqlSessionFactory getSqlSessionFactory(String databaseId, DataSource dataSource) throws Exception {
+        SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
+        factoryBean.setDataSource(dataSource);
+        factoryBean.setPlugins(pageListInterceptor());
+
+        org.apache.ibatis.session.Configuration configuration = new org.apache.ibatis.session.Configuration();
+        configuration.setMapUnderscoreToCamelCase(true);
+        configuration.setDefaultFetchSize(100);
+        configuration.setDefaultStatementTimeout(30);
+        configuration.setDatabaseId(databaseId);
+        factoryBean.setConfiguration(configuration);
+
+        String location = "classpath*:mapper/**/**.xml";
+        factoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources(location));
+        return factoryBean.getObject();
+    }
+
+    @Bean(name = "sqlSessionFactory")
+    public SqlSessionFactory sqlSessionFactory(@Qualifier("dynamicDataSource") DataSource dataSource) throws Exception {
+        SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
+        factoryBean.setDataSource(dataSource);
+        factoryBean.setPlugins(pageListInterceptor());
+
+        org.apache.ibatis.session.Configuration configuration = new org.apache.ibatis.session.Configuration();
+        configuration.setMapUnderscoreToCamelCase(true);
+        configuration.setDefaultFetchSize(100);
+        configuration.setDefaultStatementTimeout(30);
+        factoryBean.setConfiguration(configuration);
+
+        String location = "classpath*:mapper/**/**.xml";
+        factoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources(location));
+        return factoryBean.getObject();
+    }
+
+    private PageListInterceptor pageListInterceptor() {
+        return new PageListInterceptor();
+    }
+}

+ 37 - 0
content/content-service/src/main/java/cn/reghao/tnb/content/app/config/shard/DataSourceUtil.java

@@ -0,0 +1,37 @@
+package cn.reghao.tnb.content.app.config.shard;
+
+import com.zaxxer.hikari.HikariConfig;
+import com.zaxxer.hikari.HikariDataSource;
+
+import javax.sql.DataSource;
+
+/**
+ * @author reghao
+ * @date 2022-03-15 18:58:56
+ */
+public class DataSourceUtil {
+    private static final String HOST = "localhost";
+    private static final int PORT = 3306;
+    private static final String USERNAME = "dev";
+    private static final String PASSWORD = "Dev@123456";
+
+    public static DataSource createDataSource(final String database) {
+        HikariConfig hikariConfig = new HikariConfig();
+        hikariConfig.setMinimumIdle(5);
+        hikariConfig.setMaximumPoolSize(10);
+        hikariConfig.setAutoCommit(true);
+        hikariConfig.setIdleTimeout(30000);
+        hikariConfig.setPoolName("EvaluationHikariCP");
+        hikariConfig.setMaxLifetime(1800000);
+        hikariConfig.setConnectionTimeout(30000);
+        hikariConfig.setConnectionTestQuery("SELECT 1");
+        hikariConfig.setDriverClassName("com.mysql.cj.jdbc.Driver");
+
+        String prefix = String.format("jdbc:mysql://%s:%s/%s?", HOST, PORT, database);
+        String jdbcUrl = prefix + "useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT%2b8";
+        hikariConfig.setJdbcUrl(jdbcUrl);
+        hikariConfig.setUsername(USERNAME);
+        hikariConfig.setPassword(PASSWORD);
+        return new HikariDataSource(hikariConfig);
+    }
+}

+ 17 - 0
content/content-service/src/main/java/cn/reghao/tnb/content/app/config/shard/ModuloShardingTableAlgorithm.java

@@ -0,0 +1,17 @@
+package cn.reghao.tnb.content.app.config.shard;
+
+import org.apache.shardingsphere.api.sharding.standard.PreciseShardingAlgorithm;
+import org.apache.shardingsphere.api.sharding.standard.PreciseShardingValue;
+
+import java.util.Collection;
+
+/**
+ * @author reghao
+ * @date 2022-03-15 21:01:01
+ */
+public class ModuloShardingTableAlgorithm implements PreciseShardingAlgorithm<String> {
+    @Override
+    public String doSharding(Collection<String> var1, PreciseShardingValue<String> var2) {
+        return "qq_mobile_$->{qq % 4}";
+    }
+}

+ 43 - 0
content/content-service/src/main/java/cn/reghao/tnb/content/app/config/shard/ShardingDataSourceConfig.java

@@ -0,0 +1,43 @@
+package cn.reghao.tnb.content.app.config.shard;
+
+import org.apache.shardingsphere.api.config.sharding.ShardingRuleConfiguration;
+import org.apache.shardingsphere.api.config.sharding.TableRuleConfiguration;
+import org.apache.shardingsphere.api.config.sharding.strategy.StandardShardingStrategyConfiguration;
+import org.apache.shardingsphere.shardingjdbc.api.ShardingDataSourceFactory;
+
+import javax.sql.DataSource;
+import java.sql.SQLException;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Properties;
+
+/**
+ * @author reghao
+ * @date 2022-03-15 18:46:37
+ */
+//@Configuration
+public class ShardingDataSourceConfig {
+    private final String database = "reghao_oss_rdb";
+
+    //@Bean
+    public DataSource getShardingDataSource() throws SQLException {
+        ShardingRuleConfiguration shardingRuleConfig = new ShardingRuleConfiguration();
+        shardingRuleConfig.getTableRuleConfigs().add(getTableRuleConfiguration());
+        //shardingRuleConfig.setDefaultDatabaseShardingStrategyConfig(new InlineShardingStrategyConfiguration("qq", "qq_mobile_$->{qq % 4}"));
+        shardingRuleConfig.setDefaultTableShardingStrategyConfig(
+                new StandardShardingStrategyConfiguration("qq", new ModuloShardingTableAlgorithm()));
+        return ShardingDataSourceFactory.createDataSource(createDataSourceMap(), shardingRuleConfig, new Properties());
+    }
+
+    private TableRuleConfiguration getTableRuleConfiguration() {
+        String logicalTalbe = "qq_mobile";
+        String actualDataNodes = database + ".qq_mobile_$->{0..3}";
+        return new TableRuleConfiguration(logicalTalbe, actualDataNodes);
+    }
+
+    private Map<String, DataSource> createDataSourceMap() {
+        Map<String, DataSource> result = new HashMap<>();
+        result.put(database, DataSourceUtil.createDataSource(database));
+        return result;
+    }
+}

+ 59 - 0
content/content-service/src/main/java/cn/reghao/tnb/content/app/mall/controller/CartController.java

@@ -0,0 +1,59 @@
+package cn.reghao.tnb.content.app.mall.controller;
+
+import cn.reghao.jutil.web.WebResult;
+import cn.reghao.tnb.common.auth.AuthUser;
+import cn.reghao.tnb.common.auth.UserContext;
+import cn.reghao.tnb.content.app.mall.model.dto.CartDto;
+import cn.reghao.tnb.content.app.mall.model.vo.CartCard;
+import cn.reghao.tnb.content.app.mall.service.CartService;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import org.springframework.http.MediaType;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+/**
+ * @author reghao
+ * @date 2024-04-16 10:54:44
+ */
+@Api(tags = "购物车接口")
+@RestController
+@RequestMapping("/api/mall/cart")
+public class CartController {
+    private final CartService cartService;
+
+    public CartController(CartService cartService) {
+        this.cartService = cartService;
+    }
+
+    @ApiOperation(value = "将商品加入购物车", notes = "N")
+    @AuthUser
+    @PostMapping("")
+    public String addCart(@RequestBody @Validated CartDto cartDto) {
+        long loginUser = UserContext.getUser();
+        long itemId = cartDto.getItemId();
+        int num = cartDto.getNum();
+        cartService.addCartItem(loginUser, itemId, num);
+        return WebResult.success();
+    }
+
+    @ApiOperation(value = "从购物车中删除商品", notes = "N")
+    @AuthUser
+    @DeleteMapping("/{itemId}")
+    public String deleteItem(@PathVariable("itemId") Long itemId) {
+        long loginUser = UserContext.getUser();
+        cartService.deleteCartItem(loginUser, itemId);
+        return WebResult.success();
+    }
+
+    @ApiOperation(value = "获取购物车中的商品", notes = "N")
+    @AuthUser
+    @GetMapping(value = "", produces = MediaType.APPLICATION_JSON_VALUE)
+    public String getCartItems() {
+        long loginUser = UserContext.getUser();
+        List<CartCard> list = cartService.getUserCart(loginUser);
+        return WebResult.success(list);
+    }
+}

+ 51 - 0
content/content-service/src/main/java/cn/reghao/tnb/content/app/mall/controller/DeliveryController.java

@@ -0,0 +1,51 @@
+package cn.reghao.tnb.content.app.mall.controller;
+
+import cn.reghao.jutil.web.WebResult;
+import cn.reghao.tnb.common.auth.AuthUser;
+import cn.reghao.tnb.common.auth.UserContext;
+import cn.reghao.tnb.content.app.mall.model.po.Delivery;
+import cn.reghao.tnb.content.app.mall.service.DeliveryService;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import org.springframework.http.MediaType;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+/**
+ * @author reghao
+ * @date 2024-04-26 11:45:14
+ */
+@Api(tags = "收货地址接口")
+@RestController
+@RequestMapping("/api/mall/delivery")
+public class DeliveryController {
+    private final DeliveryService deliveryService;
+
+    public DeliveryController(DeliveryService deliveryService) {
+        this.deliveryService = deliveryService;
+    }
+
+    @ApiOperation(value = "添加收获地址", notes = "N")
+    @AuthUser
+    @PostMapping(value = "", produces = MediaType.APPLICATION_JSON_VALUE)
+    public String addDeliveryAddress() {
+        return WebResult.success();
+    }
+
+    @ApiOperation(value = "删除收获地址", notes = "N")
+    @AuthUser
+    @DeleteMapping(value = "", produces = MediaType.APPLICATION_JSON_VALUE)
+    public String deleteDeliveryAddress() {
+        return WebResult.success();
+    }
+
+    @ApiOperation(value = "获取用户的收获地址列表", notes = "N")
+    @AuthUser
+    @GetMapping(value = "", produces = MediaType.APPLICATION_JSON_VALUE)
+    public String getDeliveryAddress() {
+        long loginUser = UserContext.getUser();
+        List<Delivery> list = deliveryService.getUserAddresses(loginUser);
+        return WebResult.success(list);
+    }
+}

+ 55 - 0
content/content-service/src/main/java/cn/reghao/tnb/content/app/mall/controller/LogisticsController.java

@@ -0,0 +1,55 @@
+package cn.reghao.tnb.content.app.mall.controller;
+
+import cn.reghao.jutil.web.WebResult;
+import cn.reghao.tnb.common.auth.AuthUser;
+import cn.reghao.tnb.common.auth.UserContext;
+import cn.reghao.tnb.content.app.mall.model.po.Logistics;
+import cn.reghao.tnb.content.app.mall.model.po.LogisticsProgress;
+import cn.reghao.tnb.content.app.mall.service.LogisticsService;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import org.springframework.http.MediaType;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+/**
+ * @author reghao
+ * @date 2024-09-19 09:06:30
+ */
+@Api(tags = "订单物流接口")
+@RestController
+@RequestMapping("/api/mall/logistics")
+public class LogisticsController {
+    private final LogisticsService logisticsService;
+
+    public LogisticsController(LogisticsService logisticsService) {
+        this.logisticsService = logisticsService;
+    }
+
+    @ApiOperation(value = "添加订单物流", notes = "N")
+    @AuthUser
+    @PostMapping(value = "/order", produces = MediaType.APPLICATION_JSON_VALUE)
+    public String putOrderLogistics(@RequestBody @Validated Logistics logistics) {
+        logisticsService.putOrderLogistics(logistics);
+        return WebResult.success();
+    }
+
+    @ApiOperation(value = "更新订单物流进度", notes = "N")
+    @AuthUser
+    @PostMapping(value = "/progress", produces = MediaType.APPLICATION_JSON_VALUE)
+    public String putOrderLogisticsProgress(@RequestBody @Validated LogisticsProgress logisticsProgress) {
+        logisticsService.putOrderLogisticsProgress(logisticsProgress);
+        return WebResult.success();
+    }
+
+    @ApiOperation(value = "获取订单物流进度", notes = "N")
+    @AuthUser
+    @GetMapping(value = "/order/{orderId}", produces = MediaType.APPLICATION_JSON_VALUE)
+    public String getOrderLogistics(@PathVariable("orderId") Long orderId) {
+        long userId = UserContext.getUser();
+        List<LogisticsProgress> list = logisticsService.getOrderLogistics(userId, orderId);
+        return WebResult.success(list);
+    }
+}

+ 79 - 0
content/content-service/src/main/java/cn/reghao/tnb/content/app/mall/controller/OrderController.java

@@ -0,0 +1,79 @@
+package cn.reghao.tnb.content.app.mall.controller;
+
+import cn.reghao.jutil.jdk.result.Result;
+import cn.reghao.jutil.web.WebResult;
+import cn.reghao.tnb.common.auth.AuthUser;
+import cn.reghao.tnb.content.app.mall.model.dto.OrderDto;
+import cn.reghao.tnb.content.app.mall.model.vo.OrderDetail;
+import cn.reghao.tnb.content.app.mall.service.OrderService;
+import cn.reghao.tnb.content.app.mall.service.PayService;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+/**
+ * @author reghao
+ * @date 2024-04-16 10:54:52
+ */
+@Api(tags = "订单接口")
+@RestController
+@RequestMapping("/api/mall/order")
+public class OrderController {
+    private final OrderService orderService;
+    private final PayService payService;
+
+    public OrderController(OrderService orderService, PayService payService) {
+        this.orderService = orderService;
+        this.payService = payService;
+    }
+
+    @AuthUser
+    @ApiOperation(value = "提交订单", notes = "N")
+    @PostMapping("")
+    public String submitOrder(@RequestBody @Validated OrderDto orderDto) {
+        long orderId = orderService.submitOrder(orderDto);
+        return WebResult.success(orderId);
+    }
+
+    @AuthUser
+    @ApiOperation(value = "取消订单", notes = "N")
+    @PostMapping("/cancel/{orderId}")
+    public String cancelOrder(@PathVariable("orderId") Long orderId) {
+        return WebResult.success();
+    }
+
+    @ApiOperation(value = "支付订单", notes = "N")
+    @AuthUser
+    @PostMapping("/pay/{orderId}")
+    public String payOrder(@PathVariable("orderId") Long orderId) {
+        Result result = payService.pay(orderId);
+        return WebResult.result(result);
+    }
+
+    @ApiOperation(value = "订单退款", notes = "N")
+    @AuthUser
+    @PostMapping("/refund/{orderId}")
+    public String refund(@PathVariable("orderId") Long orderId) {
+        Result result = payService.pay(orderId);
+        return WebResult.result(result);
+    }
+
+    @AuthUser
+    @ApiOperation(value = "获取订单列表", notes = "N")
+    @GetMapping("")
+    public String getOrders() {
+        List<OrderDetail> list = orderService.getOrders(1);
+        return WebResult.success(list);
+    }
+
+    @AuthUser
+    @ApiOperation(value = "获取订单详情", notes = "N")
+    @GetMapping("/{orderId}")
+    public String getOrderDetail(@PathVariable("orderId") Long orderId) {
+        OrderDetail orderDetail = orderService.getOrderDetail(orderId);
+        return WebResult.success(orderDetail);
+    }
+}

+ 57 - 0
content/content-service/src/main/java/cn/reghao/tnb/content/app/mall/controller/ProductController.java

@@ -0,0 +1,57 @@
+package cn.reghao.tnb.content.app.mall.controller;
+
+import cn.reghao.jutil.jdk.db.PageList;
+import cn.reghao.jutil.jdk.result.Result;
+import cn.reghao.jutil.web.WebResult;
+import cn.reghao.tnb.content.api.dto.TaobaoItem;
+import cn.reghao.tnb.content.app.mall.model.dto.ProductAddDto;
+import cn.reghao.tnb.content.app.mall.model.po.Product;
+import cn.reghao.tnb.content.app.mall.service.ProductService;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import org.springframework.http.MediaType;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+/**
+ * @author reghao
+ * @date 2024-04-16 10:53:54
+ */
+@Api(tags = "商品接口")
+@RestController
+@RequestMapping("/api/mall/product")
+public class ProductController {
+    private final ProductService productService;
+
+    public ProductController(ProductService productService) {
+        this.productService = productService;
+    }
+
+    @ApiOperation(value = "添加淘宝商品", notes = "N")
+    @PostMapping("/taobao")
+    public String addCam(@RequestBody @Validated TaobaoItem taobaoItem) {
+        productService.add(taobaoItem);
+        return WebResult.success();
+    }
+
+    @ApiOperation(value = "发布商品", notes = "N")
+    @PostMapping("")
+    public String addProduct(@RequestBody @Validated ProductAddDto productAddDto) throws Exception {
+        Result result = productService.addProduct(productAddDto);
+        return WebResult.result(result);
+    }
+
+    @ApiOperation(value = "获取商品列表", notes = "N")
+    @GetMapping(value = "", produces = MediaType.APPLICATION_JSON_VALUE)
+    public String getUserCams(@RequestParam("page") int page) {
+        PageList<Product> pageList = productService.getPage(page);
+        return WebResult.success(pageList);
+    }
+
+    @ApiOperation(value = "获取商品详情", notes = "N")
+    @GetMapping(value = "/{itemId}", produces = MediaType.APPLICATION_JSON_VALUE)
+    public String getProduct(@PathVariable("itemId") Long itemId) {
+        Product product = productService.getProduct(itemId);
+        return WebResult.success(product);
+    }
+}

+ 16 - 0
content/content-service/src/main/java/cn/reghao/tnb/content/app/mall/db/mapper/DeliveryMapper.java

@@ -0,0 +1,16 @@
+package cn.reghao.tnb.content.app.mall.db.mapper;
+
+import cn.reghao.jutil.jdk.db.BaseMapper;
+import cn.reghao.tnb.content.app.mall.model.po.Delivery;
+import org.apache.ibatis.annotations.Mapper;
+
+import java.util.List;
+
+/**
+ * @author reghao
+ * @date 2024-04-19 09:46:32
+ */
+@Mapper
+public interface DeliveryMapper extends BaseMapper<Delivery> {
+    List<Delivery> findByOwner(long owner);
+}

+ 15 - 0
content/content-service/src/main/java/cn/reghao/tnb/content/app/mall/db/mapper/LogisticsMapper.java

@@ -0,0 +1,15 @@
+package cn.reghao.tnb.content.app.mall.db.mapper;
+
+import cn.reghao.jutil.jdk.db.BaseMapper;
+import cn.reghao.tnb.content.app.mall.model.po.Logistics;
+import org.apache.ibatis.annotations.Mapper;
+
+/**
+ * @author reghao
+ * @date 2024-09-19 09:01:13
+ */
+@Mapper
+public interface LogisticsMapper extends BaseMapper<Logistics> {
+    Logistics findByOrderId(long orderId);
+    Logistics findByLogisticsId(String logisticsId);
+}

+ 16 - 0
content/content-service/src/main/java/cn/reghao/tnb/content/app/mall/db/mapper/LogisticsProgressMapper.java

@@ -0,0 +1,16 @@
+package cn.reghao.tnb.content.app.mall.db.mapper;
+
+import cn.reghao.jutil.jdk.db.BaseMapper;
+import cn.reghao.tnb.content.app.mall.model.po.LogisticsProgress;
+import org.apache.ibatis.annotations.Mapper;
+
+import java.util.List;
+
+/**
+ * @author reghao
+ * @date 2024-09-19 09:23:55
+ */
+@Mapper
+public interface LogisticsProgressMapper extends BaseMapper<LogisticsProgress> {
+    List<LogisticsProgress> findByLogisticsId(String logisticsId);
+}

+ 20 - 0
content/content-service/src/main/java/cn/reghao/tnb/content/app/mall/db/mapper/OrderMapper.java

@@ -0,0 +1,20 @@
+package cn.reghao.tnb.content.app.mall.db.mapper;
+
+import cn.reghao.jutil.jdk.db.BaseMapper;
+import cn.reghao.tnb.content.app.mall.model.po.Order;
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
+
+import java.util.List;
+
+/**
+ * @author reghao
+ * @date 2024-04-16 10:56:47
+ */
+@Mapper
+public interface OrderMapper extends BaseMapper<Order> {
+    void updateOrderStatus(@Param("orderId") long orderId, @Param("status") int status);
+
+    List<Order> findByOwner(int pageSize, long owner);
+    Order findByOrderId(long orderId);
+}

+ 16 - 0
content/content-service/src/main/java/cn/reghao/tnb/content/app/mall/db/mapper/ProductMapper.java

@@ -0,0 +1,16 @@
+package cn.reghao.tnb.content.app.mall.db.mapper;
+
+import cn.reghao.jutil.jdk.db.BaseMapper;
+import cn.reghao.tnb.content.app.mall.model.po.Product;
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
+
+/**
+ * @author reghao
+ * @date 2024-04-16 10:56:38
+ */
+@Mapper
+public interface ProductMapper extends BaseMapper<Product> {
+    void updateStockMinus(@Param("itemId") long itemId, @Param("num") int num);
+    Product findByItemId(long itemId);
+}

+ 14 - 0
content/content-service/src/main/java/cn/reghao/tnb/content/app/mall/db/mapper/ProductSnapshotMapper.java

@@ -0,0 +1,14 @@
+package cn.reghao.tnb.content.app.mall.db.mapper;
+
+import cn.reghao.jutil.jdk.db.BaseMapper;
+import cn.reghao.tnb.content.app.mall.model.po.ProductSnapshot;
+import org.apache.ibatis.annotations.Mapper;
+
+/**
+ * @author reghao
+ * @date 2024-04-26 16:37:48
+ */
+@Mapper
+public interface ProductSnapshotMapper extends BaseMapper<ProductSnapshot> {
+    ProductSnapshot findByOrderId(long orderId);
+}

+ 27 - 0
content/content-service/src/main/java/cn/reghao/tnb/content/app/mall/db/repository/MallRepository.java

@@ -0,0 +1,27 @@
+package cn.reghao.tnb.content.app.mall.db.repository;
+
+import cn.reghao.tnb.content.app.mall.db.mapper.ProductMapper;
+import cn.reghao.tnb.content.app.mall.model.po.Product;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.cache.annotation.Cacheable;
+import org.springframework.stereotype.Repository;
+
+/**
+ * @author reghao
+ * @date 2024-04-16 15:27:35
+ */
+@Slf4j
+@Repository
+public class MallRepository {
+    private final ProductMapper productMapper;
+
+    public MallRepository(ProductMapper productMapper) {
+        this.productMapper = productMapper;
+    }
+
+    @Cacheable(cacheNames = "tnb:mall:product", key = "#itemId", unless = "#result == null")
+    public Product getProduct(Long itemId) {
+        log.info("miss cache");
+        return productMapper.findByItemId(itemId);
+    }
+}

+ 33 - 0
content/content-service/src/main/java/cn/reghao/tnb/content/app/mall/job/JobService.java

@@ -0,0 +1,33 @@
+package cn.reghao.tnb.content.app.mall.job;
+
+import cn.reghao.jutil.jdk.thread.ThreadPoolWrapper;
+import cn.reghao.tnb.content.app.mall.db.mapper.OrderMapper;
+import cn.reghao.tnb.content.app.mall.job.task.OrderTask;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * @author reghao
+ * @date 2024-09-14 16:01:30
+ */
+@Slf4j
+@Service
+public class JobService {
+    private final ScheduledExecutorService scheduler;
+    private final OrderMapper orderMapper;
+
+    public JobService(OrderMapper orderMapper) {
+        this.scheduler = ThreadPoolWrapper.scheduledThreadPool("delay-job", 10);
+        this.orderMapper = orderMapper;
+    }
+
+    public void addDelayJob(long orderId) {
+        OrderTask orderTask = new OrderTask(orderId, orderMapper);
+        ScheduledFuture<?> future = scheduler.schedule(orderTask, 15, TimeUnit.MINUTES);
+        log.info("add order related delay job");
+    }
+}

+ 32 - 0
content/content-service/src/main/java/cn/reghao/tnb/content/app/mall/job/task/OrderTask.java

@@ -0,0 +1,32 @@
+package cn.reghao.tnb.content.app.mall.job.task;
+
+import cn.reghao.tnb.content.app.mall.db.mapper.OrderMapper;
+import cn.reghao.tnb.content.app.mall.model.constant.OrderStatus;
+import cn.reghao.tnb.content.app.mall.model.po.Order;
+import lombok.extern.slf4j.Slf4j;
+
+/**
+ * @author reghao
+ * @date 2024-09-14 16:06:01
+ */
+@Slf4j
+public class OrderTask implements Runnable {
+    private final long orderId;
+    private final OrderMapper orderMapper;
+
+    public OrderTask(long orderId, OrderMapper orderMapper) {
+        this.orderId = orderId;
+        this.orderMapper = orderMapper;
+    }
+
+    @Override
+    public void run() {
+        Order order = orderMapper.findByOrderId(orderId);
+        int status = order.getStatus();
+        if (status == OrderStatus.toPay.getCode()) {
+            orderMapper.updateOrderStatus(orderId, OrderStatus.cancelled.getCode());
+            log.info("cancel order");
+        }
+        log.info("exec order task");
+    }
+}

+ 43 - 0
content/content-service/src/main/java/cn/reghao/tnb/content/app/mall/model/constant/LogisticsStatus.java

@@ -0,0 +1,43 @@
+package cn.reghao.tnb.content.app.mall.model.constant;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * @author reghao
+ * @date 2024-09-19 11:27:40
+ */
+public enum LogisticsStatus {
+    status1(1, "已发货"),
+    status2(2, "已揽件"),
+    status3(3, "运输中"),
+    status4(4, "派送中"),
+    status5(5, "待取件"),
+    status6(6, "已签收");
+
+    private final int code;
+    private final String desc;
+
+    private static Map<Integer, LogisticsStatus> map = new HashMap<>();
+    static {
+        for (LogisticsStatus status : LogisticsStatus.values()) {
+            map.put(status.code, status);
+        }
+    }
+    LogisticsStatus(int code, String desc) {
+        this.code = code;
+        this.desc = desc;
+    }
+
+    public Integer getCode() {
+        return code;
+    }
+
+    public String getDesc() {
+        return desc;
+    }
+
+    public static LogisticsStatus getByCode(int code) {
+        return map.get(code);
+    }
+}

+ 42 - 0
content/content-service/src/main/java/cn/reghao/tnb/content/app/mall/model/constant/OrderStatus.java

@@ -0,0 +1,42 @@
+package cn.reghao.tnb.content.app.mall.model.constant;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * @author reghao
+ * @date 2024-04-26 15:50:53
+ */
+public enum OrderStatus {
+    toPay(1, "待付款"),
+    toDelivery(2, "待收货"),
+    toConfirm(3, "待确认"),
+    cancelled(4, "已取消"),
+    completed(5, "已完成");
+    
+    private final int code;
+    private final String desc;
+
+    private static Map<Integer, OrderStatus> map = new HashMap<>();
+    static {
+        for (OrderStatus status : OrderStatus.values()) {
+            map.put(status.code, status);
+        }
+    }
+    OrderStatus(int code, String desc) {
+        this.code = code;
+        this.desc = desc;
+    }
+
+    public Integer getCode() {
+        return code;
+    }
+
+    public String getDesc() {
+        return desc;
+    }
+
+    public static OrderStatus getByCode(int code) {
+        return map.get(code);
+    }
+}

+ 17 - 0
content/content-service/src/main/java/cn/reghao/tnb/content/app/mall/model/dto/CartDto.java

@@ -0,0 +1,17 @@
+package cn.reghao.tnb.content.app.mall.model.dto;
+
+import lombok.Getter;
+
+import javax.validation.constraints.NotNull;
+
+/**
+ * @author reghao
+ * @date 2024-04-16 15:05:08
+ */
+@Getter
+public class CartDto {
+    @NotNull
+    private Long itemId;
+    @NotNull
+    private Integer num;
+}

+ 19 - 0
content/content-service/src/main/java/cn/reghao/tnb/content/app/mall/model/dto/OrderDto.java

@@ -0,0 +1,19 @@
+package cn.reghao.tnb.content.app.mall.model.dto;
+
+import lombok.Getter;
+
+import javax.validation.constraints.NotNull;
+import javax.validation.constraints.Size;
+import java.util.List;
+
+/**
+ * @author reghao
+ * @date 2024-04-16 15:59:27
+ */
+@Getter
+public class OrderDto {
+    @NotNull
+    private Long deliveryId;
+    @Size(min = 1, max = 100)
+    private List<OrderItem> items;
+}

+ 23 - 0
content/content-service/src/main/java/cn/reghao/tnb/content/app/mall/model/dto/OrderItem.java

@@ -0,0 +1,23 @@
+package cn.reghao.tnb.content.app.mall.model.dto;
+
+import lombok.Getter;
+import lombok.Setter;
+
+import javax.validation.constraints.NotNull;
+
+/**
+ * @author reghao
+ * @date 2024-04-26 15:34:11
+ */
+@Setter
+@Getter
+public class OrderItem {
+    @NotNull
+    private Long itemId;
+    @NotNull
+    private Integer num;
+    @NotNull
+    private Long shopId;
+    @NotNull
+    private Long sellerId;
+}

+ 18 - 0
content/content-service/src/main/java/cn/reghao/tnb/content/app/mall/model/dto/ProductAddDto.java

@@ -0,0 +1,18 @@
+package cn.reghao.tnb.content.app.mall.model.dto;
+
+import lombok.Getter;
+import lombok.Setter;
+
+/**
+ * @author reghao
+ * @date 2024-11-08 09:31:00
+ */
+@Setter
+@Getter
+public class ProductAddDto {
+    private String title;
+    private String coverFileId;
+    private Integer coverChannelId;
+    private Double price;
+    private Integer amount;
+}

+ 21 - 0
content/content-service/src/main/java/cn/reghao/tnb/content/app/mall/model/po/Delivery.java

@@ -0,0 +1,21 @@
+package cn.reghao.tnb.content.app.mall.model.po;
+
+import cn.reghao.jutil.jdk.db.BaseObject;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+
+/**
+ * @author reghao
+ * @date 2024-04-19 09:43:06
+ */
+@NoArgsConstructor
+@AllArgsConstructor
+@Setter
+@Getter
+public class Delivery extends BaseObject<Integer> {
+    private Long deliveryId;
+    private String address;
+    private Long owner;
+}

+ 19 - 0
content/content-service/src/main/java/cn/reghao/tnb/content/app/mall/model/po/Logistics.java

@@ -0,0 +1,19 @@
+package cn.reghao.tnb.content.app.mall.model.po;
+
+import lombok.Getter;
+
+import javax.validation.constraints.NotNull;
+
+/**
+ * @author reghao
+ * @date 2024-09-19 09:00:07
+ */
+@Getter
+public class Logistics {
+    @NotNull
+    private Long orderId;
+    @NotNull
+    private String tpl;
+    @NotNull
+    private String logisticsId;
+}

+ 31 - 0
content/content-service/src/main/java/cn/reghao/tnb/content/app/mall/model/po/LogisticsProgress.java

@@ -0,0 +1,31 @@
+package cn.reghao.tnb.content.app.mall.model.po;
+
+import cn.reghao.tnb.content.app.mall.model.constant.LogisticsStatus;
+import lombok.Getter;
+import lombok.Setter;
+
+import java.time.LocalDateTime;
+
+/**
+ * @author reghao
+ * @date 2024-09-19 09:22:10
+ */
+@Setter
+@Getter
+public class LogisticsProgress {
+    private Long orderId;
+    private String logisticsId;
+    private Integer status;
+    private String current;
+    private LocalDateTime createAt;
+
+    public LogisticsProgress() {
+        this.createAt = LocalDateTime.now();
+    }
+
+    public LogisticsProgress(String logisticsId) {
+        this.status = LogisticsStatus.status1.getCode();
+        this.current = "包裹正在等待揽收";
+        this.createAt = LocalDateTime.now();
+    }
+}

+ 38 - 0
content/content-service/src/main/java/cn/reghao/tnb/content/app/mall/model/po/Order.java

@@ -0,0 +1,38 @@
+package cn.reghao.tnb.content.app.mall.model.po;
+
+import cn.reghao.jutil.jdk.db.BaseObject;
+import cn.reghao.tnb.common.auth.UserContext;
+import cn.reghao.tnb.content.app.mall.model.constant.OrderStatus;
+import cn.reghao.tnb.content.app.mall.model.dto.OrderItem;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+
+/**
+ * @author reghao
+ * @date 2024-04-16 10:56:56
+ */
+@NoArgsConstructor
+@AllArgsConstructor
+@Setter
+@Getter
+public class Order extends BaseObject<Integer> {
+    private Long orderId;
+    private Long deliveryId;
+    private Long productId;
+    private Double price;
+    private Integer amount;
+    private Long owner;
+    private Integer status;
+
+    public Order(long orderId, double price, long deliveryId, OrderItem orderItem) {
+        this.orderId = orderId;
+        this.deliveryId = deliveryId;
+        this.productId = orderItem.getItemId();
+        this.price = price;
+        this.amount = orderItem.getNum();
+        this.owner = UserContext.getUser();
+        this.status = OrderStatus.toPay.getCode();
+    }
+}

+ 11 - 0
content/content-service/src/main/java/cn/reghao/tnb/content/app/mall/model/po/OrderProduct.java

@@ -0,0 +1,11 @@
+package cn.reghao.tnb.content.app.mall.model.po;
+
+/**
+ * @author reghao
+ * @date 2024-04-26 15:42:06
+ */
+public class OrderProduct {
+    private Long orderId;
+    private Long itemId;
+    private Integer num;
+}

+ 49 - 0
content/content-service/src/main/java/cn/reghao/tnb/content/app/mall/model/po/Product.java

@@ -0,0 +1,49 @@
+package cn.reghao.tnb.content.app.mall.model.po;
+
+import cn.reghao.jutil.jdk.db.BaseObject;
+import cn.reghao.tnb.common.auth.UserContext;
+import cn.reghao.tnb.content.api.dto.TaobaoItem;
+import cn.reghao.tnb.content.app.mall.model.dto.ProductAddDto;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+
+/**
+ * @author reghao
+ * @date 2024-04-16 10:56:20
+ */
+@NoArgsConstructor
+@Setter
+@Getter
+public class Product extends BaseObject<Integer> {
+    private Long itemId;
+    private String itemUrl;
+    private String title;
+    private String picUrl;
+    private Double price;
+    private String monthSale;
+    private Integer stock;
+    private Long shopId;
+    private Long sellerId;
+
+    public Product(TaobaoItem taobaoItem) {
+        this.itemId = taobaoItem.getItemId();
+        this.itemUrl = taobaoItem.getItemUrl();
+        this.title = taobaoItem.getTitle();
+        this.picUrl = taobaoItem.getPicUrl();
+        this.price = taobaoItem.getPrice();
+        this.monthSale = taobaoItem.getMonthSale();
+        this.stock = 1000;
+        this.shopId = taobaoItem.getShopId();
+        this.sellerId = taobaoItem.getSellerId();
+    }
+
+    public Product(ProductAddDto productAddDto, long productId, String coverUrl) {
+        this.itemId = productId;
+        this.title = productAddDto.getTitle();
+        this.picUrl = coverUrl;
+        this.price = productAddDto.getPrice();
+        this.stock = productAddDto.getAmount();
+        this.sellerId = UserContext.getUser();
+    }
+}

+ 29 - 0
content/content-service/src/main/java/cn/reghao/tnb/content/app/mall/model/po/ProductSnapshot.java

@@ -0,0 +1,29 @@
+package cn.reghao.tnb.content.app.mall.model.po;
+
+import cn.reghao.jutil.jdk.db.BaseObject;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+
+/**
+ * @author reghao
+ * @date 2024-04-26 16:37:29
+ */
+@NoArgsConstructor
+@Getter
+@Setter
+public class ProductSnapshot extends BaseObject<Integer> {
+    private Long orderId;
+    private Long itemId;
+    private String title;
+    private String picUrl;
+    private Double price;
+
+    public ProductSnapshot(long orderId, Product product) {
+        this.orderId = orderId;
+        this.itemId = product.getItemId();
+        this.title = product.getTitle();
+        this.picUrl = product.getPicUrl();
+        this.price = product.getPrice();
+    }
+}

+ 27 - 0
content/content-service/src/main/java/cn/reghao/tnb/content/app/mall/model/vo/CartCard.java

@@ -0,0 +1,27 @@
+package cn.reghao.tnb.content.app.mall.model.vo;
+
+import cn.reghao.tnb.content.app.mall.model.po.Product;
+
+/**
+ * @author reghao
+ * @date 2024-04-16 15:16:56
+ */
+public class CartCard {
+    private Long itemId;
+    private String title;
+    private String picUrl;
+    private Double price;
+    private Integer num;
+    private Long shopId;
+    private Long sellerId;
+
+    public CartCard(Product product, int num) {
+        this.itemId = product.getItemId();
+        this.title = product.getTitle();
+        this.picUrl = product.getPicUrl();
+        this.price = product.getPrice();
+        this.num = num;
+        this.shopId = product.getShopId();
+        this.sellerId = product.getSellerId();
+    }
+}

+ 34 - 0
content/content-service/src/main/java/cn/reghao/tnb/content/app/mall/model/vo/OrderDetail.java

@@ -0,0 +1,34 @@
+package cn.reghao.tnb.content.app.mall.model.vo;
+
+import cn.reghao.jutil.jdk.converter.DateTimeConverter;
+import cn.reghao.tnb.content.app.mall.model.constant.OrderStatus;
+import cn.reghao.tnb.content.app.mall.model.po.Order;
+import cn.reghao.tnb.content.app.mall.model.po.ProductSnapshot;
+
+/**
+ * @author reghao
+ * @date 2024-04-19 16:58:24
+ */
+public class OrderDetail {
+    private Long itemId;
+    private Long orderId;
+    private String title;
+    private String picUrl;
+    private double price;
+    private int amount;
+    private int status;
+    private String statusText;
+    private String createAt;
+
+    public OrderDetail(Order order, ProductSnapshot productSnapshot) {
+        this.itemId = productSnapshot.getItemId();
+        this.orderId = order.getOrderId();
+        this.title = productSnapshot.getTitle();
+        this.picUrl = productSnapshot.getPicUrl();
+        this.price = productSnapshot.getPrice();
+        this.amount = order.getAmount();
+        this.status = OrderStatus.getByCode(order.getStatus()).getCode();
+        this.statusText = OrderStatus.getByCode(order.getStatus()).getDesc();
+        this.createAt = DateTimeConverter.format(order.getCreateTime());
+    }
+}

+ 55 - 0
content/content-service/src/main/java/cn/reghao/tnb/content/app/mall/service/CartService.java

@@ -0,0 +1,55 @@
+package cn.reghao.tnb.content.app.mall.service;
+
+import cn.reghao.tnb.content.app.mall.db.repository.MallRepository;
+import cn.reghao.tnb.content.app.mall.model.po.Product;
+import cn.reghao.tnb.content.app.mall.model.vo.CartCard;
+import cn.reghao.tnb.content.app.util.redis.ds.RedisHash;
+import org.springframework.stereotype.Service;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * @author reghao
+ * @date 2024-04-15 15:00:10
+ */
+@Service
+public class CartService {
+    private final String cartKeyPrefix = "tnb:mall:cart";
+    private final RedisHash<String> redisHash;
+    private final MallRepository mallRepository;
+
+    public CartService(RedisHash<String> redisHash, MallRepository mallRepository) {
+        this.redisHash = redisHash;
+        this.mallRepository = mallRepository;
+    }
+
+    public void addCartItem(long userId, long productId, int num) {
+        String key = String.format("%s:%s", cartKeyPrefix, userId);
+        String productIdStr = String.valueOf(productId);
+        redisHash.hset(key, productIdStr, num+"");
+    }
+
+    public void deleteCartItem(long userId, long productId) {
+        String key = String.format("%s:%s", cartKeyPrefix, userId);
+        String productIdStr = String.valueOf(productId);
+        redisHash.hdel(key, productIdStr);
+    }
+
+    public List<CartCard> getUserCart(long userId) {
+        String key = String.format("%s:%s", cartKeyPrefix, userId);
+        Long total = redisHash.hlen(key);
+        Map<String, String> items = redisHash.hgetall(key);
+
+        List<CartCard> list = new ArrayList<>();
+        items.forEach((itemId, num) -> {
+            Product product = mallRepository.getProduct(Long.parseLong(itemId));
+            if (product != null) {
+                list.add(new CartCard(product, Integer.parseInt(num)));
+            }
+        });
+
+        return list;
+    }
+}

+ 24 - 0
content/content-service/src/main/java/cn/reghao/tnb/content/app/mall/service/DeliveryService.java

@@ -0,0 +1,24 @@
+package cn.reghao.tnb.content.app.mall.service;
+
+import cn.reghao.tnb.content.app.mall.db.mapper.DeliveryMapper;
+import cn.reghao.tnb.content.app.mall.model.po.Delivery;
+import org.springframework.stereotype.Service;
+
+import java.util.List;
+
+/**
+ * @author reghao
+ * @date 2024-04-26 11:43:53
+ */
+@Service
+public class DeliveryService {
+    private final DeliveryMapper deliveryMapper;
+
+    public DeliveryService(DeliveryMapper deliveryMapper) {
+        this.deliveryMapper = deliveryMapper;
+    }
+
+    public List<Delivery> getUserAddresses(long userId) {
+        return deliveryMapper.findByOwner(userId);
+    }
+}

+ 59 - 0
content/content-service/src/main/java/cn/reghao/tnb/content/app/mall/service/LogisticsService.java

@@ -0,0 +1,59 @@
+package cn.reghao.tnb.content.app.mall.service;
+
+import cn.reghao.tnb.content.app.mall.db.mapper.LogisticsMapper;
+import cn.reghao.tnb.content.app.mall.db.mapper.LogisticsProgressMapper;
+import cn.reghao.tnb.content.app.mall.model.po.Logistics;
+import cn.reghao.tnb.content.app.mall.model.po.LogisticsProgress;
+import org.springframework.stereotype.Service;
+
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * @author reghao
+ * @date 2024-09-19 09:05:39
+ */
+@Service
+public class LogisticsService {
+    private final LogisticsMapper logisticsMapper;
+    private final LogisticsProgressMapper logisticsProgressMapper;
+
+    public LogisticsService(LogisticsMapper logisticsMapper, LogisticsProgressMapper logisticsProgressMapper) {
+        this.logisticsMapper = logisticsMapper;
+        this.logisticsProgressMapper = logisticsProgressMapper;
+    }
+
+    public void putOrderLogistics(Logistics logistics) {
+        String logisticsId = logistics.getLogisticsId();
+        Logistics logistics1 = logisticsMapper.findByLogisticsId(logisticsId);
+        if (logistics1 != null) {
+            return;
+        }
+
+        LogisticsProgress logisticsProgress = new LogisticsProgress(logisticsId);
+        logisticsMapper.save(logistics);
+        logisticsProgressMapper.save(logisticsProgress);
+    }
+
+    public void putOrderLogisticsProgress(LogisticsProgress logisticsProgress) {
+        long orderId = logisticsProgress.getOrderId();
+        Logistics logistics = logisticsMapper.findByOrderId(orderId);
+        if (logistics == null) {
+            return;
+        }
+
+        String logisticsId = logistics.getLogisticsId();
+        logisticsProgress.setLogisticsId(logisticsId);
+        logisticsProgressMapper.save(logisticsProgress);
+    }
+
+    public List<LogisticsProgress> getOrderLogistics(long userId, long orderId) {
+        Logistics logistics = logisticsMapper.findByOrderId(orderId);
+        if (logistics == null) {
+            return Collections.emptyList();
+        }
+
+        List<LogisticsProgress> list = logisticsProgressMapper.findByLogisticsId(logistics.getLogisticsId());
+        return list;
+    }
+}

+ 125 - 0
content/content-service/src/main/java/cn/reghao/tnb/content/app/mall/service/OrderService.java

@@ -0,0 +1,125 @@
+package cn.reghao.tnb.content.app.mall.service;
+
+import cn.reghao.jutil.tool.id.SnowFlake;
+import cn.reghao.tnb.common.auth.UserContext;
+import cn.reghao.tnb.content.app.mall.db.mapper.OrderMapper;
+import cn.reghao.tnb.content.app.mall.db.mapper.ProductMapper;
+import cn.reghao.tnb.content.app.mall.db.mapper.ProductSnapshotMapper;
+import cn.reghao.tnb.content.app.mall.job.JobService;
+import cn.reghao.tnb.content.app.mall.model.constant.OrderStatus;
+import cn.reghao.tnb.content.app.mall.model.dto.OrderDto;
+import cn.reghao.tnb.content.app.mall.model.dto.OrderItem;
+import cn.reghao.tnb.content.app.mall.model.po.Order;
+import cn.reghao.tnb.content.app.mall.model.po.Product;
+import cn.reghao.tnb.content.app.mall.model.po.ProductSnapshot;
+import cn.reghao.tnb.content.app.mall.model.vo.OrderDetail;
+import org.springframework.stereotype.Service;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+/**
+ * @author reghao
+ * @date 2024-04-16 11:00:24
+ */
+@Service
+public class OrderService {
+    private final OrderMapper orderMapper;
+    private final SnowFlake idGenerator;
+    private final CartService cartService;
+    private final ProductMapper productMapper;
+    private final ProductSnapshotMapper productSnapshotMapper;
+    private final JobService jobService;
+
+    public OrderService(OrderMapper orderMapper, CartService cartService,
+                        ProductMapper productMapper, ProductSnapshotMapper productSnapshotMapper,
+                        JobService jobService) {
+        this.orderMapper = orderMapper;
+        this.idGenerator = new SnowFlake(1L, 1L);
+        this.cartService = cartService;
+        this.productMapper = productMapper;
+        this.productSnapshotMapper = productSnapshotMapper;
+        this.jobService = jobService;
+    }
+
+    public long submit(OrderDto orderDto) {
+        long deliveryId = orderDto.getDeliveryId();
+        // shop -> products
+        Map<Long, List<OrderItem>> map = orderDto.getItems().stream().collect(Collectors.groupingBy(OrderItem::getShopId));
+        List<Order> orders = new ArrayList<>();
+        map.forEach((shopId, items) -> {
+            items.forEach(item -> {
+                long orderId = idGenerator.nextId();
+                Product product = productMapper.findByItemId(item.getItemId());
+                Order order = new Order(deliveryId, product.getPrice(), orderId, item);
+                orders.add(order);
+            });
+        });
+
+        if (!orders.isEmpty()) {
+            orderMapper.saveAll(orders);
+        }
+
+        long loginUser = UserContext.getUser();
+        map.forEach((shopId, items) -> {
+            items.forEach(item -> {
+                long itemId = item.getItemId();
+                cartService.deleteCartItem(loginUser, itemId);
+            });
+        });
+
+        return 0;
+    }
+
+    public long submitOrder(OrderDto orderDto) {
+        long deliveryId = orderDto.getDeliveryId();
+        OrderItem orderItem = orderDto.getItems().get(0);
+        long orderId = idGenerator.nextId();
+        Product product = productMapper.findByItemId(orderItem.getItemId());
+        Order order = new Order(orderId, product.getPrice(), deliveryId, orderItem);
+        orderMapper.save(order);
+
+        ProductSnapshot productSnapshot = new ProductSnapshot(orderId, product);
+        productSnapshotMapper.save(productSnapshot);
+        jobService.addDelayJob(orderId);
+        return orderId;
+    }
+
+    public void payOrder(long orderId) {
+        int status = OrderStatus.toDelivery.getCode();
+        orderMapper.updateOrderStatus(orderId, status);
+    }
+
+    public void deliveryOrder(long orderId) {
+        int status = OrderStatus.toConfirm.getCode();
+        orderMapper.updateOrderStatus(orderId, status);
+    }
+
+    public void receiveOrder(long orderId) {
+        int status = OrderStatus.completed.getCode();
+        orderMapper.updateOrderStatus(orderId, status);
+    }
+
+    public List<OrderDetail> getOrders(int pageNumber) {
+        long loginUser = UserContext.getUser();
+        int pageSize = 100;
+        List<Order> list = orderMapper.findByOwner(pageSize, loginUser);
+        return list.stream().map(order -> {
+            long orderId = order.getOrderId();
+            return getOrderDetail(orderId);
+        }).collect(Collectors.toList());
+    }
+
+    public Order getOrder(long orderId) {
+        return orderMapper.findByOrderId(orderId);
+    }
+
+    public OrderDetail getOrderDetail(long orderId) {
+        Order order = orderMapper.findByOrderId(orderId);
+        ProductSnapshot productSnapshot = productSnapshotMapper.findByOrderId(orderId);
+        OrderDetail orderDetail = new OrderDetail(order, productSnapshot);
+        return orderDetail;
+    }
+}

+ 54 - 0
content/content-service/src/main/java/cn/reghao/tnb/content/app/mall/service/PayService.java

@@ -0,0 +1,54 @@
+package cn.reghao.tnb.content.app.mall.service;
+
+import cn.reghao.jutil.jdk.result.Result;
+import cn.reghao.jutil.jdk.result.ResultStatus;
+import cn.reghao.tnb.common.auth.UserContext;
+import cn.reghao.tnb.content.app.mall.db.mapper.ProductMapper;
+import cn.reghao.tnb.content.app.mall.model.po.Order;
+import cn.reghao.tnb.user.api.iface.UserWalletService;
+import org.apache.dubbo.config.annotation.DubboReference;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+/**
+ * @author reghao
+ * @date 2024-04-19 10:12:58
+ */
+@Service
+public class PayService {
+    @DubboReference(check = false)
+    private UserWalletService userWalletService;
+
+    private final OrderService orderService;
+    private final ProductMapper productMapper;
+
+    public PayService(OrderService orderService, ProductMapper productMapper) {
+        this.orderService = orderService;
+        this.productMapper = productMapper;
+    }
+
+    @Transactional(rollbackFor = Exception.class)
+    public Result pay(long orderId) {
+        Order order = orderService.getOrder(orderId);
+        if (order != null && order.getStatus() == 1) {
+            long productId = order.getProductId();
+            int amount = order.getAmount();
+            long loginUser = UserContext.getUser();
+            long sellerId = productMapper.findByItemId(order.getProductId()).getSellerId();
+
+            // TODO 在一个事务内完成, 需要用到分布式事务
+            // 支付订单
+            Result result = userWalletService.pay(loginUser, amount, sellerId);
+            if (result.getCode() == ResultStatus.SUCCESS.getCode()) {
+                // 更新订单状态
+                orderService.payOrder(orderId);
+                // 更新库存数量
+                productMapper.updateStockMinus(productId, amount);
+            }
+
+            return  result;
+        }
+
+        return Result.fail("订单支付失败");
+    }
+}

+ 68 - 0
content/content-service/src/main/java/cn/reghao/tnb/content/app/mall/service/ProductService.java

@@ -0,0 +1,68 @@
+package cn.reghao.tnb.content.app.mall.service;
+
+import cn.reghao.file.api.iface.OssService;
+import cn.reghao.jutil.jdk.db.PageList;
+import cn.reghao.jutil.jdk.result.Result;
+import cn.reghao.jutil.tool.id.SnowFlake;
+import cn.reghao.oss.sdk.model.dto.media.ImageInfo;
+import cn.reghao.tnb.content.api.dto.TaobaoItem;
+import cn.reghao.tnb.content.app.mall.db.mapper.ProductMapper;
+import cn.reghao.tnb.content.app.mall.db.repository.MallRepository;
+import cn.reghao.tnb.content.app.mall.model.dto.ProductAddDto;
+import cn.reghao.tnb.content.app.mall.model.po.Product;
+import org.apache.dubbo.config.annotation.DubboReference;
+import org.springframework.stereotype.Service;
+
+import java.util.List;
+
+/**
+ * @author reghao
+ * @date 2024-04-16 11:00:17
+ */
+@Service
+public class ProductService {
+    @DubboReference(check = false)
+    private OssService ossService;
+
+    private final int pageSize = 12;
+    private final SnowFlake idGenerator;
+    private final ProductMapper productMapper;
+    private final MallRepository mallRepository;
+
+    public ProductService(ProductMapper productMapper, MallRepository mallRepository) {
+        this.idGenerator = new SnowFlake(1L, 1L);
+        this.productMapper = productMapper;
+        this.mallRepository = mallRepository;
+    }
+
+    public void add(TaobaoItem taobaoItem) {
+        Product product = new Product(taobaoItem);
+        productMapper.save(product);
+    }
+
+    public Result addProduct(ProductAddDto productAddDto) throws Exception {
+        String coverFileId = productAddDto.getCoverFileId();
+        int coverChannelId = productAddDto.getCoverChannelId();
+        ImageInfo imageInfo = ossService.getImageInfo(coverChannelId, coverFileId);
+        if (imageInfo == null) {
+            String errMsg = String.format("封面文件 %s 在 oss 中不存在", coverFileId);
+            return Result.fail(errMsg);
+        }
+
+        long productId = idGenerator.nextId();
+        String coverUrl = imageInfo.getUrl();
+        Product product = new Product(productAddDto, productId, coverUrl);
+        productMapper.save(product);
+        return Result.success();
+    }
+
+    public PageList<Product> getPage(int pageNumber) {
+        int total = 1;
+        List<Product> list = productMapper.findAll();
+        return PageList.pageList(pageNumber, pageSize, total, list);
+    }
+
+    public Product getProduct(Long itemId) {
+        return mallRepository.getProduct(itemId);
+    }
+}

+ 20 - 5
content/content-service/src/main/resources/application-dev.yml

@@ -22,13 +22,28 @@ spring:
     host: localhost
     port: 6379
     password: Dev@123456
-  datasource:
-    url: jdbc:mysql://localhost:3306/tnb_content_rdb?useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT%2b8
-    username: dev
-    password: Dev@123456
   rabbitmq:
     host: 127.0.0.1
     port: 5672
     virtual-host: /
     username: dev
-    password: Dev@123456
+    password: Dev@123456
+  shardingsphere:
+    datasource:
+      names: master,slave
+      masterslave:
+        name: ms
+        master-data-source-name: master
+        slave-data-source-names: slave
+      master:
+        type: com.zaxxer.hikari.HikariDataSource
+        driver-class-name: com.mysql.cj.jdbc.Driver
+        jdbcUrl: jdbc:mysql://127.0.0.1/tnb_content_rdb?useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT%2b8
+        username: dev
+        password: Dev@123456
+      slave:
+        type: com.zaxxer.hikari.HikariDataSource
+        driver-class-name: com.mysql.cj.jdbc.Driver
+        jdbcUrl: jdbc:mysql://127.0.0.1/tnb_content_rdb?useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT%2b8
+        username: dev
+        password: Dev@123456

+ 17 - 0
content/content-service/src/main/resources/mapper/mall/DeliveryMapper.xml

@@ -0,0 +1,17 @@
+<?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="cn.reghao.tnb.content.app.mall.db.mapper.DeliveryMapper">
+    <insert id="save" useGeneratedKeys="true" keyProperty="id">
+        insert into mall_delivery
+        (`delivery_id`,`address`,`owner`)
+        values 
+        (#{deliveryId},#{address},#{owner})
+    </insert>
+    
+    <select id="findByOwner" resultType="cn.reghao.tnb.content.app.mall.model.po.Delivery">
+        select *
+        from mall_delivery
+        where `owner`=#{owner}
+    </select>
+</mapper>

+ 22 - 0
content/content-service/src/main/resources/mapper/mall/LogisticsMapper.xml

@@ -0,0 +1,22 @@
+<?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="cn.reghao.tnb.content.app.mall.db.mapper.LogisticsMapper">
+    <insert id="save" useGeneratedKeys="true" keyProperty="id">
+        insert into mall_logistics
+            (`order_id`,`tpl`,`logistics_id`,`current`,`create_at`)
+        values
+            (#{orderId},#{tpl},#{logisticsId},#{current},#{createAt})
+    </insert>
+
+    <select id="findByOrderId" resultType="cn.reghao.tnb.content.app.mall.model.po.Logistics">
+        select *
+        from mall_logistics
+        where `order_id`=#{orderId}
+    </select>
+    <select id="findByLogisticsId" resultType="cn.reghao.tnb.content.app.mall.model.po.Logistics">
+        select *
+        from mall_logistics
+        where `logistics_id`=#{logisticsId}
+    </select>
+</mapper>

+ 18 - 0
content/content-service/src/main/resources/mapper/mall/LogisticsProgressMapper.xml

@@ -0,0 +1,18 @@
+<?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="cn.reghao.tnb.content.app.mall.db.mapper.LogisticsProgressMapper">
+    <insert id="save" useGeneratedKeys="true" keyProperty="id">
+        insert into mall_logistics_progress
+        (`logistics_id`,`status`,`current`,`create_at`)
+        values
+        (#{logisticsId},#{status},#{current},#{createAt})
+    </insert>
+
+    <select id="findByLogisticsId" resultType="cn.reghao.tnb.content.app.mall.model.po.LogisticsProgress">
+        select *
+        from mall_logistics_progress
+        where `logistics_id`=#{logisticsId}
+        order by create_at desc
+    </select>
+</mapper>

+ 38 - 0
content/content-service/src/main/resources/mapper/mall/OrderMapper.xml

@@ -0,0 +1,38 @@
+<?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="cn.reghao.tnb.content.app.mall.db.mapper.OrderMapper">
+    <insert id="save" useGeneratedKeys="true" keyProperty="id">
+        insert into mall_order
+        (`order_id`,`product_id`,`delivery_id`,`price`,`amount`,`owner`,`status`)
+        values
+        (#{orderId},#{productId},#{deliveryId},#{price},#{amount},#{owner},#{status})
+    </insert>
+    <insert id="saveAll" useGeneratedKeys="true" keyProperty="id">
+        insert into mall_order
+        (`order_id`,`product_id`,`delivery_id`,`price`,`amount`,`owner`,`status`)
+        values
+        <foreach collection="list" item="item" index="index" separator=",">
+            (#{item.orderId},#{item.productId},#{item.deliveryId},#{item.price},#{item.amount},#{item.owner},#{item.status})
+        </foreach>
+    </insert>
+
+    <update id="updateOrderStatus">
+        update mall_order
+        set `status`=#{status}
+        where `order_id`=#{orderId}
+    </update>
+    
+    <select id="findByOwner" resultType="cn.reghao.tnb.content.app.mall.model.po.Order">
+        select *
+        from mall_order
+        where `owner`=#{owner}
+        order by create_time desc
+        limit #{pageSize}
+    </select>
+    <select id="findByOrderId" resultType="cn.reghao.tnb.content.app.mall.model.po.Order">
+        select *
+        from mall_order
+        where `order_id`=#{orderId}
+    </select>
+</mapper>

+ 29 - 0
content/content-service/src/main/resources/mapper/mall/ProductMapper.xml

@@ -0,0 +1,29 @@
+<?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="cn.reghao.tnb.content.app.mall.db.mapper.ProductMapper">
+    <insert id="save" useGeneratedKeys="true" keyProperty="id">
+        insert ignore into mall_product
+        (`item_id`,`item_url`,`title`,`pic_url`,`price`,`month_sale`,`stock`,`shop_id`,`seller_id`)
+        values 
+        (#{itemId},#{itemUrl},#{title},#{picUrl},#{price},#{monthSale},#{stock},#{shopId},#{sellerId})
+    </insert>
+
+    <update id="updateStockMinus">
+        update mall_product
+        set stock=stock-#{num}
+        where item_id=#{itemId}
+    </update>
+
+    <select id="findAll" resultType="cn.reghao.tnb.content.app.mall.model.po.Product">
+        select *
+        from mall_product
+        order by id desc
+        limit 12
+    </select>
+    <select id="findByItemId" resultType="cn.reghao.tnb.content.app.mall.model.po.Product">
+        select *
+        from mall_product
+        where item_id=#{itemId}
+    </select>
+</mapper>

+ 17 - 0
content/content-service/src/main/resources/mapper/mall/ProductSnapshotMapper.xml

@@ -0,0 +1,17 @@
+<?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="cn.reghao.tnb.content.app.mall.db.mapper.ProductSnapshotMapper">
+    <insert id="save" useGeneratedKeys="true" keyProperty="id">
+        insert into mall_product_snapshot
+        (`order_id`,`item_id`,`title`,`pic_url`,`price`)
+        values 
+        (#{orderId},#{itemId},#{title},#{picUrl},#{price})
+    </insert>
+
+    <select id="findByOrderId" resultType="cn.reghao.tnb.content.app.mall.model.po.ProductSnapshot">
+        select *
+        from mall_product_snapshot
+        where order_id=#{orderId}
+    </select>
+</mapper>

+ 1 - 1
file/file-service/pom.xml

@@ -101,7 +101,7 @@
         <dependency>
             <groupId>org.mybatis.spring.boot</groupId>
             <artifactId>mybatis-spring-boot-starter</artifactId>
-            <version>1.3.2</version>
+            <version>2.1.1</version>
         </dependency>
 
         <dependency>

+ 1 - 1
message/message-service/pom.xml

@@ -52,7 +52,7 @@
         <dependency>
             <groupId>org.mybatis.spring.boot</groupId>
             <artifactId>mybatis-spring-boot-starter</artifactId>
-            <version>1.3.2</version>
+            <version>2.1.1</version>
         </dependency>
 
         <dependency>

+ 1 - 1
user/user-service/pom.xml

@@ -81,7 +81,7 @@
         <dependency>
             <groupId>org.mybatis.spring.boot</groupId>
             <artifactId>mybatis-spring-boot-starter</artifactId>
-            <version>1.3.2</version>
+            <version>2.1.1</version>
         </dependency>
 
         <dependency>