Parcourir la source

【优化】支持登陆用户,直接读取昵称、部门等信息,也支持自定义字段

YunaiV il y a 1 an
Parent
commit
cbc36d9220
37 fichiers modifiés avec 2184 ajouts et 36 suppressions
  1. 4 3
      pom.xml
  2. 24 0
      yudao-module-guide/pom.xml
  3. 26 0
      yudao-module-guide/yudao-module-guide-api/pom.xml
  4. 24 0
      yudao-module-guide/yudao-module-guide-api/src/main/java/cn/iocoder/yudao/module/guide/enums/ErrorCodeConstants.java
  5. 63 0
      yudao-module-guide/yudao-module-guide-biz/pom.xml
  6. 25 0
      yudao-module-guide/yudao-module-guide-biz/src/main/java/cn/iocoder/yudao/module/guide/controller/admin/DemoTestController.java
  7. 139 0
      yudao-module-guide/yudao-module-guide-biz/src/main/java/cn/iocoder/yudao/module/guide/controller/admin/sights/SightsController.java
  8. 31 0
      yudao-module-guide/yudao-module-guide-biz/src/main/java/cn/iocoder/yudao/module/guide/controller/admin/sights/vo/SightsPageReqVO.java
  9. 55 0
      yudao-module-guide/yudao-module-guide-biz/src/main/java/cn/iocoder/yudao/module/guide/controller/admin/sights/vo/SightsRespVO.java
  10. 48 0
      yudao-module-guide/yudao-module-guide-biz/src/main/java/cn/iocoder/yudao/module/guide/controller/admin/sights/vo/SightsSaveReqVO.java
  11. 94 0
      yudao-module-guide/yudao-module-guide-biz/src/main/java/cn/iocoder/yudao/module/guide/controller/admin/sightscategory/SightsCategoryController.java
  12. 29 0
      yudao-module-guide/yudao-module-guide-biz/src/main/java/cn/iocoder/yudao/module/guide/controller/admin/sightscategory/vo/SightsCategoryListReqVO.java
  13. 59 0
      yudao-module-guide/yudao-module-guide-biz/src/main/java/cn/iocoder/yudao/module/guide/controller/admin/sightscategory/vo/SightsCategoryRespVO.java
  14. 43 0
      yudao-module-guide/yudao-module-guide-biz/src/main/java/cn/iocoder/yudao/module/guide/controller/admin/sightscategory/vo/SightsCategorySaveReqVO.java
  15. 25 0
      yudao-module-guide/yudao-module-guide-biz/src/main/java/cn/iocoder/yudao/module/guide/controller/app/AppDemoTestController.java
  16. 81 0
      yudao-module-guide/yudao-module-guide-biz/src/main/java/cn/iocoder/yudao/module/guide/dal/dataobject/sights/SightsDO.java
  17. 73 0
      yudao-module-guide/yudao-module-guide-biz/src/main/java/cn/iocoder/yudao/module/guide/dal/dataobject/sights/SightsI18nExtensionDO.java
  18. 67 0
      yudao-module-guide/yudao-module-guide-biz/src/main/java/cn/iocoder/yudao/module/guide/dal/dataobject/sightscategory/SightsCategoryDO.java
  19. 30 0
      yudao-module-guide/yudao-module-guide-biz/src/main/java/cn/iocoder/yudao/module/guide/dal/mysql/sights/SightsI18nExtensionMapper.java
  20. 29 0
      yudao-module-guide/yudao-module-guide-biz/src/main/java/cn/iocoder/yudao/module/guide/dal/mysql/sights/SightsMapper.java
  21. 37 0
      yudao-module-guide/yudao-module-guide-biz/src/main/java/cn/iocoder/yudao/module/guide/dal/mysql/sightscategory/SightsCategoryMapper.java
  22. 97 0
      yudao-module-guide/yudao-module-guide-biz/src/main/java/cn/iocoder/yudao/module/guide/service/sights/SightsService.java
  23. 127 0
      yudao-module-guide/yudao-module-guide-biz/src/main/java/cn/iocoder/yudao/module/guide/service/sights/SightsServiceImpl.java
  24. 55 0
      yudao-module-guide/yudao-module-guide-biz/src/main/java/cn/iocoder/yudao/module/guide/service/sightscategory/SightsCategoryService.java
  25. 136 0
      yudao-module-guide/yudao-module-guide-biz/src/main/java/cn/iocoder/yudao/module/guide/service/sightscategory/SightsCategoryServiceImpl.java
  26. 12 0
      yudao-module-guide/yudao-module-guide-biz/src/main/resources/mapper/sights/SightsMapper.xml
  27. 12 0
      yudao-module-guide/yudao-module-guide-biz/src/main/resources/mapper/sightscategory/SightsCategoryMapper.xml
  28. 142 0
      yudao-module-guide/yudao-module-guide-biz/src/test/java/cn/iocoder/yudao/module/guide/service/sights/SightsServiceImplTest.java
  29. 134 0
      yudao-module-guide/yudao-module-guide-biz/src/test/java/cn/iocoder/yudao/module/guide/service/sights4comment/Sights4commentServiceImplTest.java
  30. 141 0
      yudao-module-guide/yudao-module-guide-biz/src/test/java/cn/iocoder/yudao/module/guide/service/sightscategory/SightsCategoryServiceImplTest.java
  31. 142 0
      yudao-module-guide/yudao-module-guide-biz/src/test/java/cn/iocoder/yudao/module/guide/service/sightscomment/SightsCommentServiceImplTest.java
  32. 55 0
      yudao-module-guide/yudao-module-guide-biz/src/test/resources/application-unit-test.yaml
  33. 4 0
      yudao-module-guide/yudao-module-guide-biz/src/test/resources/logback.xml
  34. 5 0
      yudao-module-guide/yudao-module-guide-biz/src/test/resources/sql/clean.sql
  35. 69 0
      yudao-module-guide/yudao-module-guide-biz/src/test/resources/sql/create_tables.sql
  36. 35 31
      yudao-server/pom.xml
  37. 12 2
      yudao-server/src/main/resources/application-local.yaml

+ 4 - 3
pom.xml

@@ -15,12 +15,13 @@
         <!-- 各种 module 拓展 -->
         <module>yudao-module-system</module>
         <module>yudao-module-infra</module>
-<!--        <module>yudao-module-member</module>-->
+        <module>yudao-module-guide</module>
+        <module>yudao-module-member</module>
 <!--        <module>yudao-module-bpm</module>-->
 <!--        <module>yudao-module-report</module>-->
 <!--        <module>yudao-module-mp</module>-->
-<!--        <module>yudao-module-pay</module>-->
-<!--        <module>yudao-module-mall</module>-->
+        <module>yudao-module-pay</module>
+        <module>yudao-module-mall</module>
 <!--        <module>yudao-module-crm</module>-->
 <!--        <module>yudao-module-erp</module>-->
         <!-- 示例项目 -->

+ 24 - 0
yudao-module-guide/pom.xml

@@ -0,0 +1,24 @@
+<?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>cn.iocoder.boot</groupId>
+        <artifactId>yudao</artifactId>
+        <version>${revision}</version>
+    </parent>
+
+    <artifactId>yudao-module-guide</artifactId>
+    <packaging>pom</packaging>
+
+    <name>${project.artifactId}</name>
+    <description>
+        guide模块,主要实现 导游、用户、旅程 等功能。
+    </description>
+    <modules>
+        <module>yudao-module-guide-api</module>
+        <module>yudao-module-guide-biz</module>
+    </modules>
+
+</project>

+ 26 - 0
yudao-module-guide/yudao-module-guide-api/pom.xml

@@ -0,0 +1,26 @@
+<?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>cn.iocoder.boot</groupId>
+        <artifactId>yudao-module-guide</artifactId>
+        <version>${revision}</version>
+    </parent>
+
+    <artifactId>yudao-module-guide-api</artifactId>
+    <packaging>jar</packaging>
+    <name>${project.artifactId}</name>
+    <description>
+        guide 模块 API,暴露给其它模块调用
+    </description>
+
+    <dependencies>
+        <dependency>
+            <groupId>cn.iocoder.boot</groupId>
+            <artifactId>yudao-common</artifactId>
+        </dependency>
+    </dependencies>
+
+</project>

+ 24 - 0
yudao-module-guide/yudao-module-guide-api/src/main/java/cn/iocoder/yudao/module/guide/enums/ErrorCodeConstants.java

@@ -0,0 +1,24 @@
+package cn.iocoder.yudao.module.guide.enums;
+import cn.iocoder.yudao.framework.common.exception.ErrorCode;
+/**
+ * System 错误码枚举类
+ * <p>
+ * system 系统,使用 9-00-000-000 段
+ */
+public interface ErrorCodeConstants {
+    // ========== 观光景点 9-00-000-001 ==========
+    ErrorCode CATEGORY_NOT_EXISTS = new ErrorCode(9-00-000-001, "观光景点分类不存在");
+    ErrorCode SIGHTS_CATEGORY_NOT_EXISTS = new ErrorCode(9-00-000-002, "观光景点分类不存在");
+    ErrorCode SIGHTS_CATEGORY_EXITS_CHILDREN = new ErrorCode(9-00-000-003, "存在存在子观光景点分类,无法删除");
+    ErrorCode SIGHTS_CATEGORY_PARENT_NOT_EXITS = new ErrorCode(9-00-000-004,"父级观光景点分类不存在");
+    ErrorCode SIGHTS_CATEGORY_PARENT_ERROR = new ErrorCode(9-00-000-005, "不能设置自己为父观光景点分类");
+    ErrorCode SIGHTS_CATEGORY_NAME_CN_DUPLICATE = new ErrorCode(9-00-000-006, "已经存在该分类中文名称的观光景点分类");
+
+    ErrorCode SIGHTS_CATEGORY_PARENT_IS_CHILD = new ErrorCode(9-00-000-007, "不能设置自己的子SightsCategory为父SightsCategory");
+
+    ErrorCode SIGHTS_NOT_EXISTS = new ErrorCode(9-00-002-001, "观光景点基本信息不存在");
+    ErrorCode SIGHTS_I18N_EXTENSION_NOT_EXISTS = new ErrorCode(9-00-002-002, "观光景点对语言扩充信息不存在");
+
+    ErrorCode SIGHTS_COMMENT_NOT_EXISTS = new ErrorCode(9-00-004-001, "观光景点评论不存在");
+    ErrorCode SIGHTS4COMMENT_NOT_EXISTS = new ErrorCode(9-00-005-001, "观光景点评价不存在");
+}

+ 63 - 0
yudao-module-guide/yudao-module-guide-biz/pom.xml

@@ -0,0 +1,63 @@
+<?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>cn.iocoder.boot</groupId>
+        <artifactId>yudao-module-guide</artifactId>
+        <version>${revision}</version>
+    </parent>
+    <packaging>jar</packaging>
+    <artifactId>yudao-module-guide-biz</artifactId>
+
+    <name>${project.artifactId}</name>
+    <description>
+        guide-biz模块,主要实现 导游、用户、旅程 等功能的逻辑实现。
+    </description>
+
+    <properties>
+        <maven.compiler.source>21</maven.compiler.source>
+        <maven.compiler.target>21</maven.compiler.target>
+        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+    </properties>
+    <dependencies>
+        <dependency>
+            <groupId>cn.iocoder.boot</groupId>
+            <artifactId>yudao-module-guide-api</artifactId>
+            <version>${revision}</version>
+        </dependency>
+
+        <!-- Web 相关 -->
+        <dependency>
+            <groupId>cn.iocoder.boot</groupId>
+            <artifactId>yudao-spring-boot-starter-web</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>cn.iocoder.boot</groupId>
+            <artifactId>yudao-spring-boot-starter-security</artifactId>
+        </dependency>
+
+        <!-- DB 相关 -->
+        <dependency>
+            <groupId>cn.iocoder.boot</groupId>
+            <artifactId>yudao-spring-boot-starter-mybatis</artifactId>
+        </dependency>
+
+        <!-- Test 测试相关 -->
+        <dependency>
+            <groupId>cn.iocoder.boot</groupId>
+            <artifactId>yudao-spring-boot-starter-test</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.alibaba</groupId>
+            <artifactId>easyexcel-core</artifactId>
+            <version>3.3.3</version>
+        </dependency>
+        <dependency>
+            <groupId>cn.iocoder.boot</groupId>
+            <artifactId>yudao-spring-boot-starter-excel</artifactId>
+        </dependency>
+    </dependencies>
+</project>

+ 25 - 0
yudao-module-guide/yudao-module-guide-biz/src/main/java/cn/iocoder/yudao/module/guide/controller/admin/DemoTestController.java

@@ -0,0 +1,25 @@
+package cn.iocoder.yudao.module.guide.controller.admin;
+
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import io.swagger.v3.oas.annotations.Operation;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+
+@Tag(name = "管理后台 - Test")
+@RestController
+@RequestMapping("/demo/test")
+@Validated
+public class DemoTestController {
+
+    @GetMapping("/get")
+    @Operation(summary = "获取 test 信息")
+    public CommonResult<String> get() {
+        return success("true");
+    }
+
+}

+ 139 - 0
yudao-module-guide/yudao-module-guide-biz/src/main/java/cn/iocoder/yudao/module/guide/controller/admin/sights/SightsController.java

@@ -0,0 +1,139 @@
+package cn.iocoder.yudao.module.guide.controller.admin.sights;
+
+import org.springframework.web.bind.annotation.*;
+import jakarta.annotation.Resource;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.security.access.prepost.PreAuthorize;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.Operation;
+
+import jakarta.validation.constraints.*;
+import jakarta.validation.*;
+import jakarta.servlet.http.*;
+import java.util.*;
+import java.io.IOException;
+
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+
+import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
+
+import cn.iocoder.yudao.framework.apilog.core.annotations.ApiAccessLog;
+import static cn.iocoder.yudao.framework.apilog.core.enums.OperateTypeEnum.*;
+
+import cn.iocoder.yudao.module.guide.controller.admin.sights.vo.*;
+import cn.iocoder.yudao.module.guide.dal.dataobject.sights.SightsDO;
+import cn.iocoder.yudao.module.guide.dal.dataobject.sights.SightsI18nExtensionDO;
+import cn.iocoder.yudao.module.guide.service.sights.SightsService;
+
+@Tag(name = "管理后台 - 观光景点基本信息")
+@RestController
+@RequestMapping("/guide/sights")
+@Validated
+public class SightsController {
+
+    @Resource
+    private SightsService sightsService;
+
+    @PostMapping("/create")
+    @Operation(summary = "创建观光景点基本信息")
+    @PreAuthorize("@ss.hasPermission('guide:sights:create')")
+    public CommonResult<Long> createSights(@Valid @RequestBody SightsSaveReqVO createReqVO) {
+        return success(sightsService.createSights(createReqVO));
+    }
+
+    @PutMapping("/update")
+    @Operation(summary = "更新观光景点基本信息")
+    @PreAuthorize("@ss.hasPermission('guide:sights:update')")
+    public CommonResult<Boolean> updateSights(@Valid @RequestBody SightsSaveReqVO updateReqVO) {
+        sightsService.updateSights(updateReqVO);
+        return success(true);
+    }
+
+    @DeleteMapping("/delete")
+    @Operation(summary = "删除观光景点基本信息")
+    @Parameter(name = "id", description = "编号", required = true)
+    @PreAuthorize("@ss.hasPermission('guide:sights:delete')")
+    public CommonResult<Boolean> deleteSights(@RequestParam("id") Long id) {
+        sightsService.deleteSights(id);
+        return success(true);
+    }
+
+    @GetMapping("/get")
+    @Operation(summary = "获得观光景点基本信息")
+    @Parameter(name = "id", description = "编号", required = true, example = "1024")
+    @PreAuthorize("@ss.hasPermission('guide:sights:query')")
+    public CommonResult<SightsRespVO> getSights(@RequestParam("id") Long id) {
+        SightsDO sights = sightsService.getSights(id);
+        return success(BeanUtils.toBean(sights, SightsRespVO.class));
+    }
+
+    @GetMapping("/page")
+    @Operation(summary = "获得观光景点基本信息分页")
+    @PreAuthorize("@ss.hasPermission('guide:sights:query')")
+    public CommonResult<PageResult<SightsRespVO>> getSightsPage(@Valid SightsPageReqVO pageReqVO) {
+        PageResult<SightsDO> pageResult = sightsService.getSightsPage(pageReqVO);
+        return success(BeanUtils.toBean(pageResult, SightsRespVO.class));
+    }
+
+    @GetMapping("/export-excel")
+    @Operation(summary = "导出观光景点基本信息 Excel")
+    @PreAuthorize("@ss.hasPermission('guide:sights:export')")
+    @ApiAccessLog(operateType = EXPORT)
+    public void exportSightsExcel(@Valid SightsPageReqVO pageReqVO,
+              HttpServletResponse response) throws IOException {
+        pageReqVO.setPageSize(PageParam.PAGE_SIZE_NONE);
+        List<SightsDO> list = sightsService.getSightsPage(pageReqVO).getList();
+        // 导出 Excel
+        ExcelUtils.write(response, "观光景点基本信息.xls", "数据", SightsRespVO.class,
+                        BeanUtils.toBean(list, SightsRespVO.class));
+    }
+
+    // ==================== 子表(观光景点对语言扩充信息) ====================
+
+    @GetMapping("/sights-i18n-extension/page")
+    @Operation(summary = "获得观光景点对语言扩充信息分页")
+    @Parameter(name = "sightsId", description = "景点")
+    @PreAuthorize("@ss.hasPermission('guide:sights:query')")
+    public CommonResult<PageResult<SightsI18nExtensionDO>> getSightsI18nExtensionPage(PageParam pageReqVO,
+                                                                                        @RequestParam("sightsId") Long sightsId) {
+        return success(sightsService.getSightsI18nExtensionPage(pageReqVO, sightsId));
+    }
+
+    @PostMapping("/sights-i18n-extension/create")
+    @Operation(summary = "创建观光景点对语言扩充信息")
+    @PreAuthorize("@ss.hasPermission('guide:sights:create')")
+    public CommonResult<Long> createSightsI18nExtension(@Valid @RequestBody SightsI18nExtensionDO sightsI18nExtension) {
+        return success(sightsService.createSightsI18nExtension(sightsI18nExtension));
+    }
+
+    @PutMapping("/sights-i18n-extension/update")
+    @Operation(summary = "更新观光景点对语言扩充信息")
+    @PreAuthorize("@ss.hasPermission('guide:sights:update')")
+    public CommonResult<Boolean> updateSightsI18nExtension(@Valid @RequestBody SightsI18nExtensionDO sightsI18nExtension) {
+        sightsService.updateSightsI18nExtension(sightsI18nExtension);
+        return success(true);
+    }
+
+    @DeleteMapping("/sights-i18n-extension/delete")
+    @Parameter(name = "id", description = "编号", required = true)
+    @Operation(summary = "删除观光景点对语言扩充信息")
+    @PreAuthorize("@ss.hasPermission('guide:sights:delete')")
+    public CommonResult<Boolean> deleteSightsI18nExtension(@RequestParam("id") Long id) {
+        sightsService.deleteSightsI18nExtension(id);
+        return success(true);
+    }
+
+	@GetMapping("/sights-i18n-extension/get")
+	@Operation(summary = "获得观光景点对语言扩充信息")
+	@Parameter(name = "id", description = "编号", required = true)
+    @PreAuthorize("@ss.hasPermission('guide:sights:query')")
+	public CommonResult<SightsI18nExtensionDO> getSightsI18nExtension(@RequestParam("id") Long id) {
+	    return success(sightsService.getSightsI18nExtension(id));
+	}
+
+}

+ 31 - 0
yudao-module-guide/yudao-module-guide-biz/src/main/java/cn/iocoder/yudao/module/guide/controller/admin/sights/vo/SightsPageReqVO.java

@@ -0,0 +1,31 @@
+package cn.iocoder.yudao.module.guide.controller.admin.sights.vo;
+
+import lombok.*;
+import java.util.*;
+import io.swagger.v3.oas.annotations.media.Schema;
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import org.springframework.format.annotation.DateTimeFormat;
+import java.time.LocalDateTime;
+
+import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
+
+@Schema(description = "管理后台 - 观光景点基本信息分页 Request VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class SightsPageReqVO extends PageParam {
+
+    @Schema(description = "景点分类数组,以逗号分隔")
+    private List<Integer> categoryIds;
+
+    @Schema(description = "电话号码")
+    private String tel;
+
+    @Schema(description = "状态: 1 上架(开启) 0 下架(禁用) -1 回收", example = "1")
+    private Integer status;
+
+    @Schema(description = "创建时间")
+    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+    private LocalDateTime[] createTime;
+
+}

+ 55 - 0
yudao-module-guide/yudao-module-guide-biz/src/main/java/cn/iocoder/yudao/module/guide/controller/admin/sights/vo/SightsRespVO.java

@@ -0,0 +1,55 @@
+package cn.iocoder.yudao.module.guide.controller.admin.sights.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.*;
+import java.util.*;
+import java.util.*;
+import org.springframework.format.annotation.DateTimeFormat;
+import java.time.LocalDateTime;
+import com.alibaba.excel.annotation.*;
+import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
+import cn.iocoder.yudao.framework.excel.core.convert.DictConvert;
+
+@Schema(description = "管理后台 - 观光景点基本信息 Response VO")
+@Data
+@ExcelIgnoreUnannotated
+public class SightsRespVO {
+
+    @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "3009")
+    @ExcelProperty("编号")
+    private Long id;
+
+    @Schema(description = "景点分类数组,以逗号分隔", requiredMode = Schema.RequiredMode.REQUIRED)
+    @ExcelProperty("景点分类数组,以逗号分隔")
+    private List<Integer> categoryIds;
+
+    @Schema(description = "景点封面图", example = "https://www.iocoder.cn")
+    @ExcelProperty("景点封面图")
+    private String picUrl;
+
+    @Schema(description = "官网URL", example = "https://www.iocoder.cn")
+    @ExcelProperty("官网URL")
+    private String url;
+
+    @Schema(description = "电话号码")
+    @ExcelProperty("电话号码")
+    private String tel;
+
+    @Schema(description = "开放时间")
+    @ExcelProperty("开放时间")
+    private String opentime;
+
+    @Schema(description = "大致游玩所需时间")
+    @ExcelProperty("大致游玩所需时间")
+    private Integer durationSightseeing;
+
+    @Schema(description = "状态: 1 上架(开启) 0 下架(禁用) -1 回收", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+    @ExcelProperty(value = "状态: 1 上架(开启) 0 下架(禁用) -1 回收", converter = DictConvert.class)
+    @DictFormat("product_spu_status") // TODO 代码优化:建议设置到对应的 DictTypeConstants 枚举类中
+    private Integer status;
+
+    @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
+    @ExcelProperty("创建时间")
+    private LocalDateTime createTime;
+
+}

+ 48 - 0
yudao-module-guide/yudao-module-guide-biz/src/main/java/cn/iocoder/yudao/module/guide/controller/admin/sights/vo/SightsSaveReqVO.java

@@ -0,0 +1,48 @@
+package cn.iocoder.yudao.module.guide.controller.admin.sights.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.*;
+import java.util.*;
+import jakarta.validation.constraints.*;
+import cn.iocoder.yudao.module.guide.dal.dataobject.sights.SightsI18nExtensionDO;
+
+@Schema(description = "管理后台 - 观光景点基本信息新增/修改 Request VO")
+@Data
+public class SightsSaveReqVO {
+
+    @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "3009")
+    private Long id;
+
+    @Schema(description = "景点分类数组,以逗号分隔", requiredMode = Schema.RequiredMode.REQUIRED)
+    @NotEmpty(message = "景点分类数组,以逗号分隔不能为空")
+    private List<Integer> categoryIds;
+
+    @Schema(description = "景点封面图", example = "https://www.iocoder.cn")
+    private String picUrl;
+
+    @Schema(description = "景点轮播图地址数组,以逗号分隔,最多10")
+    private List<String> sliderPicUrls;
+
+    @Schema(description = "官网URL", example = "https://www.iocoder.cn")
+    private String url;
+
+    @Schema(description = "电话号码")
+    private String tel;
+
+    @Schema(description = "开放时间")
+    private String opentime;
+
+    @Schema(description = "大致游玩所需时间")
+    private Integer durationSightseeing;
+
+    @Schema(description = "地理信息表ID", example = "5209")
+    private Long geographicalId;
+
+    @Schema(description = "排序")
+    private Integer sort;
+
+    @Schema(description = "状态: 1 上架(开启) 0 下架(禁用) -1 回收", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+    @NotNull(message = "状态: 1 上架(开启) 0 下架(禁用) -1 回收不能为空")
+    private Integer status;
+
+}

+ 94 - 0
yudao-module-guide/yudao-module-guide-biz/src/main/java/cn/iocoder/yudao/module/guide/controller/admin/sightscategory/SightsCategoryController.java

@@ -0,0 +1,94 @@
+package cn.iocoder.yudao.module.guide.controller.admin.sightscategory;
+
+import org.springframework.web.bind.annotation.*;
+import jakarta.annotation.Resource;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.security.access.prepost.PreAuthorize;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.Operation;
+
+import jakarta.validation.constraints.*;
+import jakarta.validation.*;
+import jakarta.servlet.http.*;
+import java.util.*;
+import java.io.IOException;
+
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+
+import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
+
+import cn.iocoder.yudao.framework.apilog.core.annotations.ApiAccessLog;
+import static cn.iocoder.yudao.framework.apilog.core.enums.OperateTypeEnum.*;
+
+import cn.iocoder.yudao.module.guide.controller.admin.sightscategory.vo.*;
+import cn.iocoder.yudao.module.guide.dal.dataobject.sightscategory.SightsCategoryDO;
+import cn.iocoder.yudao.module.guide.service.sightscategory.SightsCategoryService;
+
+@Tag(name = "管理后台 - 观光景点分类")
+@RestController
+@RequestMapping("/guide/sights-category")
+@Validated
+public class SightsCategoryController {
+
+    @Resource
+    private SightsCategoryService sightsCategoryService;
+
+    @PostMapping("/create")
+    @Operation(summary = "创建观光景点分类")
+    @PreAuthorize("@ss.hasPermission('guide:sights-category:create')")
+    public CommonResult<Long> createSightsCategory(@Valid @RequestBody SightsCategorySaveReqVO createReqVO) {
+        return success(sightsCategoryService.createSightsCategory(createReqVO));
+    }
+
+    @PutMapping("/update")
+    @Operation(summary = "更新观光景点分类")
+    @PreAuthorize("@ss.hasPermission('guide:sights-category:update')")
+    public CommonResult<Boolean> updateSightsCategory(@Valid @RequestBody SightsCategorySaveReqVO updateReqVO) {
+        sightsCategoryService.updateSightsCategory(updateReqVO);
+        return success(true);
+    }
+
+    @DeleteMapping("/delete")
+    @Operation(summary = "删除观光景点分类")
+    @Parameter(name = "id", description = "编号", required = true)
+    @PreAuthorize("@ss.hasPermission('guide:sights-category:delete')")
+    public CommonResult<Boolean> deleteSightsCategory(@RequestParam("id") Long id) {
+        sightsCategoryService.deleteSightsCategory(id);
+        return success(true);
+    }
+
+    @GetMapping("/get")
+    @Operation(summary = "获得观光景点分类")
+    @Parameter(name = "id", description = "编号", required = true, example = "1024")
+    @PreAuthorize("@ss.hasPermission('guide:sights-category:query')")
+    public CommonResult<SightsCategoryRespVO> getSightsCategory(@RequestParam("id") Long id) {
+        SightsCategoryDO sightsCategory = sightsCategoryService.getSightsCategory(id);
+        return success(BeanUtils.toBean(sightsCategory, SightsCategoryRespVO.class));
+    }
+
+    @GetMapping("/list")
+    @Operation(summary = "获得观光景点分类列表")
+    @PreAuthorize("@ss.hasPermission('guide:sights-category:query')")
+    public CommonResult<List<SightsCategoryRespVO>> getSightsCategoryList(@Valid SightsCategoryListReqVO listReqVO) {
+        List<SightsCategoryDO> list = sightsCategoryService.getSightsCategoryList(listReqVO);
+        return success(BeanUtils.toBean(list, SightsCategoryRespVO.class));
+    }
+
+    @GetMapping("/export-excel")
+    @Operation(summary = "导出观光景点分类 Excel")
+    @PreAuthorize("@ss.hasPermission('guide:sights-category:export')")
+    @ApiAccessLog(operateType = EXPORT)
+    public void exportSightsCategoryExcel(@Valid SightsCategoryListReqVO listReqVO,
+              HttpServletResponse response) throws IOException {
+        List<SightsCategoryDO> list = sightsCategoryService.getSightsCategoryList(listReqVO);
+        // 导出 Excel
+        ExcelUtils.write(response, "观光景点分类.xls", "数据", SightsCategoryRespVO.class,
+                        BeanUtils.toBean(list, SightsCategoryRespVO.class));
+    }
+
+}

+ 29 - 0
yudao-module-guide/yudao-module-guide-biz/src/main/java/cn/iocoder/yudao/module/guide/controller/admin/sightscategory/vo/SightsCategoryListReqVO.java

@@ -0,0 +1,29 @@
+package cn.iocoder.yudao.module.guide.controller.admin.sightscategory.vo;
+
+import lombok.*;
+import java.util.*;
+import io.swagger.v3.oas.annotations.media.Schema;
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import java.time.LocalDateTime;
+import org.springframework.format.annotation.DateTimeFormat;
+
+import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
+
+@Schema(description = "管理后台 - 观光景点分类列表 Request VO")
+@Data
+public class SightsCategoryListReqVO {
+
+    @Schema(description = "分类中文名称")
+    private String nameZh;
+
+    @Schema(description = "分类日文名称")
+    private String nameJa;
+
+    @Schema(description = "分类英文名称")
+    private String nameEn;
+
+    @Schema(description = "创建时间")
+    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+    private LocalDateTime[] createTime;
+
+}

+ 59 - 0
yudao-module-guide/yudao-module-guide-biz/src/main/java/cn/iocoder/yudao/module/guide/controller/admin/sightscategory/vo/SightsCategoryRespVO.java

@@ -0,0 +1,59 @@
+package cn.iocoder.yudao.module.guide.controller.admin.sightscategory.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.*;
+import java.util.*;
+import java.util.*;
+import org.springframework.format.annotation.DateTimeFormat;
+import java.time.LocalDateTime;
+import com.alibaba.excel.annotation.*;
+import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
+import cn.iocoder.yudao.framework.excel.core.convert.DictConvert;
+
+@Schema(description = "管理后台 - 观光景点分类 Response VO")
+@Data
+@ExcelIgnoreUnannotated
+public class SightsCategoryRespVO {
+
+    @Schema(description = "分类编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "23705")
+    @ExcelProperty("分类编号")
+    private Long id;
+
+    @Schema(description = "父分类编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "424")
+    @ExcelProperty("父分类编号")
+    private Long parentId;
+
+    @Schema(description = "分类中文名称", requiredMode = Schema.RequiredMode.REQUIRED)
+    @ExcelProperty("分类中文名称")
+    private String nameZh;
+
+    @Schema(description = "分类日文名称")
+    @ExcelProperty("分类日文名称")
+    private String nameJa;
+
+    @Schema(description = "分类英文名称")
+    @ExcelProperty("分类英文名称")
+    private String nameEn;
+
+    @Schema(description = "分类XX名称")
+    @ExcelProperty("分类XX名称")
+    private String nameOther;
+
+    @Schema(description = "移动端分类图", requiredMode = Schema.RequiredMode.REQUIRED, example = "https://www.iocoder.cn")
+    @ExcelProperty("移动端分类图")
+    private String picUrl;
+
+    @Schema(description = "分类排序")
+    @ExcelProperty("分类排序")
+    private Integer sort;
+
+    @Schema(description = "开启状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+    @ExcelProperty(value = "开启状态", converter = DictConvert.class)
+    @DictFormat("common_status") // TODO 代码优化:建议设置到对应的 DictTypeConstants 枚举类中
+    private Integer status;
+
+    @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
+    @ExcelProperty("创建时间")
+    private LocalDateTime createTime;
+
+}

+ 43 - 0
yudao-module-guide/yudao-module-guide-biz/src/main/java/cn/iocoder/yudao/module/guide/controller/admin/sightscategory/vo/SightsCategorySaveReqVO.java

@@ -0,0 +1,43 @@
+package cn.iocoder.yudao.module.guide.controller.admin.sightscategory.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.*;
+import java.util.*;
+import jakarta.validation.constraints.*;
+
+@Schema(description = "管理后台 - 观光景点分类新增/修改 Request VO")
+@Data
+public class SightsCategorySaveReqVO {
+
+    @Schema(description = "分类编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "23705")
+    private Long id;
+
+    @Schema(description = "父分类编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "424")
+    @NotNull(message = "父分类编号不能为空")
+    private Long parentId;
+
+    @Schema(description = "分类中文名称", requiredMode = Schema.RequiredMode.REQUIRED)
+    @NotEmpty(message = "分类中文名称不能为空")
+    private String nameZh;
+
+    @Schema(description = "分类日文名称")
+    private String nameJa;
+
+    @Schema(description = "分类英文名称")
+    private String nameEn;
+
+    @Schema(description = "分类XX名称")
+    private String nameOther;
+
+    @Schema(description = "移动端分类图", requiredMode = Schema.RequiredMode.REQUIRED, example = "https://www.iocoder.cn")
+    @NotEmpty(message = "移动端分类图不能为空")
+    private String picUrl;
+
+    @Schema(description = "分类排序")
+    private Integer sort;
+
+    @Schema(description = "开启状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+    @NotNull(message = "开启状态不能为空")
+    private Integer status;
+
+}

+ 25 - 0
yudao-module-guide/yudao-module-guide-biz/src/main/java/cn/iocoder/yudao/module/guide/controller/app/AppDemoTestController.java

@@ -0,0 +1,25 @@
+package cn.iocoder.yudao.module.guide.controller.app;
+
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import io.swagger.v3.oas.annotations.Operation;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+
+@Tag(name = "用户 App - Test")
+@RestController
+@RequestMapping("/demo/test")
+@Validated
+public class AppDemoTestController {
+
+    @GetMapping("/get")
+    @Operation(summary = "获取 test 信息")
+    public CommonResult<String> get() {
+        return success("true");
+    }
+
+}

+ 81 - 0
yudao-module-guide/yudao-module-guide-biz/src/main/java/cn/iocoder/yudao/module/guide/dal/dataobject/sights/SightsDO.java

@@ -0,0 +1,81 @@
+package cn.iocoder.yudao.module.guide.dal.dataobject.sights;
+
+import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler;
+import lombok.*;
+import java.util.*;
+import java.time.LocalDateTime;
+import java.time.LocalDateTime;
+import com.baomidou.mybatisplus.annotation.*;
+import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
+import com.baomidou.mybatisplus.annotation.KeySequence;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler;
+
+/**
+ * 观光景点基本信息 DO
+ *
+ * @author 芋道源码
+ */
+@TableName(value="guide_sights", autoResultMap = true)
+@KeySequence("guide_sights_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class SightsDO extends BaseDO {
+
+    /**
+     * 编号
+     */
+    @TableId
+    private Long id;
+    /**
+     * 景点分类数组,以逗号分隔
+     */
+    @TableField(value = "category_ids", typeHandler = JacksonTypeHandler.class)
+    private List<Integer> categoryIds;
+    /**
+     * 景点封面图
+     */
+    private String picUrl;
+    /**
+     * 景点轮播图地址数组,以逗号分隔,最多10
+     */
+    @TableField(value = "slider_pic_urls", typeHandler = JacksonTypeHandler.class)
+    private List<String> sliderPicUrls;
+    /**
+     * 官网URL
+     */
+    private String url;
+    /**
+     * 电话号码
+     */
+    private String tel;
+    /**
+     * 开放时间
+     */
+    private String opentime;
+    /**
+     * 大致游玩所需时间
+     */
+    private Integer durationSightseeing;
+    /**
+     * 地理信息表ID
+     */
+    private Long geographicalId;
+    /**
+     * 排序
+     */
+    private Integer sort;
+    /**
+     * 状态: 1 上架(开启) 0 下架(禁用) -1 回收
+     *
+     * 枚举 {@link TODO product_spu_status 对应的类}
+     */
+    private Integer status;
+
+}

+ 73 - 0
yudao-module-guide/yudao-module-guide-biz/src/main/java/cn/iocoder/yudao/module/guide/dal/dataobject/sights/SightsI18nExtensionDO.java

@@ -0,0 +1,73 @@
+package cn.iocoder.yudao.module.guide.dal.dataobject.sights;
+
+import lombok.*;
+import java.util.*;
+import java.time.LocalDateTime;
+import java.time.LocalDateTime;
+import com.baomidou.mybatisplus.annotation.*;
+import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
+
+/**
+ * 观光景点对语言扩充信息 DO
+ *
+ * @author 芋道源码
+ */
+@TableName("guide_sights_i18n_extension")
+@KeySequence("guide_sights_i18n_extension_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class SightsI18nExtensionDO extends BaseDO {
+
+    /**
+     * 编号
+     */
+    @TableId
+    private Long id;
+    /**
+     * 景点
+     */
+    private Long sightsId;
+    /**
+     * 多语言类型
+     *
+     * 枚举 {@link TODO i8n_type 对应的类}
+     */
+    private Long languageType;
+    /**
+     * 标题
+     */
+    private String title;
+    /**
+     * 副标题
+     */
+    private String subtitle;
+    /**
+     * 简介
+     */
+    private String overview;
+    /**
+     * 景点图文说明
+     */
+    private String description;
+    /**
+     * 地址
+     */
+    private String address;
+    /**
+     * 门票&预约信息()
+     */
+    private String bookingDetails;
+    /**
+     * 关键字
+     */
+    private String keywords;
+    /**
+     * 补充说明
+     */
+    private String memo;
+
+}

+ 67 - 0
yudao-module-guide/yudao-module-guide-biz/src/main/java/cn/iocoder/yudao/module/guide/dal/dataobject/sightscategory/SightsCategoryDO.java

@@ -0,0 +1,67 @@
+package cn.iocoder.yudao.module.guide.dal.dataobject.sightscategory;
+
+import lombok.*;
+import java.util.*;
+import java.time.LocalDateTime;
+import java.time.LocalDateTime;
+import com.baomidou.mybatisplus.annotation.*;
+import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
+
+/**
+ * 观光景点分类 DO
+ *
+ * @author 芋道源码
+ */
+@TableName("guide_sights_category")
+@KeySequence("guide_sights_category_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class SightsCategoryDO extends BaseDO {
+
+    public static final Long PARENT_ID_ROOT = 0L;
+
+    /**
+     * 分类编号
+     */
+    @TableId
+    private Long id;
+    /**
+     * 父分类编号
+     */
+    private Long parentId;
+    /**
+     * 分类中文名称
+     */
+    private String nameZh;
+    /**
+     * 分类日文名称
+     */
+    private String nameJa;
+    /**
+     * 分类英文名称
+     */
+    private String nameEn;
+    /**
+     * 分类XX名称
+     */
+    private String nameOther;
+    /**
+     * 移动端分类图
+     */
+    private String picUrl;
+    /**
+     * 分类排序
+     */
+    private Integer sort;
+    /**
+     * 开启状态
+     *
+     * 枚举 {@link TODO common_status 对应的类}
+     */
+    private Integer status;
+
+}

+ 30 - 0
yudao-module-guide/yudao-module-guide-biz/src/main/java/cn/iocoder/yudao/module/guide/dal/mysql/sights/SightsI18nExtensionMapper.java

@@ -0,0 +1,30 @@
+package cn.iocoder.yudao.module.guide.dal.mysql.sights;
+
+import java.util.*;
+
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
+import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
+import cn.iocoder.yudao.module.guide.dal.dataobject.sights.SightsI18nExtensionDO;
+import org.apache.ibatis.annotations.Mapper;
+
+/**
+ * 观光景点对语言扩充信息 Mapper
+ *
+ * @author 芋道源码
+ */
+@Mapper
+public interface SightsI18nExtensionMapper extends BaseMapperX<SightsI18nExtensionDO> {
+
+    default PageResult<SightsI18nExtensionDO> selectPage(PageParam reqVO, Long sightsId) {
+        return selectPage(reqVO, new LambdaQueryWrapperX<SightsI18nExtensionDO>()
+            .eq(SightsI18nExtensionDO::getSightsId, sightsId)
+            .orderByDesc(SightsI18nExtensionDO::getId));
+    }
+
+    default int deleteBySightsId(Long sightsId) {
+        return delete(SightsI18nExtensionDO::getSightsId, sightsId);
+    }
+
+}

+ 29 - 0
yudao-module-guide/yudao-module-guide-biz/src/main/java/cn/iocoder/yudao/module/guide/dal/mysql/sights/SightsMapper.java

@@ -0,0 +1,29 @@
+package cn.iocoder.yudao.module.guide.dal.mysql.sights;
+
+import java.util.*;
+
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
+import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
+import cn.iocoder.yudao.module.guide.dal.dataobject.sights.SightsDO;
+import org.apache.ibatis.annotations.Mapper;
+import cn.iocoder.yudao.module.guide.controller.admin.sights.vo.*;
+
+/**
+ * 观光景点基本信息 Mapper
+ *
+ * @author 芋道源码
+ */
+@Mapper
+public interface SightsMapper extends BaseMapperX<SightsDO> {
+
+    default PageResult<SightsDO> selectPage(SightsPageReqVO reqVO) {
+        return selectPage(reqVO, new LambdaQueryWrapperX<SightsDO>()
+                .eqIfPresent(SightsDO::getCategoryIds, reqVO.getCategoryIds())
+                .eqIfPresent(SightsDO::getTel, reqVO.getTel())
+                .eqIfPresent(SightsDO::getStatus, reqVO.getStatus())
+                .betweenIfPresent(SightsDO::getCreateTime, reqVO.getCreateTime())
+                .orderByDesc(SightsDO::getId));
+    }
+
+}

+ 37 - 0
yudao-module-guide/yudao-module-guide-biz/src/main/java/cn/iocoder/yudao/module/guide/dal/mysql/sightscategory/SightsCategoryMapper.java

@@ -0,0 +1,37 @@
+package cn.iocoder.yudao.module.guide.dal.mysql.sightscategory;
+
+import java.util.*;
+
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
+import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
+import cn.iocoder.yudao.module.guide.dal.dataobject.sightscategory.SightsCategoryDO;
+import org.apache.ibatis.annotations.Mapper;
+import cn.iocoder.yudao.module.guide.controller.admin.sightscategory.vo.*;
+
+/**
+ * 观光景点分类 Mapper
+ *
+ * @author 芋道源码
+ */
+@Mapper
+public interface SightsCategoryMapper extends BaseMapperX<SightsCategoryDO> {
+
+    default List<SightsCategoryDO> selectList(SightsCategoryListReqVO reqVO) {
+        return selectList(new LambdaQueryWrapperX<SightsCategoryDO>()
+                .eqIfPresent(SightsCategoryDO::getNameZh, reqVO.getNameZh())
+                .eqIfPresent(SightsCategoryDO::getNameJa, reqVO.getNameJa())
+                .eqIfPresent(SightsCategoryDO::getNameEn, reqVO.getNameEn())
+                .betweenIfPresent(SightsCategoryDO::getCreateTime, reqVO.getCreateTime())
+                .orderByDesc(SightsCategoryDO::getId));
+    }
+
+	default SightsCategoryDO selectByParentIdAndNameZh(Long parentId, String nameZh) {
+	    return selectOne(SightsCategoryDO::getParentId, parentId, SightsCategoryDO::getNameZh, nameZh);
+	}
+
+    default Long selectCountByParentId(Long parentId) {
+        return selectCount(SightsCategoryDO::getParentId, parentId);
+    }
+
+}

+ 97 - 0
yudao-module-guide/yudao-module-guide-biz/src/main/java/cn/iocoder/yudao/module/guide/service/sights/SightsService.java

@@ -0,0 +1,97 @@
+package cn.iocoder.yudao.module.guide.service.sights;
+
+import java.util.*;
+import jakarta.validation.*;
+import cn.iocoder.yudao.module.guide.controller.admin.sights.vo.*;
+import cn.iocoder.yudao.module.guide.dal.dataobject.sights.SightsDO;
+import cn.iocoder.yudao.module.guide.dal.dataobject.sights.SightsI18nExtensionDO;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+
+/**
+ * 观光景点基本信息 Service 接口
+ *
+ * @author 芋道源码
+ */
+public interface SightsService {
+
+    /**
+     * 创建观光景点基本信息
+     *
+     * @param createReqVO 创建信息
+     * @return 编号
+     */
+    Long createSights(@Valid SightsSaveReqVO createReqVO);
+
+    /**
+     * 更新观光景点基本信息
+     *
+     * @param updateReqVO 更新信息
+     */
+    void updateSights(@Valid SightsSaveReqVO updateReqVO);
+
+    /**
+     * 删除观光景点基本信息
+     *
+     * @param id 编号
+     */
+    void deleteSights(Long id);
+
+    /**
+     * 获得观光景点基本信息
+     *
+     * @param id 编号
+     * @return 观光景点基本信息
+     */
+    SightsDO getSights(Long id);
+
+    /**
+     * 获得观光景点基本信息分页
+     *
+     * @param pageReqVO 分页查询
+     * @return 观光景点基本信息分页
+     */
+    PageResult<SightsDO> getSightsPage(SightsPageReqVO pageReqVO);
+
+    // ==================== 子表(观光景点对语言扩充信息) ====================
+
+    /**
+     * 获得观光景点对语言扩充信息分页
+     *
+     * @param pageReqVO 分页查询
+     * @param sightsId 景点
+     * @return 观光景点对语言扩充信息分页
+     */
+    PageResult<SightsI18nExtensionDO> getSightsI18nExtensionPage(PageParam pageReqVO, Long sightsId);
+
+    /**
+     * 创建观光景点对语言扩充信息
+     *
+     * @param sightsI18nExtension 创建信息
+     * @return 编号
+     */
+    Long createSightsI18nExtension(@Valid SightsI18nExtensionDO sightsI18nExtension);
+
+    /**
+     * 更新观光景点对语言扩充信息
+     *
+     * @param sightsI18nExtension 更新信息
+     */
+    void updateSightsI18nExtension(@Valid SightsI18nExtensionDO sightsI18nExtension);
+
+    /**
+     * 删除观光景点对语言扩充信息
+     *
+     * @param id 编号
+     */
+    void deleteSightsI18nExtension(Long id);
+
+	/**
+	 * 获得观光景点对语言扩充信息
+	 *
+	 * @param id 编号
+     * @return 观光景点对语言扩充信息
+	 */
+    SightsI18nExtensionDO getSightsI18nExtension(Long id);
+
+}

+ 127 - 0
yudao-module-guide/yudao-module-guide-biz/src/main/java/cn/iocoder/yudao/module/guide/service/sights/SightsServiceImpl.java

@@ -0,0 +1,127 @@
+package cn.iocoder.yudao.module.guide.service.sights;
+
+import org.springframework.stereotype.Service;
+import jakarta.annotation.Resource;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.util.*;
+import cn.iocoder.yudao.module.guide.controller.admin.sights.vo.*;
+import cn.iocoder.yudao.module.guide.dal.dataobject.sights.SightsDO;
+import cn.iocoder.yudao.module.guide.dal.dataobject.sights.SightsI18nExtensionDO;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+
+import cn.iocoder.yudao.module.guide.dal.mysql.sights.SightsMapper;
+import cn.iocoder.yudao.module.guide.dal.mysql.sights.SightsI18nExtensionMapper;
+
+import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
+import static cn.iocoder.yudao.module.guide.enums.ErrorCodeConstants.*;
+
+/**
+ * 观光景点基本信息 Service 实现类
+ *
+ * @author 芋道源码
+ */
+@Service
+@Validated
+public class SightsServiceImpl implements SightsService {
+
+    @Resource
+    private SightsMapper sightsMapper;
+    @Resource
+    private SightsI18nExtensionMapper sightsI18nExtensionMapper;
+
+    @Override
+    public Long createSights(SightsSaveReqVO createReqVO) {
+        // 插入
+        SightsDO sights = BeanUtils.toBean(createReqVO, SightsDO.class);
+        sightsMapper.insert(sights);
+        // 返回
+        return sights.getId();
+    }
+
+    @Override
+    public void updateSights(SightsSaveReqVO updateReqVO) {
+        // 校验存在
+        validateSightsExists(updateReqVO.getId());
+        // 更新
+        SightsDO updateObj = BeanUtils.toBean(updateReqVO, SightsDO.class);
+        sightsMapper.updateById(updateObj);
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void deleteSights(Long id) {
+        // 校验存在
+        validateSightsExists(id);
+        // 删除
+        sightsMapper.deleteById(id);
+
+        // 删除子表
+        deleteSightsI18nExtensionBySightsId(id);
+    }
+
+    private void validateSightsExists(Long id) {
+        if (sightsMapper.selectById(id) == null) {
+            throw exception(SIGHTS_NOT_EXISTS);
+        }
+    }
+
+    @Override
+    public SightsDO getSights(Long id) {
+        SightsDO sights = sightsMapper.selectById(id);
+        return sights;
+    }
+
+    @Override
+    public PageResult<SightsDO> getSightsPage(SightsPageReqVO pageReqVO) {
+        return sightsMapper.selectPage(pageReqVO);
+    }
+
+    // ==================== 子表(观光景点对语言扩充信息) ====================
+
+    @Override
+    public PageResult<SightsI18nExtensionDO> getSightsI18nExtensionPage(PageParam pageReqVO, Long sightsId) {
+        return sightsI18nExtensionMapper.selectPage(pageReqVO, sightsId);
+    }
+
+    @Override
+    public Long createSightsI18nExtension(SightsI18nExtensionDO sightsI18nExtension) {
+        sightsI18nExtensionMapper.insert(sightsI18nExtension);
+        return sightsI18nExtension.getId();
+    }
+
+    @Override
+    public void updateSightsI18nExtension(SightsI18nExtensionDO sightsI18nExtension) {
+        // 校验存在
+        validateSightsI18nExtensionExists(sightsI18nExtension.getId());
+        // 更新
+        sightsI18nExtensionMapper.updateById(sightsI18nExtension);
+    }
+
+    @Override
+    public void deleteSightsI18nExtension(Long id) {
+        // 校验存在
+        validateSightsI18nExtensionExists(id);
+        // 删除
+        sightsI18nExtensionMapper.deleteById(id);
+    }
+
+    @Override
+    public SightsI18nExtensionDO getSightsI18nExtension(Long id) {
+        return sightsI18nExtensionMapper.selectById(id);
+    }
+
+    private void validateSightsI18nExtensionExists(Long id) {
+        if (sightsI18nExtensionMapper.selectById(id) == null) {
+            throw exception(SIGHTS_I18N_EXTENSION_NOT_EXISTS);
+        }
+    }
+
+    private void deleteSightsI18nExtensionBySightsId(Long sightsId) {
+        sightsI18nExtensionMapper.deleteBySightsId(sightsId);
+    }
+
+}

+ 55 - 0
yudao-module-guide/yudao-module-guide-biz/src/main/java/cn/iocoder/yudao/module/guide/service/sightscategory/SightsCategoryService.java

@@ -0,0 +1,55 @@
+package cn.iocoder.yudao.module.guide.service.sightscategory;
+
+import java.util.*;
+import jakarta.validation.*;
+import cn.iocoder.yudao.module.guide.controller.admin.sightscategory.vo.*;
+import cn.iocoder.yudao.module.guide.dal.dataobject.sightscategory.SightsCategoryDO;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+
+/**
+ * 观光景点分类 Service 接口
+ *
+ * @author 芋道源码
+ */
+public interface SightsCategoryService {
+
+    /**
+     * 创建观光景点分类
+     *
+     * @param createReqVO 创建信息
+     * @return 编号
+     */
+    Long createSightsCategory(@Valid SightsCategorySaveReqVO createReqVO);
+
+    /**
+     * 更新观光景点分类
+     *
+     * @param updateReqVO 更新信息
+     */
+    void updateSightsCategory(@Valid SightsCategorySaveReqVO updateReqVO);
+
+    /**
+     * 删除观光景点分类
+     *
+     * @param id 编号
+     */
+    void deleteSightsCategory(Long id);
+
+    /**
+     * 获得观光景点分类
+     *
+     * @param id 编号
+     * @return 观光景点分类
+     */
+    SightsCategoryDO getSightsCategory(Long id);
+
+    /**
+     * 获得观光景点分类列表
+     *
+     * @param listReqVO 查询条件
+     * @return 观光景点分类列表
+     */
+    List<SightsCategoryDO> getSightsCategoryList(SightsCategoryListReqVO listReqVO);
+
+}

+ 136 - 0
yudao-module-guide/yudao-module-guide-biz/src/main/java/cn/iocoder/yudao/module/guide/service/sightscategory/SightsCategoryServiceImpl.java

@@ -0,0 +1,136 @@
+package cn.iocoder.yudao.module.guide.service.sightscategory;
+
+import org.springframework.stereotype.Service;
+import jakarta.annotation.Resource;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.util.*;
+import cn.iocoder.yudao.module.guide.controller.admin.sightscategory.vo.*;
+import cn.iocoder.yudao.module.guide.dal.dataobject.sightscategory.SightsCategoryDO;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+
+import cn.iocoder.yudao.module.guide.dal.mysql.sightscategory.SightsCategoryMapper;
+
+import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
+import static cn.iocoder.yudao.module.guide.enums.ErrorCodeConstants.*;
+
+/**
+ * 观光景点分类 Service 实现类
+ *
+ * @author 芋道源码
+ */
+@Service
+@Validated
+public class SightsCategoryServiceImpl implements SightsCategoryService {
+
+    @Resource
+    private SightsCategoryMapper sightsCategoryMapper;
+
+    @Override
+    public Long createSightsCategory(SightsCategorySaveReqVO createReqVO) {
+        // 校验父分类编号的有效性
+        validateParentSightsCategory(null, createReqVO.getParentId());
+        // 校验分类中文名称的唯一性
+        validateSightsCategoryNameZhUnique(null, createReqVO.getParentId(), createReqVO.getNameZh());
+
+        // 插入
+        SightsCategoryDO sightsCategory = BeanUtils.toBean(createReqVO, SightsCategoryDO.class);
+        sightsCategoryMapper.insert(sightsCategory);
+        // 返回
+        return sightsCategory.getId();
+    }
+
+    @Override
+    public void updateSightsCategory(SightsCategorySaveReqVO updateReqVO) {
+        // 校验存在
+        validateSightsCategoryExists(updateReqVO.getId());
+        // 校验父分类编号的有效性
+        validateParentSightsCategory(updateReqVO.getId(), updateReqVO.getParentId());
+        // 校验分类中文名称的唯一性
+        validateSightsCategoryNameZhUnique(updateReqVO.getId(), updateReqVO.getParentId(), updateReqVO.getNameZh());
+
+        // 更新
+        SightsCategoryDO updateObj = BeanUtils.toBean(updateReqVO, SightsCategoryDO.class);
+        sightsCategoryMapper.updateById(updateObj);
+    }
+
+    @Override
+    public void deleteSightsCategory(Long id) {
+        // 校验存在
+        validateSightsCategoryExists(id);
+        // 校验是否有子观光景点分类
+        if (sightsCategoryMapper.selectCountByParentId(id) > 0) {
+            throw exception(SIGHTS_CATEGORY_EXITS_CHILDREN);
+        }
+        // 删除
+        sightsCategoryMapper.deleteById(id);
+    }
+
+    private void validateSightsCategoryExists(Long id) {
+        if (sightsCategoryMapper.selectById(id) == null) {
+            throw exception(SIGHTS_CATEGORY_NOT_EXISTS);
+        }
+    }
+
+    private void validateParentSightsCategory(Long id, Long parentId) {
+        if (parentId == null || SightsCategoryDO.PARENT_ID_ROOT.equals(parentId)) {
+            return;
+        }
+        // 1. 不能设置自己为父观光景点分类
+        if (Objects.equals(id, parentId)) {
+            throw exception(SIGHTS_CATEGORY_PARENT_ERROR);
+        }
+        // 2. 父观光景点分类不存在
+        SightsCategoryDO parentSightsCategory = sightsCategoryMapper.selectById(parentId);
+        if (parentSightsCategory == null) {
+            throw exception(SIGHTS_CATEGORY_PARENT_NOT_EXITS);
+        }
+        // 3. 递归校验父观光景点分类,如果父观光景点分类是自己的子观光景点分类,则报错,避免形成环路
+        if (id == null) { // id 为空,说明新增,不需要考虑环路
+            return;
+        }
+        for (int i = 0; i < Short.MAX_VALUE; i++) {
+            // 3.1 校验环路
+            parentId = parentSightsCategory.getParentId();
+            if (Objects.equals(id, parentId)) {
+                throw exception(SIGHTS_CATEGORY_PARENT_IS_CHILD);
+            }
+            // 3.2 继续递归下一级父观光景点分类
+            if (parentId == null || SightsCategoryDO.PARENT_ID_ROOT.equals(parentId)) {
+                break;
+            }
+            parentSightsCategory = sightsCategoryMapper.selectById(parentId);
+            if (parentSightsCategory == null) {
+                break;
+            }
+        }
+    }
+
+    private void validateSightsCategoryNameZhUnique(Long id, Long parentId, String nameZh) {
+        SightsCategoryDO sightsCategory = sightsCategoryMapper.selectByParentIdAndNameZh(parentId, nameZh);
+        if (sightsCategory == null) {
+            return;
+        }
+        // 如果 id 为空,说明不用比较是否为相同 id 的观光景点分类
+        if (id == null) {
+            throw exception(SIGHTS_CATEGORY_NAME_CN_DUPLICATE);
+        }
+        if (!Objects.equals(sightsCategory.getId(), id)) {
+            throw exception(SIGHTS_CATEGORY_NAME_CN_DUPLICATE);
+        }
+    }
+
+    @Override
+    public SightsCategoryDO getSightsCategory(Long id) {
+        return sightsCategoryMapper.selectById(id);
+    }
+
+    @Override
+    public List<SightsCategoryDO> getSightsCategoryList(SightsCategoryListReqVO listReqVO) {
+        return sightsCategoryMapper.selectList(listReqVO);
+    }
+
+}

+ 12 - 0
yudao-module-guide/yudao-module-guide-biz/src/main/resources/mapper/sights/SightsMapper.xml

@@ -0,0 +1,12 @@
+<?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.iocoder.yudao.module.guide.dal.mysql.sights.SightsMapper">
+
+    <!--
+        一般情况下,尽可能使用 Mapper 进行 CRUD 增删改查即可。
+        无法满足的场景,例如说多表关联查询,才使用 XML 编写 SQL。
+        代码生成器暂时只生成 Mapper XML 文件本身,更多推荐 MybatisX 快速开发插件来生成查询。
+        文档可见:https://www.iocoder.cn/MyBatis/x-plugins/
+     -->
+
+</mapper>

+ 12 - 0
yudao-module-guide/yudao-module-guide-biz/src/main/resources/mapper/sightscategory/SightsCategoryMapper.xml

@@ -0,0 +1,12 @@
+<?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.iocoder.yudao.module.guide.dal.mysql.sightscategory.SightsCategoryMapper">
+
+    <!--
+        一般情况下,尽可能使用 Mapper 进行 CRUD 增删改查即可。
+        无法满足的场景,例如说多表关联查询,才使用 XML 编写 SQL。
+        代码生成器暂时只生成 Mapper XML 文件本身,更多推荐 MybatisX 快速开发插件来生成查询。
+        文档可见:https://www.iocoder.cn/MyBatis/x-plugins/
+     -->
+
+</mapper>

+ 142 - 0
yudao-module-guide/yudao-module-guide-biz/src/test/java/cn/iocoder/yudao/module/guide/service/sights/SightsServiceImplTest.java

@@ -0,0 +1,142 @@
+package cn.iocoder.yudao.module.guide.service.sights;
+
+import org.junit.jupiter.api.Disabled;
+import org.junit.jupiter.api.Test;
+import org.springframework.boot.test.mock.mockito.MockBean;
+
+import jakarta.annotation.Resource;
+
+import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest;
+
+import cn.iocoder.yudao.module.guide.controller.admin.sights.vo.*;
+import cn.iocoder.yudao.module.guide.dal.dataobject.sights.SightsDO;
+import cn.iocoder.yudao.module.guide.dal.mysql.sights.SightsMapper;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+
+import jakarta.annotation.Resource;
+import org.springframework.context.annotation.Import;
+import java.util.*;
+import java.time.LocalDateTime;
+
+import static cn.hutool.core.util.RandomUtil.*;
+import static cn.iocoder.yudao.module.guide.enums.ErrorCodeConstants.*;
+import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.*;
+import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.*;
+import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.*;
+import static cn.iocoder.yudao.framework.common.util.object.ObjectUtils.*;
+import static cn.iocoder.yudao.framework.common.util.date.DateUtils.*;
+import static org.junit.jupiter.api.Assertions.*;
+import static org.mockito.Mockito.*;
+
+/**
+ * {@link SightsServiceImpl} 的单元测试类
+ *
+ * @author 芋道源码
+ */
+@Import(SightsServiceImpl.class)
+public class SightsServiceImplTest extends BaseDbUnitTest {
+
+    @Resource
+    private SightsServiceImpl sightsService;
+
+    @Resource
+    private SightsMapper sightsMapper;
+
+    @Test
+    public void testCreateSights_success() {
+        // 准备参数
+        SightsSaveReqVO createReqVO = randomPojo(SightsSaveReqVO.class).setId(null);
+
+        // 调用
+        Long sightsId = sightsService.createSights(createReqVO);
+        // 断言
+        assertNotNull(sightsId);
+        // 校验记录的属性是否正确
+        SightsDO sights = sightsMapper.selectById(sightsId);
+        assertPojoEquals(createReqVO, sights, "id");
+    }
+
+    @Test
+    public void testUpdateSights_success() {
+        // mock 数据
+        SightsDO dbSights = randomPojo(SightsDO.class);
+        sightsMapper.insert(dbSights);// @Sql: 先插入出一条存在的数据
+        // 准备参数
+        SightsSaveReqVO updateReqVO = randomPojo(SightsSaveReqVO.class, o -> {
+            o.setId(dbSights.getId()); // 设置更新的 ID
+        });
+
+        // 调用
+        sightsService.updateSights(updateReqVO);
+        // 校验是否更新正确
+        SightsDO sights = sightsMapper.selectById(updateReqVO.getId()); // 获取最新的
+        assertPojoEquals(updateReqVO, sights);
+    }
+
+    @Test
+    public void testUpdateSights_notExists() {
+        // 准备参数
+        SightsSaveReqVO updateReqVO = randomPojo(SightsSaveReqVO.class);
+
+        // 调用, 并断言异常
+        assertServiceException(() -> sightsService.updateSights(updateReqVO), SIGHTS_NOT_EXISTS);
+    }
+
+    @Test
+    public void testDeleteSights_success() {
+        // mock 数据
+        SightsDO dbSights = randomPojo(SightsDO.class);
+        sightsMapper.insert(dbSights);// @Sql: 先插入出一条存在的数据
+        // 准备参数
+        Long id = dbSights.getId();
+
+        // 调用
+        sightsService.deleteSights(id);
+       // 校验数据不存在了
+       assertNull(sightsMapper.selectById(id));
+    }
+
+    @Test
+    public void testDeleteSights_notExists() {
+        // 准备参数
+        Long id = randomLongId();
+
+        // 调用, 并断言异常
+        assertServiceException(() -> sightsService.deleteSights(id), SIGHTS_NOT_EXISTS);
+    }
+
+    @Test
+    @Disabled  // TODO 请修改 null 为需要的值,然后删除 @Disabled 注解
+    public void testGetSightsPage() {
+       // mock 数据
+       SightsDO dbSights = randomPojo(SightsDO.class, o -> { // 等会查询到
+           o.setCategoryIds(null);
+           o.setTel(null);
+           o.setStatus(null);
+           o.setCreateTime(null);
+       });
+       sightsMapper.insert(dbSights);
+       // 测试 categoryIds 不匹配
+       sightsMapper.insert(cloneIgnoreId(dbSights, o -> o.setCategoryIds(null)));
+       // 测试 tel 不匹配
+       sightsMapper.insert(cloneIgnoreId(dbSights, o -> o.setTel(null)));
+       // 测试 status 不匹配
+       sightsMapper.insert(cloneIgnoreId(dbSights, o -> o.setStatus(null)));
+       // 测试 createTime 不匹配
+       sightsMapper.insert(cloneIgnoreId(dbSights, o -> o.setCreateTime(null)));
+       // 准备参数
+       SightsPageReqVO reqVO = new SightsPageReqVO();
+       reqVO.setCategoryIds(null);
+       reqVO.setTel(null);
+       reqVO.setStatus(null);
+       reqVO.setCreateTime(buildBetweenTime(2023, 2, 1, 2023, 2, 28));
+
+       // 调用
+       PageResult<SightsDO> pageResult = sightsService.getSightsPage(reqVO);
+       // 断言
+       assertEquals(1, pageResult.getTotal());
+       assertEquals(1, pageResult.getList().size());
+       assertPojoEquals(dbSights, pageResult.getList().get(0));
+    }
+
+}

+ 134 - 0
yudao-module-guide/yudao-module-guide-biz/src/test/java/cn/iocoder/yudao/module/guide/service/sights4comment/Sights4commentServiceImplTest.java

@@ -0,0 +1,134 @@
+package cn.iocoder.yudao.module.guide.service.sights4comment;
+
+import org.junit.jupiter.api.Disabled;
+import org.junit.jupiter.api.Test;
+import org.springframework.boot.test.mock.mockito.MockBean;
+
+import jakarta.annotation.Resource;
+
+import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest;
+
+import cn.iocoder.yudao.module.guide.controller.admin.sights4comment.vo.*;
+import cn.iocoder.yudao.module.guide.dal.dataobject.sights4comment.Sights4commentDO;
+import cn.iocoder.yudao.module.guide.dal.mysql.sights4comment.Sights4commentMapper;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+
+import jakarta.annotation.Resource;
+import org.springframework.context.annotation.Import;
+import java.util.*;
+import java.time.LocalDateTime;
+
+import static cn.hutool.core.util.RandomUtil.*;
+import static cn.iocoder.yudao.module.guide.enums.ErrorCodeConstants.*;
+import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.*;
+import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.*;
+import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.*;
+import static cn.iocoder.yudao.framework.common.util.object.ObjectUtils.*;
+import static cn.iocoder.yudao.framework.common.util.date.DateUtils.*;
+import static org.junit.jupiter.api.Assertions.*;
+import static org.mockito.Mockito.*;
+
+/**
+ * {@link Sights4commentServiceImpl} 的单元测试类
+ *
+ * @author 芋道源码
+ */
+@Import(Sights4commentServiceImpl.class)
+public class Sights4commentServiceImplTest extends BaseDbUnitTest {
+
+    @Resource
+    private Sights4commentServiceImpl sights4commentService;
+
+    @Resource
+    private Sights4commentMapper sights4commentMapper;
+
+    @Test
+    public void testCreateSights4comment_success() {
+        // 准备参数
+        Sights4commentSaveReqVO createReqVO = randomPojo(Sights4commentSaveReqVO.class).setId(null);
+
+        // 调用
+        Long sights4commentId = sights4commentService.createSights4comment(createReqVO);
+        // 断言
+        assertNotNull(sights4commentId);
+        // 校验记录的属性是否正确
+        Sights4commentDO sights4comment = sights4commentMapper.selectById(sights4commentId);
+        assertPojoEquals(createReqVO, sights4comment, "id");
+    }
+
+    @Test
+    public void testUpdateSights4comment_success() {
+        // mock 数据
+        Sights4commentDO dbSights4comment = randomPojo(Sights4commentDO.class);
+        sights4commentMapper.insert(dbSights4comment);// @Sql: 先插入出一条存在的数据
+        // 准备参数
+        Sights4commentSaveReqVO updateReqVO = randomPojo(Sights4commentSaveReqVO.class, o -> {
+            o.setId(dbSights4comment.getId()); // 设置更新的 ID
+        });
+
+        // 调用
+        sights4commentService.updateSights4comment(updateReqVO);
+        // 校验是否更新正确
+        Sights4commentDO sights4comment = sights4commentMapper.selectById(updateReqVO.getId()); // 获取最新的
+        assertPojoEquals(updateReqVO, sights4comment);
+    }
+
+    @Test
+    public void testUpdateSights4comment_notExists() {
+        // 准备参数
+        Sights4commentSaveReqVO updateReqVO = randomPojo(Sights4commentSaveReqVO.class);
+
+        // 调用, 并断言异常
+        assertServiceException(() -> sights4commentService.updateSights4comment(updateReqVO), SIGHTS4COMMENT_NOT_EXISTS);
+    }
+
+    @Test
+    public void testDeleteSights4comment_success() {
+        // mock 数据
+        Sights4commentDO dbSights4comment = randomPojo(Sights4commentDO.class);
+        sights4commentMapper.insert(dbSights4comment);// @Sql: 先插入出一条存在的数据
+        // 准备参数
+        Long id = dbSights4comment.getId();
+
+        // 调用
+        sights4commentService.deleteSights4comment(id);
+       // 校验数据不存在了
+       assertNull(sights4commentMapper.selectById(id));
+    }
+
+    @Test
+    public void testDeleteSights4comment_notExists() {
+        // 准备参数
+        Long id = randomLongId();
+
+        // 调用, 并断言异常
+        assertServiceException(() -> sights4commentService.deleteSights4comment(id), SIGHTS4COMMENT_NOT_EXISTS);
+    }
+
+    @Test
+    @Disabled  // TODO 请修改 null 为需要的值,然后删除 @Disabled 注解
+    public void testGetSights4commentPage() {
+       // mock 数据
+       Sights4commentDO dbSights4comment = randomPojo(Sights4commentDO.class, o -> { // 等会查询到
+           o.setCategoryIds(null);
+           o.setCreateTime(null);
+       });
+       sights4commentMapper.insert(dbSights4comment);
+       // 测试 categoryIds 不匹配
+       sights4commentMapper.insert(cloneIgnoreId(dbSights4comment, o -> o.setCategoryIds(null)));
+       // 测试 createTime 不匹配
+       sights4commentMapper.insert(cloneIgnoreId(dbSights4comment, o -> o.setCreateTime(null)));
+       // 准备参数
+       Sights4commentPageReqVO reqVO = new Sights4commentPageReqVO();
+       reqVO.setCategoryIds(null);
+       reqVO.setCreateTime(buildBetweenTime(2023, 2, 1, 2023, 2, 28));
+
+       // 调用
+       PageResult<Sights4commentDO> pageResult = sights4commentService.getSights4commentPage(reqVO);
+       // 断言
+       assertEquals(1, pageResult.getTotal());
+       assertEquals(1, pageResult.getList().size());
+       assertPojoEquals(dbSights4comment, pageResult.getList().get(0));
+    }
+
+}

+ 141 - 0
yudao-module-guide/yudao-module-guide-biz/src/test/java/cn/iocoder/yudao/module/guide/service/sightscategory/SightsCategoryServiceImplTest.java

@@ -0,0 +1,141 @@
+package cn.iocoder.yudao.module.guide.service.sightscategory;
+
+import org.junit.jupiter.api.Disabled;
+import org.junit.jupiter.api.Test;
+import org.springframework.boot.test.mock.mockito.MockBean;
+
+import jakarta.annotation.Resource;
+
+import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest;
+
+import cn.iocoder.yudao.module.guide.controller.admin.sightscategory.vo.*;
+import cn.iocoder.yudao.module.guide.dal.dataobject.sightscategory.SightsCategoryDO;
+import cn.iocoder.yudao.module.guide.dal.mysql.sightscategory.SightsCategoryMapper;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+
+import jakarta.annotation.Resource;
+import org.springframework.context.annotation.Import;
+import java.util.*;
+import java.time.LocalDateTime;
+
+import static cn.hutool.core.util.RandomUtil.*;
+import static cn.iocoder.yudao.module.guide.enums.ErrorCodeConstants.*;
+import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.*;
+import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.*;
+import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.*;
+import static cn.iocoder.yudao.framework.common.util.object.ObjectUtils.*;
+import static cn.iocoder.yudao.framework.common.util.date.DateUtils.*;
+import static org.junit.jupiter.api.Assertions.*;
+import static org.mockito.Mockito.*;
+
+/**
+ * {@link SightsCategoryServiceImpl} 的单元测试类
+ *
+ * @author 芋道源码
+ */
+@Import(SightsCategoryServiceImpl.class)
+public class SightsCategoryServiceImplTest extends BaseDbUnitTest {
+
+    @Resource
+    private SightsCategoryServiceImpl sightsCategoryService;
+
+    @Resource
+    private SightsCategoryMapper sightsCategoryMapper;
+
+    @Test
+    public void testCreateSightsCategory_success() {
+        // 准备参数
+        SightsCategorySaveReqVO createReqVO = randomPojo(SightsCategorySaveReqVO.class).setId(null);
+
+        // 调用
+        Long sightsCategoryId = sightsCategoryService.createSightsCategory(createReqVO);
+        // 断言
+        assertNotNull(sightsCategoryId);
+        // 校验记录的属性是否正确
+        SightsCategoryDO sightsCategory = sightsCategoryMapper.selectById(sightsCategoryId);
+        assertPojoEquals(createReqVO, sightsCategory, "id");
+    }
+
+    @Test
+    public void testUpdateSightsCategory_success() {
+        // mock 数据
+        SightsCategoryDO dbSightsCategory = randomPojo(SightsCategoryDO.class);
+        sightsCategoryMapper.insert(dbSightsCategory);// @Sql: 先插入出一条存在的数据
+        // 准备参数
+        SightsCategorySaveReqVO updateReqVO = randomPojo(SightsCategorySaveReqVO.class, o -> {
+            o.setId(dbSightsCategory.getId()); // 设置更新的 ID
+        });
+
+        // 调用
+        sightsCategoryService.updateSightsCategory(updateReqVO);
+        // 校验是否更新正确
+        SightsCategoryDO sightsCategory = sightsCategoryMapper.selectById(updateReqVO.getId()); // 获取最新的
+        assertPojoEquals(updateReqVO, sightsCategory);
+    }
+
+    @Test
+    public void testUpdateSightsCategory_notExists() {
+        // 准备参数
+        SightsCategorySaveReqVO updateReqVO = randomPojo(SightsCategorySaveReqVO.class);
+
+        // 调用, 并断言异常
+        assertServiceException(() -> sightsCategoryService.updateSightsCategory(updateReqVO), SIGHTS_CATEGORY_NOT_EXISTS);
+    }
+
+    @Test
+    public void testDeleteSightsCategory_success() {
+        // mock 数据
+        SightsCategoryDO dbSightsCategory = randomPojo(SightsCategoryDO.class);
+        sightsCategoryMapper.insert(dbSightsCategory);// @Sql: 先插入出一条存在的数据
+        // 准备参数
+        Long id = dbSightsCategory.getId();
+
+        // 调用
+        sightsCategoryService.deleteSightsCategory(id);
+       // 校验数据不存在了
+       assertNull(sightsCategoryMapper.selectById(id));
+    }
+
+    @Test
+    public void testDeleteSightsCategory_notExists() {
+        // 准备参数
+        Long id = randomLongId();
+
+        // 调用, 并断言异常
+        assertServiceException(() -> sightsCategoryService.deleteSightsCategory(id), SIGHTS_CATEGORY_NOT_EXISTS);
+    }
+
+    @Test
+    @Disabled  // TODO 请修改 null 为需要的值,然后删除 @Disabled 注解
+    public void testGetSightsCategoryList() {
+       // mock 数据
+       SightsCategoryDO dbSightsCategory = randomPojo(SightsCategoryDO.class, o -> { // 等会查询到
+           o.setNameZh(null);
+           o.setNameJp(null);
+           o.setNameEn(null);
+           o.setCreateTime(null);
+       });
+       sightsCategoryMapper.insert(dbSightsCategory);
+       // 测试 nameZh 不匹配
+       sightsCategoryMapper.insert(cloneIgnoreId(dbSightsCategory, o -> o.setNameZh(null)));
+       // 测试 nameJa 不匹配
+       sightsCategoryMapper.insert(cloneIgnoreId(dbSightsCategory, o -> o.setNameJp(null)));
+       // 测试 nameEn 不匹配
+       sightsCategoryMapper.insert(cloneIgnoreId(dbSightsCategory, o -> o.setNameEn(null)));
+       // 测试 createTime 不匹配
+       sightsCategoryMapper.insert(cloneIgnoreId(dbSightsCategory, o -> o.setCreateTime(null)));
+       // 准备参数
+       SightsCategoryListReqVO reqVO = new SightsCategoryListReqVO();
+       reqVO.setNameZh(null);
+       reqVO.setNameJp(null);
+       reqVO.setNameEn(null);
+       reqVO.setCreateTime(buildBetweenTime(2023, 2, 1, 2023, 2, 28));
+
+       // 调用
+       List<SightsCategoryDO> list = sightsCategoryService.getSightsCategoryList(reqVO);
+       // 断言
+       assertEquals(1, list.size());
+       assertPojoEquals(dbSightsCategory, list.get(0));
+    }
+
+}

+ 142 - 0
yudao-module-guide/yudao-module-guide-biz/src/test/java/cn/iocoder/yudao/module/guide/service/sightscomment/SightsCommentServiceImplTest.java

@@ -0,0 +1,142 @@
+package cn.iocoder.yudao.module.guide.service.sightscomment;
+
+import org.junit.jupiter.api.Disabled;
+import org.junit.jupiter.api.Test;
+import org.springframework.boot.test.mock.mockito.MockBean;
+
+import jakarta.annotation.Resource;
+
+import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest;
+
+import cn.iocoder.yudao.module.guide.controller.admin.sightscomment.vo.*;
+import cn.iocoder.yudao.module.guide.dal.dataobject.sightscomment.SightsCommentDO;
+import cn.iocoder.yudao.module.guide.dal.mysql.sightscomment.SightsCommentMapper;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+
+import jakarta.annotation.Resource;
+import org.springframework.context.annotation.Import;
+import java.util.*;
+import java.time.LocalDateTime;
+
+import static cn.hutool.core.util.RandomUtil.*;
+import static cn.iocoder.yudao.module.guide.enums.ErrorCodeConstants.*;
+import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.*;
+import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.*;
+import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.*;
+import static cn.iocoder.yudao.framework.common.util.object.ObjectUtils.*;
+import static cn.iocoder.yudao.framework.common.util.date.DateUtils.*;
+import static org.junit.jupiter.api.Assertions.*;
+import static org.mockito.Mockito.*;
+
+/**
+ * {@link SightsCommentServiceImpl} 的单元测试类
+ *
+ * @author 芋道源码
+ */
+@Import(SightsCommentServiceImpl.class)
+public class SightsCommentServiceImplTest extends BaseDbUnitTest {
+
+    @Resource
+    private SightsCommentServiceImpl sightsCommentService;
+
+    @Resource
+    private SightsCommentMapper sightsCommentMapper;
+
+    @Test
+    public void testCreateSightsComment_success() {
+        // 准备参数
+        SightsCommentSaveReqVO createReqVO = randomPojo(SightsCommentSaveReqVO.class).setId(null);
+
+        // 调用
+        Long sightsCommentId = sightsCommentService.createSightsComment(createReqVO);
+        // 断言
+        assertNotNull(sightsCommentId);
+        // 校验记录的属性是否正确
+        SightsCommentDO sightsComment = sightsCommentMapper.selectById(sightsCommentId);
+        assertPojoEquals(createReqVO, sightsComment, "id");
+    }
+
+    @Test
+    public void testUpdateSightsComment_success() {
+        // mock 数据
+        SightsCommentDO dbSightsComment = randomPojo(SightsCommentDO.class);
+        sightsCommentMapper.insert(dbSightsComment);// @Sql: 先插入出一条存在的数据
+        // 准备参数
+        SightsCommentSaveReqVO updateReqVO = randomPojo(SightsCommentSaveReqVO.class, o -> {
+            o.setId(dbSightsComment.getId()); // 设置更新的 ID
+        });
+
+        // 调用
+        sightsCommentService.updateSightsComment(updateReqVO);
+        // 校验是否更新正确
+        SightsCommentDO sightsComment = sightsCommentMapper.selectById(updateReqVO.getId()); // 获取最新的
+        assertPojoEquals(updateReqVO, sightsComment);
+    }
+
+    @Test
+    public void testUpdateSightsComment_notExists() {
+        // 准备参数
+        SightsCommentSaveReqVO updateReqVO = randomPojo(SightsCommentSaveReqVO.class);
+
+        // 调用, 并断言异常
+        assertServiceException(() -> sightsCommentService.updateSightsComment(updateReqVO), SIGHTS_COMMENT_NOT_EXISTS);
+    }
+
+    @Test
+    public void testDeleteSightsComment_success() {
+        // mock 数据
+        SightsCommentDO dbSightsComment = randomPojo(SightsCommentDO.class);
+        sightsCommentMapper.insert(dbSightsComment);// @Sql: 先插入出一条存在的数据
+        // 准备参数
+        Long id = dbSightsComment.getId();
+
+        // 调用
+        sightsCommentService.deleteSightsComment(id);
+       // 校验数据不存在了
+       assertNull(sightsCommentMapper.selectById(id));
+    }
+
+    @Test
+    public void testDeleteSightsComment_notExists() {
+        // 准备参数
+        Long id = randomLongId();
+
+        // 调用, 并断言异常
+        assertServiceException(() -> sightsCommentService.deleteSightsComment(id), SIGHTS_COMMENT_NOT_EXISTS);
+    }
+
+    @Test
+    @Disabled  // TODO 请修改 null 为需要的值,然后删除 @Disabled 注解
+    public void testGetSightsCommentPage() {
+       // mock 数据
+       SightsCommentDO dbSightsComment = randomPojo(SightsCommentDO.class, o -> { // 等会查询到
+           o.setUserNickname(null);
+           o.setSightsTitle(null);
+           o.setScores(null);
+           o.setCreateTime(null);
+       });
+       sightsCommentMapper.insert(dbSightsComment);
+       // 测试 userNickname 不匹配
+       sightsCommentMapper.insert(cloneIgnoreId(dbSightsComment, o -> o.setUserNickname(null)));
+       // 测试 sightsTitle 不匹配
+       sightsCommentMapper.insert(cloneIgnoreId(dbSightsComment, o -> o.setSightsTitle(null)));
+       // 测试 scores 不匹配
+       sightsCommentMapper.insert(cloneIgnoreId(dbSightsComment, o -> o.setScores(null)));
+       // 测试 createTime 不匹配
+       sightsCommentMapper.insert(cloneIgnoreId(dbSightsComment, o -> o.setCreateTime(null)));
+       // 准备参数
+       SightsCommentPageReqVO reqVO = new SightsCommentPageReqVO();
+       reqVO.setUserNickname(null);
+       reqVO.setSightsTitle(null);
+       reqVO.setScores(null);
+       reqVO.setCreateTime(buildBetweenTime(2023, 2, 1, 2023, 2, 28));
+
+       // 调用
+       PageResult<SightsCommentDO> pageResult = sightsCommentService.getSightsCommentPage(reqVO);
+       // 断言
+       assertEquals(1, pageResult.getTotal());
+       assertEquals(1, pageResult.getList().size());
+       assertPojoEquals(dbSightsComment, pageResult.getList().get(0));
+    }
+
+}

+ 55 - 0
yudao-module-guide/yudao-module-guide-biz/src/test/resources/application-unit-test.yaml

@@ -0,0 +1,55 @@
+spring:
+  main:
+    lazy-initialization: true # 开启懒加载,加快速度
+    banner-mode: off # 单元测试,禁用 Banner
+
+--- #################### 数据库相关配置 ####################
+
+spring:
+  # 数据源配置项
+  datasource:
+    name: ruoyi-vue-pro
+    url: jdbc:h2:mem:testdb;MODE=MYSQL;DATABASE_TO_UPPER=false;NON_KEYWORDS=value; # MODE 使用 MySQL 模式;DATABASE_TO_UPPER 配置表和字段使用小写
+    driver-class-name: org.h2.Driver
+    username: sa
+    password:
+    druid:
+      async-init: true # 单元测试,异步初始化 Druid 连接池,提升启动速度
+      initial-size: 1 # 单元测试,配置为 1,提升启动速度
+  sql:
+    init:
+      schema-locations: classpath:/sql/create_tables.sql
+
+  # Redis 配置。Redisson 默认的配置足够使用,一般不需要进行调优
+  data:
+    redis:
+      host: 127.0.0.1 # 地址
+      port: 16379 # 端口(单元测试,使用 16379 端口)
+      database: 0 # 数据库索引
+
+mybatis:
+  lazy-initialization: true # 单元测试,设置 MyBatis Mapper 延迟加载,加速每个单元测试
+
+--- #################### 定时任务相关配置 ####################
+
+--- #################### 配置中心相关配置 ####################
+
+--- #################### 服务保障相关配置 ####################
+
+# Lock4j 配置项(单元测试,禁用 Lock4j)
+
+# Resilience4j 配置项
+
+--- #################### 监控相关配置 ####################
+
+--- #################### 芋道相关配置 ####################
+
+# 芋道配置项,设置当前项目所有自定义的配置
+yudao:
+  info:
+    base-package: cn.iocoder.yudao.module
+  captcha:
+    timeout: 5m
+    width: 160
+    height: 60
+    enable: true

+ 4 - 0
yudao-module-guide/yudao-module-guide-biz/src/test/resources/logback.xml

@@ -0,0 +1,4 @@
+<configuration>
+    <!-- 引用 Spring Boot 的 logback 基础配置 -->
+    <include resource="org/springframework/boot/logging/logback/defaults.xml" />
+</configuration>

+ 5 - 0
yudao-module-guide/yudao-module-guide-biz/src/test/resources/sql/clean.sql

@@ -0,0 +1,5 @@
+DELETE FROM "guide_sights_category";
+
+DELETE FROM "guide_sights";
+
+DELETE FROM "guide_sights_comment";

+ 69 - 0
yudao-module-guide/yudao-module-guide-biz/src/test/resources/sql/create_tables.sql

@@ -0,0 +1,69 @@
+CREATE TABLE IF NOT EXISTS "guide_sights_category" (
+                                                       "id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY,
+                                                       "parent_id" bigint NOT NULL,
+                                                       "name_cn" varchar NOT NULL,
+                                                       "name_jp" varchar,
+                                                       "name_en" varchar,
+                                                       "name_other" varchar,
+                                                       "pic_url" varchar NOT NULL,
+                                                       "sort" int,
+                                                       "status" int NOT NULL,
+                                                       "creator" varchar DEFAULT '',
+                                                       "create_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
+                                                       "updater" varchar DEFAULT '',
+                                                       "update_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+                                                       "deleted" bit NOT NULL DEFAULT FALSE,
+                                                       "tenant_id" bigint NOT NULL,
+                                                       PRIMARY KEY ("id")
+    ) COMMENT '观光景点分类';
+
+CREATE TABLE IF NOT EXISTS "guide_sights" (
+                                              "id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY,
+                                              "category_ids" varchar NOT NULL,
+                                              "pic_url" varchar,
+                                              "slider_pic_urls" varchar,
+                                              "url" varchar,
+                                              "tel" varchar,
+                                              "opentime" varchar,
+                                              "duration_sightseeing" int,
+                                              "geographical_id" bigint,
+                                              "sort" int,
+                                              "status" int NOT NULL,
+                                              "creator" varchar DEFAULT '',
+                                              "create_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
+                                              "updater" varchar DEFAULT '',
+                                              "update_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+                                              "deleted" bit NOT NULL DEFAULT FALSE,
+                                              "tenant_id" bigint NOT NULL,
+                                              PRIMARY KEY ("id")
+    ) COMMENT '观光景点基本信息';
+
+CREATE TABLE IF NOT EXISTS "guide_sights_comment" (
+                                                      "id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY,
+                                                      "user_id" bigint,
+                                                      "user_nickname" varchar,
+                                                      "user_avatar" varchar,
+                                                      "anonymous" bit NOT NULL,
+                                                      "sights_id" bigint NOT NULL,
+                                                      "sights_title" varchar,
+                                                      "visible" bit,
+                                                      "scores" int NOT NULL,
+                                                      "scenery_scores" int NOT NULL,
+                                                      "benefit_scores" int NOT NULL,
+                                                      "content" varchar NOT NULL,
+                                                      "duration_tour" varchar,
+                                                      "like_count"" int NOT NULL,
+                                                      "pic_urls" varchar,
+    "reply_status" bit,
+    "reply_user_id" bigint,
+    "reply_content" varchar,
+    "reply_time" varchar,
+    "creator" varchar DEFAULT '',
+    "create_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
+    "updater" varchar DEFAULT '',
+    "update_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+    "deleted" bit NOT NULL DEFAULT FALSE,
+    "tenant_id" bigint NOT NULL,
+    PRIMARY KEY ("id")
+) COMMENT '观光景点评论';
+

+ 35 - 31
yudao-server/pom.xml

@@ -31,13 +31,17 @@
             <artifactId>yudao-module-infra-biz</artifactId>
             <version>${revision}</version>
         </dependency>
-
+        <dependency>
+            <groupId>cn.iocoder.boot</groupId>
+            <artifactId>yudao-module-guide-biz</artifactId>
+            <version>${revision}</version>
+        </dependency>
         <!-- 会员中心。默认注释,保证编译速度 -->
-<!--        <dependency>-->
-<!--            <groupId>cn.iocoder.boot</groupId>-->
-<!--            <artifactId>yudao-module-member-biz</artifactId>-->
-<!--            <version>${revision}</version>-->
-<!--        </dependency>-->
+        <dependency>
+            <groupId>cn.iocoder.boot</groupId>
+            <artifactId>yudao-module-member-biz</artifactId>
+            <version>${revision}</version>
+        </dependency>
 
         <!-- 数据报表。默认注释,保证编译速度 -->
 <!--        <dependency>-->
@@ -52,11 +56,11 @@
 <!--            <version>${revision}</version>-->
 <!--        </dependency>-->
         <!-- 支付服务。默认注释,保证编译速度 -->
-<!--        <dependency>-->
-<!--            <groupId>cn.iocoder.boot</groupId>-->
-<!--            <artifactId>yudao-module-pay-biz</artifactId>-->
-<!--            <version>${revision}</version>-->
-<!--        </dependency>-->
+        <dependency>
+            <groupId>cn.iocoder.boot</groupId>
+            <artifactId>yudao-module-pay-biz</artifactId>
+            <version>${revision}</version>
+        </dependency>
 
         <!-- 微信公众号模块。默认注释,保证编译速度 -->
 <!--        <dependency>-->
@@ -66,26 +70,26 @@
 <!--        </dependency>-->
 
         <!-- 商城相关模块。默认注释,保证编译速度-->
-<!--        <dependency>-->
-<!--            <groupId>cn.iocoder.boot</groupId>-->
-<!--            <artifactId>yudao-module-promotion-biz</artifactId>-->
-<!--            <version>${revision}</version>-->
-<!--        </dependency>-->
-<!--        <dependency>-->
-<!--            <groupId>cn.iocoder.boot</groupId>-->
-<!--            <artifactId>yudao-module-product-biz</artifactId>-->
-<!--            <version>${revision}</version>-->
-<!--        </dependency>-->
-<!--        <dependency>-->
-<!--            <groupId>cn.iocoder.boot</groupId>-->
-<!--            <artifactId>yudao-module-trade-biz</artifactId>-->
-<!--            <version>${revision}</version>-->
-<!--        </dependency>-->
-<!--        <dependency>-->
-<!--            <groupId>cn.iocoder.boot</groupId>-->
-<!--            <artifactId>yudao-module-statistics-biz</artifactId>-->
-<!--            <version>${revision}</version>-->
-<!--        </dependency>-->
+        <dependency>
+            <groupId>cn.iocoder.boot</groupId>
+            <artifactId>yudao-module-promotion-biz</artifactId>
+            <version>${revision}</version>
+        </dependency>
+        <dependency>
+            <groupId>cn.iocoder.boot</groupId>
+            <artifactId>yudao-module-product-biz</artifactId>
+            <version>${revision}</version>
+        </dependency>
+        <dependency>
+            <groupId>cn.iocoder.boot</groupId>
+            <artifactId>yudao-module-trade-biz</artifactId>
+            <version>${revision}</version>
+        </dependency>
+        <dependency>
+            <groupId>cn.iocoder.boot</groupId>
+            <artifactId>yudao-module-statistics-biz</artifactId>
+            <version>${revision}</version>
+        </dependency>
 
         <!-- CRM 相关模块。默认注释,保证编译速度 -->
 <!--        <dependency>-->

+ 12 - 2
yudao-server/src/main/resources/application-local.yaml

@@ -47,14 +47,24 @@ spring:
       datasource:
         master:
           name: ruoyi-vue-pro
-          url: jdbc:mysql://127.0.0.1:3306/${spring.datasource.dynamic.datasource.master.name}?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true # MySQL Connector/J 8.X 连接的示例
+#          url: jdbc:mysql://127.0.0.1:3306/${spring.datasource.dynamic.datasource.master.name}?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true # MySQL Connector/J 8.X 连接的示例
+#          #          url: jdbc:mysql://127.0.0.1:3306/${spring.datasource.dynamic.datasource.master.name}?useSSL=false&allowPublicKeyRetrieval=true&useUnicode=true&characterEncoding=UTF-8&serverTimezone=CTT # MySQL Connector/J 5.X 连接的示例
+#          #          url: jdbc:postgresql://127.0.0.1:5432/${spring.datasource.dynamic.datasource.master.name} # PostgreSQL 连接的示例
+#          #          url: jdbc:oracle:thin:@127.0.0.1:1521:xe # Oracle 连接的示例
+#          #          url: jdbc:sqlserver://127.0.0.1:1433;DatabaseName=${spring.datasource.dynamic.datasource.master.name} # SQLServer 连接的示例
+#          #          url: jdbc:dm://10.211.55.4:5236?schema=RUOYI_VUE_PRO # DM 连接的示例
+#          username: root
+#          password: 123456
+
+          url: jdbc:mysql://52.197.141.149:3306/${spring.datasource.dynamic.datasource.master.name}?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true # MySQL Connector/J 8.X 连接的示例
           #          url: jdbc:mysql://127.0.0.1:3306/${spring.datasource.dynamic.datasource.master.name}?useSSL=false&allowPublicKeyRetrieval=true&useUnicode=true&characterEncoding=UTF-8&serverTimezone=CTT # MySQL Connector/J 5.X 连接的示例
           #          url: jdbc:postgresql://127.0.0.1:5432/${spring.datasource.dynamic.datasource.master.name} # PostgreSQL 连接的示例
           #          url: jdbc:oracle:thin:@127.0.0.1:1521:xe # Oracle 连接的示例
           #          url: jdbc:sqlserver://127.0.0.1:1433;DatabaseName=${spring.datasource.dynamic.datasource.master.name} # SQLServer 连接的示例
           #          url: jdbc:dm://10.211.55.4:5236?schema=RUOYI_VUE_PRO # DM 连接的示例
           username: root
-          password: 123456
+          password: 5a8bzjy?o&Tu
+
         #          username: sa
         #          password: JSm:g(*%lU4ZAkz06cd52KqT3)i1?H7W
         #          username: SYSDBA # DM 连接的示例