kongxiangyang пре 1 месец
родитељ
комит
3b10dbeb98
57 измењених фајлова са 2004 додато и 0 уклоњено
  1. 96 0
      pom.xml
  2. 15 0
      src/main/java/com/exness/promotion/PromotionApplication.java
  3. 18 0
      src/main/java/com/exness/promotion/common/PageVO.java
  4. 39 0
      src/main/java/com/exness/promotion/common/Result.java
  5. 27 0
      src/main/java/com/exness/promotion/config/MinioConfig.java
  6. 22 0
      src/main/java/com/exness/promotion/config/MyBatisPlusConfig.java
  7. 58 0
      src/main/java/com/exness/promotion/controller/BizTypeController.java
  8. 36 0
      src/main/java/com/exness/promotion/controller/CommonController.java
  9. 101 0
      src/main/java/com/exness/promotion/controller/FileController.java
  10. 41 0
      src/main/java/com/exness/promotion/controller/LangDictController.java
  11. 41 0
      src/main/java/com/exness/promotion/controller/MaterialCategoryController.java
  12. 42 0
      src/main/java/com/exness/promotion/controller/MaterialItemController.java
  13. 52 0
      src/main/java/com/exness/promotion/controller/MaterialMainController.java
  14. 10 0
      src/main/java/com/exness/promotion/controller/MinioController.java
  15. 50 0
      src/main/java/com/exness/promotion/controller/SpecDictController.java
  16. 50 0
      src/main/java/com/exness/promotion/enums/LanguageEnum.java
  17. 87 0
      src/main/java/com/exness/promotion/enums/SizeEnum.java
  18. 64 0
      src/main/java/com/exness/promotion/handler/GlobalExceptionHandler.java
  19. 10 0
      src/main/java/com/exness/promotion/mapper/BizTypeMapper.java
  20. 9 0
      src/main/java/com/exness/promotion/mapper/LangDictMapper.java
  21. 9 0
      src/main/java/com/exness/promotion/mapper/MaterialCategoryMapper.java
  22. 9 0
      src/main/java/com/exness/promotion/mapper/MaterialItemMapper.java
  23. 10 0
      src/main/java/com/exness/promotion/mapper/MaterialMainMapper.java
  24. 9 0
      src/main/java/com/exness/promotion/mapper/SpecDictMapper.java
  25. 10 0
      src/main/java/com/exness/promotion/mapper/SysFileMapper.java
  26. 20 0
      src/main/java/com/exness/promotion/pojo/BizType.java
  27. 14 0
      src/main/java/com/exness/promotion/pojo/LangDict.java
  28. 14 0
      src/main/java/com/exness/promotion/pojo/MaterialCategory.java
  29. 41 0
      src/main/java/com/exness/promotion/pojo/MaterialItem.java
  30. 34 0
      src/main/java/com/exness/promotion/pojo/MaterialMain.java
  31. 16 0
      src/main/java/com/exness/promotion/pojo/SpecDict.java
  32. 68 0
      src/main/java/com/exness/promotion/pojo/SysFile.java
  33. 22 0
      src/main/java/com/exness/promotion/service/BizTypeService.java
  34. 13 0
      src/main/java/com/exness/promotion/service/LangDictService.java
  35. 13 0
      src/main/java/com/exness/promotion/service/MaterialCategoryService.java
  36. 13 0
      src/main/java/com/exness/promotion/service/MaterialItemService.java
  37. 28 0
      src/main/java/com/exness/promotion/service/MaterialMainService.java
  38. 13 0
      src/main/java/com/exness/promotion/service/SpecDictService.java
  39. 14 0
      src/main/java/com/exness/promotion/service/SysFileService.java
  40. 72 0
      src/main/java/com/exness/promotion/service/impl/BizTypeServiceImpl.java
  41. 34 0
      src/main/java/com/exness/promotion/service/impl/LangDictServiceImpl.java
  42. 35 0
      src/main/java/com/exness/promotion/service/impl/MaterialCategoryServiceImpl.java
  43. 33 0
      src/main/java/com/exness/promotion/service/impl/MaterialItemServiceImpl.java
  44. 186 0
      src/main/java/com/exness/promotion/service/impl/MaterialMainServiceImpl.java
  45. 34 0
      src/main/java/com/exness/promotion/service/impl/SpecDictServiceImpl.java
  46. 73 0
      src/main/java/com/exness/promotion/service/impl/SysFileServiceImpl.java
  47. 105 0
      src/main/java/com/exness/promotion/util/MinioUtil.java
  48. 9 0
      src/main/java/com/exness/promotion/vo/BaseVo.java
  49. 13 0
      src/main/java/com/exness/promotion/vo/LabelValueVO.java
  50. 44 0
      src/main/java/com/exness/promotion/vo/req/FileMaterialUploadReqVo.java
  51. 22 0
      src/main/java/com/exness/promotion/vo/req/LandingMaterialReqVo.java
  52. 20 0
      src/main/java/com/exness/promotion/vo/req/MaterialMainSearchReqVo.java
  53. 12 0
      src/main/java/com/exness/promotion/vo/resp/BizTypeSearchRespVo.java
  54. 20 0
      src/main/java/com/exness/promotion/vo/resp/MaterialMainSearchRespVo.java
  55. 34 0
      src/main/resources/application.yml
  56. 7 0
      src/main/resources/mapper/MaterialMainMapper.xml
  57. 13 0
      src/test/java/com/exness/promotion/PromotionApplicationTests.java

+ 96 - 0
pom.xml

@@ -0,0 +1,96 @@
+<?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>org.springframework.boot</groupId>
+        <artifactId>spring-boot-starter-parent</artifactId>
+        <version>3.4.0</version>
+        <relativePath/>
+    </parent>
+
+    <groupId>com.exness.promotion</groupId>
+    <artifactId>promotion</artifactId>
+    <version>0.0.1</version>
+
+    <properties>
+        <java.version>17</java.version>
+    </properties>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-web</artifactId>
+        </dependency>
+
+        <!-- 流式输出 -->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-webflux</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.projectlombok</groupId>
+            <artifactId>lombok</artifactId>
+            <version>1.18.24</version> <!-- 使用最新的稳定版本 -->
+            <scope>provided</scope>
+        </dependency>
+
+        <!-- MinIO Java Client -->
+        <dependency>
+            <groupId>io.minio</groupId>
+            <artifactId>minio</artifactId>
+            <version>8.3.3</version> <!-- 请检查最新版本 -->
+        </dependency>
+
+        <!-- MyBatis-Plus 启动器(适配 SpringBoot3) -->
+        <dependency>
+            <groupId>com.baomidou</groupId>
+            <artifactId>mybatis-plus-spring-boot3-starter</artifactId>
+            <version>3.5.5</version>
+        </dependency>
+
+        <!-- MySQL 驱动 -->
+        <dependency>
+            <groupId>com.mysql</groupId>
+            <artifactId>mysql-connector-j</artifactId>
+            <scope>runtime</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>com.github.pagehelper</groupId>
+            <artifactId>pagehelper-spring-boot-starter</artifactId>
+            <version>1.4.7</version>
+        </dependency>
+
+        <dependency>
+            <groupId>cn.hutool</groupId>
+            <artifactId>hutool-all</artifactId>
+            <version>5.8.3</version>
+        </dependency>
+
+        <!-- JDK17/SpringBoot3 参数校验核心依赖 -->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-validation</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-test</artifactId>
+            <scope>test</scope>
+        </dependency>
+    </dependencies>
+
+    <!-- 阿里云仓库,必须加 -->
+    <repositories>
+        <repository>
+            <id>aliyunmaven</id>
+            <name>Aliyun Maven</name>
+            <url>https://maven.aliyun.com/repository/public</url>
+        </repository>
+    </repositories>
+
+</project>

+ 15 - 0
src/main/java/com/exness/promotion/PromotionApplication.java

@@ -0,0 +1,15 @@
+package com.exness.promotion;
+
+import org.mybatis.spring.annotation.MapperScan;
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+@SpringBootApplication
+@MapperScan("com.exness.promotion.mapper")
+public class PromotionApplication {
+
+    public static void main(String[] args) {
+        SpringApplication.run(PromotionApplication.class, args);
+    }
+
+}

+ 18 - 0
src/main/java/com/exness/promotion/common/PageVO.java

@@ -0,0 +1,18 @@
+package com.exness.promotion.common;
+
+import lombok.Data;
+import java.util.List;
+
+@Data
+public class PageVO<T> {
+    // 总记录数
+    private Long total;
+    // 当前页码
+    private Long current;
+    // 每页条数
+    private Long size;
+    // 总页数
+    private Long pages;
+    // 数据列表
+    private List<T> rows;
+}

+ 39 - 0
src/main/java/com/exness/promotion/common/Result.java

@@ -0,0 +1,39 @@
+package com.exness.promotion.common;
+
+import lombok.Data;
+
+@Data
+public class Result<T> {
+
+    private Integer code;
+    private String msg;
+    private T data;
+
+    public static <T> Result<T> success() {
+        return result(200, "操作成功", null);
+    }
+
+    public static <T> Result<T> success(T data) {
+        return result(200, "操作成功", data);
+    }
+
+    public static <T> Result<T> success(String msg, T data) {
+        return result(200, msg, data);
+    }
+
+    public static <T> Result<T> fail(String msg) {
+        return result(500, msg, null);
+    }
+
+    public static <T> Result<T> fail(Integer code, String msg) {
+        return result(code, msg, null);
+    }
+
+    private static <T> Result<T> result(Integer code, String msg, T data) {
+        Result<T> r = new Result<>();
+        r.setCode(code);
+        r.setMsg(msg);
+        r.setData(data);
+        return r;
+    }
+}

+ 27 - 0
src/main/java/com/exness/promotion/config/MinioConfig.java

@@ -0,0 +1,27 @@
+package com.exness.promotion.config;
+
+import io.minio.MinioClient;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+@Configuration
+public class MinioConfig {
+
+    @Value("${minio.endpoint}")
+    private String endpoint;
+
+    @Value("${minio.accessKey}")
+    private String accessKey;
+
+    @Value("${minio.secretKey}")
+    private String secretKey;
+
+    @Bean
+    public MinioClient minioClient() {
+        return MinioClient.builder()
+                .endpoint(endpoint)
+                .credentials(accessKey, secretKey)
+                .build();
+    }
+}

+ 22 - 0
src/main/java/com/exness/promotion/config/MyBatisPlusConfig.java

@@ -0,0 +1,22 @@
+package com.exness.promotion.config;
+
+import com.baomidou.mybatisplus.annotation.DbType;
+import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
+import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+@Configuration
+public class MyBatisPlusConfig {
+
+    /**
+     * 分页插件(必须配,否则 total 永远为 0)
+     */
+    @Bean
+    public MybatisPlusInterceptor mybatisPlusInterceptor() {
+        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
+        // MySQL 分页
+        interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
+        return interceptor;
+    }
+}

+ 58 - 0
src/main/java/com/exness/promotion/controller/BizTypeController.java

@@ -0,0 +1,58 @@
+package com.exness.promotion.controller;
+
+import com.exness.promotion.common.Result;
+import com.exness.promotion.pojo.BizType;
+import com.exness.promotion.service.BizTypeService;
+import com.exness.promotion.vo.resp.BizTypeSearchRespVo;
+import lombok.RequiredArgsConstructor;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+@RestController
+@RequestMapping("/biz/type")
+@RequiredArgsConstructor
+public class BizTypeController {
+
+    private final BizTypeService bizTypeService;
+
+    /**
+     * 列表查询
+     */
+    @GetMapping("/list")
+    public Result<List<BizTypeSearchRespVo>> list() {
+        return Result.success(bizTypeService.getList());
+    }
+
+    /**
+     * 新增
+     */
+    @PostMapping("/add")
+    public Result<Boolean> add(@RequestBody BizType bizType) {
+        return Result.success(bizTypeService.add(bizType));
+    }
+
+    /**
+     * 修改
+     */
+    @PutMapping("/edit")
+    public Result<Boolean> edit(@RequestBody BizType bizType) {
+        return Result.success(bizTypeService.edit(bizType));
+    }
+
+    /**
+     * 删除
+     */
+    @DeleteMapping("/delete/{id}")
+    public Result<Boolean> delete(@PathVariable Long id) {
+        return Result.success(bizTypeService.remove(id));
+    }
+
+    /**
+     * 详情
+     */
+    @GetMapping("/detail/{id}")
+    public Result<BizType> detail(@PathVariable Long id) {
+        return Result.success(bizTypeService.getById(id));
+    }
+}

+ 36 - 0
src/main/java/com/exness/promotion/controller/CommonController.java

@@ -0,0 +1,36 @@
+package com.exness.promotion.controller;
+
+import com.exness.promotion.enums.LanguageEnum;
+import com.exness.promotion.enums.SizeEnum;
+import com.exness.promotion.vo.LabelValueVO;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.Collectors;
+
+@RestController
+@RequestMapping("/api/enum")
+public class CommonController {
+    /**
+     * 语言类型下拉框
+     */
+    @GetMapping("/language")
+    public List<LabelValueVO> languageList() {
+        return Arrays.stream(LanguageEnum.values())
+                .map(e -> new LabelValueVO(e.getDesc(), e.getCode()))
+                .collect(Collectors.toList());
+    }
+
+    /**
+     * 尺寸类型下拉框
+     */
+    @GetMapping("/size")
+    public List<LabelValueVO> sizeList() {
+        return Arrays.stream(SizeEnum.values())
+                .map(e -> new LabelValueVO(e.getDesc(), e.getCode()))
+                .collect(Collectors.toList());
+    }
+}

+ 101 - 0
src/main/java/com/exness/promotion/controller/FileController.java

@@ -0,0 +1,101 @@
+package com.exness.promotion.controller;
+
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.exness.promotion.common.PageVO;
+import com.exness.promotion.common.Result;
+import com.exness.promotion.pojo.SysFile;
+import com.exness.promotion.service.SysFileService;
+import com.exness.promotion.util.MinioUtil;
+import lombok.RequiredArgsConstructor;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.MediaType;
+import org.springframework.http.MediaTypeFactory;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.io.InputStream;
+import java.net.URLEncoder;
+import java.nio.charset.StandardCharsets;
+
+@RestController
+@RequestMapping("/file")
+@RequiredArgsConstructor
+public class FileController {
+
+    @Autowired
+    private MinioUtil minioUtil;
+
+    private final SysFileService sysFileService;
+
+    /**
+     * 上传文件
+     */
+    @PostMapping("/upload")
+    public Result<SysFile> upload(@RequestParam("file") MultipartFile file) {
+        SysFile sysFile = sysFileService.upload(file);
+        return Result.success("上传成功", sysFile);
+    }
+
+
+    /**
+     * 分页文件列表
+     */
+    @GetMapping("/list")
+    public Result<PageVO<SysFile>> list(
+            @RequestParam(defaultValue = "1") Integer pageNum,
+            @RequestParam(defaultValue = "10") Integer pageSize) {
+
+        Page<SysFile> page = new Page<>(pageNum, pageSize);
+        IPage<SysFile> iPage = sysFileService.page(page);
+
+        PageVO<SysFile> pageVO = new PageVO<>();
+        pageVO.setTotal(iPage.getTotal());
+        pageVO.setCurrent(iPage.getCurrent());
+        pageVO.setSize(iPage.getSize());
+        pageVO.setPages(iPage.getPages());
+        pageVO.setRows(iPage.getRecords());
+
+        return Result.success(pageVO);
+    }
+
+    /**
+     * 删除文件
+     */
+    @DeleteMapping("/delete/{id}")
+    public Result<String> delete(@PathVariable Long id) {
+        boolean ok = sysFileService.deleteFile(id);
+        if (ok) {
+            return Result.success("删除成功");
+        }
+        return Result.fail("删除失败,文件不存在");
+    }
+
+    @GetMapping("/download")
+    public ResponseEntity<byte[]> downloadFile(@RequestParam("fileName") String fileName) {
+        try (InputStream inputStream = minioUtil.downloadFile(fileName)) {
+            // 读取文件流为字节数组
+            byte[] bytes = inputStream.readAllBytes();
+
+            // 获取文件MIME类型
+            String contentType = MediaTypeFactory.getMediaType(fileName)
+                    .map(MediaType::toString)
+                    .orElse(MediaType.APPLICATION_OCTET_STREAM_VALUE);
+
+            // 处理中文文件名(防止乱码)
+            String encodedFileName = URLEncoder.encode(fileName, StandardCharsets.UTF_8.name()).replace("+", "%20");
+
+            // 返回下载响应
+            return ResponseEntity.ok()
+                    .contentType(MediaType.parseMediaType(contentType))
+                    .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename*=UTF-8''" + encodedFileName)
+                    .body(bytes);
+
+        } catch (Exception e) {
+            e.printStackTrace();
+            return ResponseEntity.status(500).body(("下载失败:" + e.getMessage()).getBytes(StandardCharsets.UTF_8));
+        }
+    }
+}

+ 41 - 0
src/main/java/com/exness/promotion/controller/LangDictController.java

@@ -0,0 +1,41 @@
+package com.exness.promotion.controller;
+
+import com.exness.promotion.common.Result;
+import com.exness.promotion.pojo.LangDict;
+import com.exness.promotion.service.LangDictService;
+import lombok.RequiredArgsConstructor;
+import org.springframework.web.bind.annotation.*;
+import java.util.List;
+
+@RestController
+@RequestMapping("/lang")
+@RequiredArgsConstructor
+public class LangDictController {
+
+    private final LangDictService langDictService;
+
+    @GetMapping("/list")
+    public Result<List<LangDict>> list() {
+        return Result.success(langDictService.getList());
+    }
+
+    @PostMapping("/add")
+    public Result<Boolean> add(@RequestBody LangDict entity) {
+        return Result.success(langDictService.add(entity));
+    }
+
+    @PutMapping("/edit")
+    public Result<Boolean> edit(@RequestBody LangDict entity) {
+        return Result.success(langDictService.edit(entity));
+    }
+
+    @DeleteMapping("/delete/{id}")
+    public Result<Boolean> delete(@PathVariable Long id) {
+        return Result.success(langDictService.del(id));
+    }
+
+    @GetMapping("/detail/{id}")
+    public Result<LangDict> detail(@PathVariable Long id) {
+        return Result.success(langDictService.getById(id));
+    }
+}

+ 41 - 0
src/main/java/com/exness/promotion/controller/MaterialCategoryController.java

@@ -0,0 +1,41 @@
+package com.exness.promotion.controller;
+
+import com.exness.promotion.common.Result;
+import com.exness.promotion.pojo.MaterialCategory;
+import com.exness.promotion.service.MaterialCategoryService;
+import lombok.RequiredArgsConstructor;
+import org.springframework.web.bind.annotation.*;
+import java.util.List;
+
+@RestController
+@RequestMapping("/material/category")
+@RequiredArgsConstructor
+public class MaterialCategoryController {
+
+    private final MaterialCategoryService categoryService;
+
+    @GetMapping("/list")
+    public Result<List<MaterialCategory>> list() {
+        return Result.success(categoryService.getList());
+    }
+
+    @PostMapping("/add")
+    public Result<Boolean> add(@RequestBody MaterialCategory entity) {
+        return Result.success(categoryService.add(entity));
+    }
+
+    @PutMapping("/edit")
+    public Result<Boolean> edit(@RequestBody MaterialCategory entity) {
+        return Result.success(categoryService.edit(entity));
+    }
+
+    @DeleteMapping("/delete/{id}")
+    public Result<Boolean> delete(@PathVariable Long id) {
+        return Result.success(categoryService.del(id));
+    }
+
+    @GetMapping("/detail/{id}")
+    public Result<MaterialCategory> detail(@PathVariable Long id) {
+        return Result.success(categoryService.getById(id));
+    }
+}

+ 42 - 0
src/main/java/com/exness/promotion/controller/MaterialItemController.java

@@ -0,0 +1,42 @@
+package com.exness.promotion.controller;
+
+import com.exness.promotion.common.Result;
+import com.exness.promotion.pojo.MaterialItem;
+import com.exness.promotion.service.MaterialItemService;
+import lombok.RequiredArgsConstructor;
+import org.springframework.web.bind.annotation.*;
+import java.util.List;
+
+@RestController
+@RequestMapping("/material/item")
+@RequiredArgsConstructor
+public class MaterialItemController {
+
+    private final MaterialItemService materialItemService;
+
+    /** 根据主素材ID查询所有子项(多语言/多尺寸/多比例) */
+    @GetMapping("/listByMainId/{mainId}")
+    public Result<List<MaterialItem>> listByMainId(@PathVariable Long mainId) {
+        return Result.success(materialItemService.getListByMainId(mainId));
+    }
+
+    @PostMapping("/add")
+    public Result<Boolean> add(@RequestBody MaterialItem entity) {
+        return Result.success(materialItemService.add(entity));
+    }
+
+    @PutMapping("/edit")
+    public Result<Boolean> edit(@RequestBody MaterialItem entity) {
+        return Result.success(materialItemService.edit(entity));
+    }
+
+    @DeleteMapping("/delete/{id}")
+    public Result<Boolean> delete(@PathVariable Long id) {
+        return Result.success(materialItemService.del(id));
+    }
+
+    @GetMapping("/detail/{id}")
+    public Result<MaterialItem> detail(@PathVariable Long id) {
+        return Result.success(materialItemService.getById(id));
+    }
+}

+ 52 - 0
src/main/java/com/exness/promotion/controller/MaterialMainController.java

@@ -0,0 +1,52 @@
+package com.exness.promotion.controller;
+
+import com.exness.promotion.common.Result;
+import com.exness.promotion.pojo.MaterialMain;
+import com.exness.promotion.service.MaterialMainService;
+import com.exness.promotion.vo.req.FileMaterialUploadReqVo;
+import com.exness.promotion.vo.req.LandingMaterialReqVo;
+import com.exness.promotion.vo.req.MaterialMainSearchReqVo;
+import com.github.pagehelper.PageInfo;
+import jakarta.validation.Valid;
+import lombok.RequiredArgsConstructor;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+@RestController
+@RequestMapping("/material/main")
+@RequiredArgsConstructor
+public class MaterialMainController {
+
+    private final MaterialMainService materialMainService;
+
+    /**
+     * 文件素材上传:BANNER / GIF / VIDEO
+     *
+     * @param uploadVo
+     * @return
+     */
+    @PostMapping("/add")
+    public Result<MaterialMain> add(@Valid @RequestBody FileMaterialUploadReqVo uploadVo) {
+        return Result.success(materialMainService.add(uploadVo));
+    }
+
+    /**
+     * 新增多语言着陆页
+     */
+    @PostMapping("/upload/landing")
+    public Result<MaterialMain> addLanding(@Valid @RequestBody LandingMaterialReqVo landingMaterialReqVo) {
+        return materialMainService.addLandingMaterial(landingMaterialReqVo);
+    }
+
+    /**
+     * 素材列表查询
+     * @param searchVo
+     * @return
+     */
+    @PostMapping("/search")
+    public Result<PageInfo> search(@Valid @RequestBody MaterialMainSearchReqVo searchVo) {
+        return Result.success(materialMainService.search(searchVo));
+    }
+}

+ 10 - 0
src/main/java/com/exness/promotion/controller/MinioController.java

@@ -0,0 +1,10 @@
+package com.exness.promotion.controller;
+
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+@RestController
+@RequestMapping("/minio")
+public class MinioController {
+
+}

+ 50 - 0
src/main/java/com/exness/promotion/controller/SpecDictController.java

@@ -0,0 +1,50 @@
+package com.exness.promotion.controller;
+
+import com.exness.promotion.common.Result;
+import com.exness.promotion.pojo.SpecDict;
+import com.exness.promotion.service.SpecDictService;
+import lombok.RequiredArgsConstructor;
+import org.springframework.web.bind.annotation.*;
+import java.util.List;
+
+@RestController
+@RequestMapping("/spec")
+@RequiredArgsConstructor
+public class SpecDictController {
+
+    private final SpecDictService specDictService;
+
+    @GetMapping("/list")
+    public Result<List<SpecDict>> list() {
+        return Result.success(specDictService.getList());
+    }
+
+    // 根据素材分类编码查询对应规格(前端下拉必用)
+    @GetMapping("/listByCategory")
+    public Result<List<SpecDict>> listByCategory(@RequestParam String categoryCode) {
+        List<SpecDict> list = specDictService.lambdaQuery()
+                .eq(SpecDict::getCategoryCode, categoryCode)
+                .list();
+        return Result.success(list);
+    }
+
+    @PostMapping("/add")
+    public Result<Boolean> add(@RequestBody SpecDict entity) {
+        return Result.success(specDictService.add(entity));
+    }
+
+    @PutMapping("/edit")
+    public Result<Boolean> edit(@RequestBody SpecDict entity) {
+        return Result.success(specDictService.edit(entity));
+    }
+
+    @DeleteMapping("/delete/{id}")
+    public Result<Boolean> delete(@PathVariable Long id) {
+        return Result.success(specDictService.del(id));
+    }
+
+    @GetMapping("/detail/{id}")
+    public Result<SpecDict> detail(@PathVariable Long id) {
+        return Result.success(specDictService.getById(id));
+    }
+}

+ 50 - 0
src/main/java/com/exness/promotion/enums/LanguageEnum.java

@@ -0,0 +1,50 @@
+package com.exness.promotion.enums;
+
+public enum LanguageEnum {
+
+    ZH_CN("zh-CN", "简体中文"),
+    ZH_TW("zh-TW", "繁体中文"),
+    EN("en", "英语"),
+    JA("ja", "日语"),
+    KO("ko", "韩语"),
+    FR("fr", "法语"),
+    DE("de", "德语"),
+    ES("es", "西班牙语"),
+    RU("ru", "俄语"),
+    PT("pt", "葡萄牙语"),
+    AR("ar", "阿拉伯语"),
+    HI("hi", "印地语"),
+    ID("id", "印尼语"),
+    TH("th", "泰语"),
+    VI("vi", "越南语"),
+    IT("it", "意大利语"),
+    TR("tr", "土耳其语");
+
+    private final String code;
+    private final String desc;
+
+    LanguageEnum(String code, String desc) {
+        this.code = code;
+        this.desc = desc;
+    }
+
+    public String getCode() {
+        return code;
+    }
+
+    public String getDesc() {
+        return desc;
+    }
+
+    /**
+     * 根据语言code获取中文名称
+     */
+    public static String getDescByCode(String code) {
+        for (LanguageEnum languageEnum : LanguageEnum.values()) {
+            if (languageEnum.getCode().equals(code)) {
+                return languageEnum.getDesc();
+            }
+        }
+        return "";
+    }
+}

+ 87 - 0
src/main/java/com/exness/promotion/enums/SizeEnum.java

@@ -0,0 +1,87 @@
+package com.exness.promotion.enums;
+
+public enum SizeEnum {
+
+    // 尺寸规格(code 和 desc 完全一致)
+    SIZE_720x90("720*90", "720*90"),
+    SIZE_990x251("990*251", "990*251"),
+    SIZE_120x600("120*600", "120*600"),
+    SIZE_320x100("320*100", "320*100"),
+    SIZE_300x600("300*600", "300*600"),
+    SIZE_728x90("728*90", "728*90"),
+    SIZE_320x50("320*50", "320*50"),
+    SIZE_320x51("320*51", "320*51"),
+    SIZE_1200x1500("1200*1500", "1200*1500"),
+    SIZE_1024x768("1024*768", "1024*768"),
+    SIZE_1200x628("1200*628", "1200*628"),
+    SIZE_990x250("990*250", "990*250"),
+    SIZE_794x794("794*794", "794*794"),
+    SIZE_828x1200("828*1200", "828*1200"),
+    SIZE_320x101("320*101", "320*101"),
+    SIZE_960x300("960*300", "960*300"),
+    SIZE_600x800("600*800", "600*800"),
+    SIZE_980x251("980*251", "980*251"),
+    SIZE_980x250("980*250", "980*250"),
+    SIZE_640x100("640*100", "640*100"),
+    SIZE_300x250("300*250", "300*250"),
+    SIZE_794x795("794*795", "794*795"),
+    SIZE_1200x1200("1200*1200", "1200*1200"),
+    SIZE_1000x500("1000*500", "1000*500"),
+    SIZE_970x251("970*251", "970*251"),
+    SIZE_160x601("160*601", "160*601"),
+    SIZE_720x91("720*91", "720*91"),
+    SIZE_800x800("800*800", "800*800"),
+    SIZE_300x251("300*251", "300*251"),
+    SIZE_320x480("320*480", "320*480"),
+    SIZE_960x150("960*150", "960*150"),
+    SIZE_512x512("512*512", "512*512"),
+    SIZE_970x250("970*250", "970*250"),
+    SIZE_768x1024("768*1024", "768*1024"),
+    SIZE_728x91("728*91", "728*91"),
+    SIZE_1242x2208("1242*2208", "1242*2208"),
+    SIZE_300x601("300*601", "300*601"),
+    SIZE_160x600("160*600", "160*600"),
+    SIZE_800x600("800*600", "800*600"),
+    SIZE_1080x1920("1080*1920", "1080*1920"),
+    SIZE_1200x1501("1200*1501", "1200*1501"),
+    SIZE_480x320("480*320", "480*320"),
+    SIZE_1200x1201("1200*1201", "1200*1201"),
+    SIZE_628x1201("628*1201", "628*1201"),
+    SIZE_120x601("120*601", "120*601"),
+    SIZE_1200x629("1200*629", "1200*629"),
+    SIZE_628x1200("628*1200", "628*1200"),
+    SIZE_320x481("320*481", "320*481"),
+
+    // 特殊方向类型
+    SQUARE("SQUARE", "正方形"),
+    VERTICAL("VERTICAL", "纵向"),
+    HORIZONTAL("HORIZONTAL", "横向");
+
+    private final String code;
+    private final String desc;
+
+    SizeEnum(String code, String desc) {
+        this.code = code;
+        this.desc = desc;
+    }
+
+    public String getCode() {
+        return code;
+    }
+
+    public String getDesc() {
+        return desc;
+    }
+
+    /**
+     * 根据code获取对应的描述
+     */
+    public static String getDescByCode(String code) {
+        for (SizeEnum sizeEnum : values()) {
+            if (sizeEnum.code.equals(code)) {
+                return sizeEnum.desc;
+            }
+        }
+        return "";
+    }
+}

+ 64 - 0
src/main/java/com/exness/promotion/handler/GlobalExceptionHandler.java

@@ -0,0 +1,64 @@
+package com.exness.promotion.handler;
+
+import com.exness.promotion.common.Result;
+import jakarta.validation.ConstraintViolation;
+import jakarta.validation.ConstraintViolationException;
+import org.springframework.validation.BindException;
+import org.springframework.validation.FieldError;
+import org.springframework.web.bind.MethodArgumentNotValidException;
+import org.springframework.web.bind.annotation.ExceptionHandler;
+import org.springframework.web.bind.annotation.RestControllerAdvice;
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+@RestControllerAdvice
+public class GlobalExceptionHandler {
+
+    /**
+     * 处理 @RequestBody 参数校验异常
+     */
+    @ExceptionHandler(MethodArgumentNotValidException.class)
+    public Result<?> handleValidException(MethodArgumentNotValidException e) {
+        List<FieldError> fieldErrors = e.getBindingResult().getFieldErrors();
+        String message = fieldErrors.stream()
+                .map(error -> error.getDefaultMessage())
+                .collect(Collectors.joining(";"));
+
+        return Result.fail(400, "参数校验失败:" + message);
+    }
+
+    /**
+     * 处理表单参数校验异常
+     */
+    @ExceptionHandler(BindException.class)
+    public Result<?> handleBindException(BindException e) {
+        List<FieldError> fieldErrors = e.getBindingResult().getFieldErrors();
+        String message = fieldErrors.stream()
+                .map(error -> error.getDefaultMessage())
+                .collect(Collectors.joining(";"));
+
+        return Result.fail(400, "参数校验失败:" + message);
+    }
+
+    /**
+     * 处理单个参数校验异常
+     */
+    @ExceptionHandler(ConstraintViolationException.class)
+    public Result<?> handleConstraintViolationException(ConstraintViolationException e) {
+        Set<ConstraintViolation<?>> violations = e.getConstraintViolations();
+        String message = violations.stream()
+                .map(ConstraintViolation::getMessage)
+                .collect(Collectors.joining(";"));
+
+        return Result.fail(400, "参数校验失败:" + message);
+    }
+
+//    /**
+//     * 处理其他所有异常
+//     */
+//    @ExceptionHandler(Exception.class)
+//    public Result<?> handleException(Exception e) {
+//        return Result.fail(500, "服务器异常:" + e.getMessage());
+//    }
+}

+ 10 - 0
src/main/java/com/exness/promotion/mapper/BizTypeMapper.java

@@ -0,0 +1,10 @@
+package com.exness.promotion.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.exness.promotion.pojo.BizType;
+import org.apache.ibatis.annotations.Mapper;
+
+@Mapper
+public interface BizTypeMapper extends BaseMapper<BizType> {
+
+}

+ 9 - 0
src/main/java/com/exness/promotion/mapper/LangDictMapper.java

@@ -0,0 +1,9 @@
+package com.exness.promotion.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.exness.promotion.pojo.LangDict;
+import org.apache.ibatis.annotations.Mapper;
+
+@Mapper
+public interface LangDictMapper extends BaseMapper<LangDict> {
+}

+ 9 - 0
src/main/java/com/exness/promotion/mapper/MaterialCategoryMapper.java

@@ -0,0 +1,9 @@
+package com.exness.promotion.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.exness.promotion.pojo.MaterialCategory;
+import org.apache.ibatis.annotations.Mapper;
+
+@Mapper
+public interface MaterialCategoryMapper extends BaseMapper<MaterialCategory> {
+}

+ 9 - 0
src/main/java/com/exness/promotion/mapper/MaterialItemMapper.java

@@ -0,0 +1,9 @@
+package com.exness.promotion.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.exness.promotion.pojo.MaterialItem;
+import org.apache.ibatis.annotations.Mapper;
+
+@Mapper
+public interface MaterialItemMapper extends BaseMapper<MaterialItem> {
+}

+ 10 - 0
src/main/java/com/exness/promotion/mapper/MaterialMainMapper.java

@@ -0,0 +1,10 @@
+package com.exness.promotion.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.exness.promotion.pojo.MaterialMain;
+import org.apache.ibatis.annotations.Mapper;
+
+@Mapper
+public interface MaterialMainMapper extends BaseMapper<MaterialMain> {
+
+}

+ 9 - 0
src/main/java/com/exness/promotion/mapper/SpecDictMapper.java

@@ -0,0 +1,9 @@
+package com.exness.promotion.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.exness.promotion.pojo.SpecDict;
+import org.apache.ibatis.annotations.Mapper;
+
+@Mapper
+public interface SpecDictMapper extends BaseMapper<SpecDict> {
+}

+ 10 - 0
src/main/java/com/exness/promotion/mapper/SysFileMapper.java

@@ -0,0 +1,10 @@
+package com.exness.promotion.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.exness.promotion.pojo.SysFile;
+import org.apache.ibatis.annotations.Mapper;
+
+@Mapper
+public interface SysFileMapper extends BaseMapper<SysFile> {
+
+}

+ 20 - 0
src/main/java/com/exness/promotion/pojo/BizType.java

@@ -0,0 +1,20 @@
+package com.exness.promotion.pojo;
+
+import com.baomidou.mybatisplus.annotation.*;
+import lombok.Data;
+
+import java.time.LocalDateTime;
+
+@Data
+@TableName("biz_type")
+public class BizType {
+    @TableId(type = IdType.AUTO)
+    private Long id;
+    private String typeName;
+    private Integer sort;
+    private Integer status;
+    private LocalDateTime createTime;
+    private LocalDateTime updateTime;
+    @TableLogic
+    private Integer delFlag;
+}

+ 14 - 0
src/main/java/com/exness/promotion/pojo/LangDict.java

@@ -0,0 +1,14 @@
+package com.exness.promotion.pojo;
+
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+
+@Data
+@TableName("lang_dict")
+public class LangDict {
+    private Long id;
+    /** 语言编码:zh-CN / en / ar / fr */
+    private String langCode;
+    /** 语言名称 */
+    private String langName;
+}

+ 14 - 0
src/main/java/com/exness/promotion/pojo/MaterialCategory.java

@@ -0,0 +1,14 @@
+package com.exness.promotion.pojo;
+
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+
+@Data
+@TableName("material_category")
+public class MaterialCategory {
+    private Long id;
+    /** 素材分类编码:BANNER/VIDEO/LANDING/GIF */
+    private String categoryCode;
+    /** 分类名称 */
+    private String categoryName;
+}

+ 41 - 0
src/main/java/com/exness/promotion/pojo/MaterialItem.java

@@ -0,0 +1,41 @@
+package com.exness.promotion.pojo;
+
+import com.baomidou.mybatisplus.annotation.*;
+import lombok.Data;
+
+import java.time.LocalDateTime;
+
+@Data
+@TableName("material_item")
+public class MaterialItem {
+
+    @TableId(type = IdType.AUTO)
+    private Long id;
+
+    /** 素材主表ID */
+    private Long mainId;
+
+    /** 规格编码(尺寸/比例) */
+    private String specCode;
+
+    /** 语言编码 */
+    private String langCode;
+
+    /** 文件URL 或 着陆页URL */
+    private String fileUrl;
+
+    /** 原始文件名 */
+    private String fileName;
+
+    /** 文件大小 */
+    private Long fileSize;
+
+    /** 是否着陆页 1=是 */
+    private Integer isLanding;
+
+    @TableField(fill = FieldFill.INSERT)
+    private LocalDateTime createTime;
+
+    @TableLogic
+    private Integer delFlag;
+}

+ 34 - 0
src/main/java/com/exness/promotion/pojo/MaterialMain.java

@@ -0,0 +1,34 @@
+package com.exness.promotion.pojo;
+
+import com.baomidou.mybatisplus.annotation.*;
+import lombok.Data;
+import java.time.LocalDateTime;
+
+@Data
+@TableName("material_main")
+public class MaterialMain {
+
+    @TableId(type = IdType.AUTO)
+    private Long id;
+
+    /** 业务类型ID */
+    private Long bizTypeId;
+
+    /** 素材类型编码 BANNER/VIDEO/GIF/LANDING */
+    private String categoryCode;
+
+    /** 素材标题 */
+    private String title;
+
+    /** 排序 */
+    private Integer sort;
+
+    /** 状态 0禁用 1启用 */
+    private Integer status;
+
+    @TableField(fill = FieldFill.INSERT)
+    private LocalDateTime createTime;
+
+    @TableLogic
+    private Integer delFlag;
+}

+ 16 - 0
src/main/java/com/exness/promotion/pojo/SpecDict.java

@@ -0,0 +1,16 @@
+package com.exness.promotion.pojo;
+
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+
+@Data
+@TableName("spec_dict")
+public class SpecDict {
+    private Long id;
+    /** 关联素材分类编码 */
+    private String categoryCode;
+    /** 规格编码 */
+    private String specCode;
+    /** 规格名称(展示文案) */
+    private String specName;
+}

+ 68 - 0
src/main/java/com/exness/promotion/pojo/SysFile.java

@@ -0,0 +1,68 @@
+package com.exness.promotion.pojo;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableLogic;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+
+import java.time.LocalDateTime;
+
+@Data
+@TableName("sys_file")
+public class SysFile {
+
+    @TableId(type = IdType.AUTO)
+    private Long id;
+
+    /**
+     * 原始文件名
+     */
+    private String fileName;
+
+    /**
+     * 文件后缀
+     */
+    private String fileSuffix;
+
+    /**
+     * 文件访问URL
+     */
+    private String fileUrl;
+
+    /**
+     * 文件大小(字节)
+     */
+    private Long fileSize;
+
+    /**
+     * 桶名
+     */
+    private String bucketName;
+
+    /**
+     * 是否公开 0私有 1公开
+     */
+    private Integer isPublic;
+
+    /**
+     * 上传人
+     */
+    private String createBy;
+
+    /**
+     * 上传时间
+     */
+    private LocalDateTime createTime;
+
+    /**
+     * 更新时间
+     */
+    private LocalDateTime updateTime;
+
+    /**
+     * 逻辑删除
+     */
+    @TableLogic
+    private Integer delFlag;
+}

+ 22 - 0
src/main/java/com/exness/promotion/service/BizTypeService.java

@@ -0,0 +1,22 @@
+package com.exness.promotion.service;
+
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.exness.promotion.pojo.BizType;
+import com.exness.promotion.vo.resp.BizTypeSearchRespVo;
+
+import java.util.List;
+
+public interface BizTypeService extends IService<BizType> {
+
+    // 列表(不分页)
+    List<BizTypeSearchRespVo> getList();
+
+    // 新增
+    boolean add(BizType bizType);
+
+    // 修改
+    boolean edit(BizType bizType);
+
+    // 删除
+    boolean remove(Long id);
+}

+ 13 - 0
src/main/java/com/exness/promotion/service/LangDictService.java

@@ -0,0 +1,13 @@
+package com.exness.promotion.service;
+
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.exness.promotion.pojo.LangDict;
+
+import java.util.List;
+
+public interface LangDictService extends IService<LangDict> {
+    List<LangDict> getList();
+    boolean add(LangDict entity);
+    boolean edit(LangDict entity);
+    boolean del(Long id);
+}

+ 13 - 0
src/main/java/com/exness/promotion/service/MaterialCategoryService.java

@@ -0,0 +1,13 @@
+package com.exness.promotion.service;
+
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.exness.promotion.pojo.MaterialCategory;
+
+import java.util.List;
+
+public interface MaterialCategoryService extends IService<MaterialCategory> {
+    List<MaterialCategory> getList();
+    boolean add(MaterialCategory entity);
+    boolean edit(MaterialCategory entity);
+    boolean del(Long id);
+}

+ 13 - 0
src/main/java/com/exness/promotion/service/MaterialItemService.java

@@ -0,0 +1,13 @@
+package com.exness.promotion.service;
+
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.exness.promotion.pojo.MaterialItem;
+
+import java.util.List;
+
+public interface MaterialItemService extends IService<MaterialItem> {
+    List<MaterialItem> getListByMainId(Long mainId);
+    boolean add(MaterialItem entity);
+    boolean edit(MaterialItem entity);
+    boolean del(Long id);
+}

+ 28 - 0
src/main/java/com/exness/promotion/service/MaterialMainService.java

@@ -0,0 +1,28 @@
+package com.exness.promotion.service;
+
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.exness.promotion.common.Result;
+import com.exness.promotion.pojo.MaterialMain;
+import com.exness.promotion.vo.req.FileMaterialUploadReqVo;
+import com.exness.promotion.vo.req.LandingMaterialReqVo;
+import com.exness.promotion.vo.req.MaterialMainSearchReqVo;
+import com.github.pagehelper.PageInfo;
+
+public interface MaterialMainService extends IService<MaterialMain> {
+    /**
+     * 文件素材上传:BANNER / GIF / VIDEO
+     */
+    MaterialMain add(FileMaterialUploadReqVo uploadVo);
+
+    /**
+     * 新增着陆页素材(仅链接、无文件、无规格)
+     */
+    Result<MaterialMain> addLandingMaterial(LandingMaterialReqVo landingMaterialReqVo);
+
+    /**
+     * 逻辑删除主素材 + 批量删除下属所有item
+     */
+    Result<Boolean> removeMaterialAll(Long mainId);
+
+    PageInfo search(MaterialMainSearchReqVo searchVo);
+}

+ 13 - 0
src/main/java/com/exness/promotion/service/SpecDictService.java

@@ -0,0 +1,13 @@
+package com.exness.promotion.service;
+
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.exness.promotion.pojo.SpecDict;
+
+import java.util.List;
+
+public interface SpecDictService extends IService<SpecDict> {
+    List<SpecDict> getList();
+    boolean add(SpecDict entity);
+    boolean edit(SpecDict entity);
+    boolean del(Long id);
+}

+ 14 - 0
src/main/java/com/exness/promotion/service/SysFileService.java

@@ -0,0 +1,14 @@
+package com.exness.promotion.service;
+
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.exness.promotion.pojo.SysFile;
+import org.springframework.web.multipart.MultipartFile;
+
+public interface SysFileService extends IService<SysFile> {
+
+    // 上传文件
+    SysFile upload(MultipartFile file);
+
+    // 删除文件(数据库+MinIO)
+    boolean deleteFile(Long id);
+}

+ 72 - 0
src/main/java/com/exness/promotion/service/impl/BizTypeServiceImpl.java

@@ -0,0 +1,72 @@
+package com.exness.promotion.service.impl;
+
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.exness.promotion.mapper.BizTypeMapper;
+import com.exness.promotion.pojo.BizType;
+import com.exness.promotion.pojo.MaterialMain;
+import com.exness.promotion.service.BizTypeService;
+import com.exness.promotion.service.MaterialMainService;
+import com.exness.promotion.vo.resp.BizTypeSearchRespVo;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Lazy;
+import org.springframework.stereotype.Service;
+
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+@Service
+public class BizTypeServiceImpl extends ServiceImpl<BizTypeMapper, BizType> implements BizTypeService {
+
+    @Lazy
+    @Autowired
+    private MaterialMainService materialMainService;
+
+    @Override
+    public List<BizTypeSearchRespVo> getList() {
+        List<BizType> bizTypes = list();
+        List<BizTypeSearchRespVo> list = new LinkedList<>();
+        for (BizType bizType : bizTypes) {
+            BizTypeSearchRespVo vo = new BizTypeSearchRespVo();
+            vo.setId(bizType.getId());
+            vo.setTypeName(bizType.getTypeName());
+            vo.setCountMap(countByCategory(bizType.getId()));
+            list.add(vo);
+        }
+        return list;
+    }
+
+    /**
+     * 根据bizTypeId统计每种素材的数量(纯Java,无需SQL分组)
+     */
+    public Map<String, Long> countByCategory(Long bizTypeId) {
+        // 1. 查询该业务下所有素材
+        List<MaterialMain> list = materialMainService.lambdaQuery()
+                .eq(MaterialMain::getBizTypeId, bizTypeId)
+                .eq(MaterialMain::getDelFlag, 0)
+                .list();
+
+        // 2. Java 流式分组统计
+        return list.stream()
+                .collect(Collectors.groupingBy(
+                        MaterialMain::getCategoryCode,  // 按类型分组
+                        Collectors.counting()            // 统计数量
+                ));
+    }
+
+    @Override
+    public boolean add(BizType bizType) {
+        return save(bizType);
+    }
+
+    @Override
+    public boolean edit(BizType bizType) {
+        return updateById(bizType);
+    }
+
+    @Override
+    public boolean remove(Long id) {
+        return removeById(id);
+    }
+}

+ 34 - 0
src/main/java/com/exness/promotion/service/impl/LangDictServiceImpl.java

@@ -0,0 +1,34 @@
+package com.exness.promotion.service.impl;
+
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.exness.promotion.mapper.LangDictMapper;
+import com.exness.promotion.pojo.LangDict;
+import com.exness.promotion.service.LangDictService;
+import org.springframework.stereotype.Service;
+import java.util.List;
+
+@Service
+public class LangDictServiceImpl
+        extends ServiceImpl<LangDictMapper, LangDict>
+        implements LangDictService {
+
+    @Override
+    public List<LangDict> getList() {
+        return list();
+    }
+
+    @Override
+    public boolean add(LangDict entity) {
+        return save(entity);
+    }
+
+    @Override
+    public boolean edit(LangDict entity) {
+        return updateById(entity);
+    }
+
+    @Override
+    public boolean del(Long id) {
+        return removeById(id);
+    }
+}

+ 35 - 0
src/main/java/com/exness/promotion/service/impl/MaterialCategoryServiceImpl.java

@@ -0,0 +1,35 @@
+package com.exness.promotion.service.impl;
+
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.exness.promotion.mapper.MaterialCategoryMapper;
+import com.exness.promotion.pojo.MaterialCategory;
+import com.exness.promotion.service.MaterialCategoryService;
+import org.springframework.stereotype.Service;
+
+import java.util.List;
+
+@Service
+public class MaterialCategoryServiceImpl
+        extends ServiceImpl<MaterialCategoryMapper, MaterialCategory>
+        implements MaterialCategoryService {
+
+    @Override
+    public List<MaterialCategory> getList() {
+        return list();
+    }
+
+    @Override
+    public boolean add(MaterialCategory entity) {
+        return save(entity);
+    }
+
+    @Override
+    public boolean edit(MaterialCategory entity) {
+        return updateById(entity);
+    }
+
+    @Override
+    public boolean del(Long id) {
+        return removeById(id);
+    }
+}

+ 33 - 0
src/main/java/com/exness/promotion/service/impl/MaterialItemServiceImpl.java

@@ -0,0 +1,33 @@
+package com.exness.promotion.service.impl;
+
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.exness.promotion.mapper.MaterialItemMapper;
+import com.exness.promotion.pojo.MaterialItem;
+import com.exness.promotion.service.MaterialItemService;
+import org.springframework.stereotype.Service;
+import java.util.List;
+
+@Service
+public class MaterialItemServiceImpl extends ServiceImpl<MaterialItemMapper, MaterialItem> implements MaterialItemService {
+
+    @Override
+    public List<MaterialItem> getListByMainId(Long mainId) {
+        return list(Wrappers.lambdaQuery(MaterialItem.class).eq(MaterialItem::getMainId, mainId));
+    }
+
+    @Override
+    public boolean add(MaterialItem entity) {
+        return save(entity);
+    }
+
+    @Override
+    public boolean edit(MaterialItem entity) {
+        return updateById(entity);
+    }
+
+    @Override
+    public boolean del(Long id) {
+        return removeById(id);
+    }
+}

+ 186 - 0
src/main/java/com/exness/promotion/service/impl/MaterialMainServiceImpl.java

@@ -0,0 +1,186 @@
+package com.exness.promotion.service.impl;
+
+import cn.hutool.core.collection.CollectionUtil;
+import cn.hutool.core.util.StrUtil;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.exness.promotion.common.Result;
+import com.exness.promotion.enums.LanguageEnum;
+import com.exness.promotion.enums.SizeEnum;
+import com.exness.promotion.mapper.MaterialMainMapper;
+import com.exness.promotion.pojo.BizType;
+import com.exness.promotion.pojo.MaterialItem;
+import com.exness.promotion.pojo.MaterialMain;
+import com.exness.promotion.pojo.SysFile;
+import com.exness.promotion.service.BizTypeService;
+import com.exness.promotion.service.MaterialItemService;
+import com.exness.promotion.service.MaterialMainService;
+import com.exness.promotion.service.SysFileService;
+import com.exness.promotion.vo.req.FileMaterialUploadReqVo;
+import com.exness.promotion.vo.req.LandingMaterialReqVo;
+import com.exness.promotion.vo.req.MaterialMainSearchReqVo;
+import com.exness.promotion.vo.resp.MaterialMainSearchRespVo;
+import com.github.pagehelper.PageHelper;
+import com.github.pagehelper.PageInfo;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.util.*;
+import java.util.stream.Collectors;
+
+@Service
+public class MaterialMainServiceImpl extends ServiceImpl<MaterialMainMapper, MaterialMain> implements MaterialMainService {
+
+    @Autowired
+    private SysFileService fileService;
+
+    @Autowired
+    private MaterialItemService materialItemService;
+
+    @Autowired
+    private BizTypeService bizTypeService;
+
+    @Override
+    @Transactional
+    public MaterialMain add(FileMaterialUploadReqVo uploadVo) {
+        SysFile sysFile = fileService.getById(uploadVo.getFileId());
+        // 2. 新增素材主表
+        MaterialMain main = new MaterialMain();
+        main.setBizTypeId(uploadVo.getBizTypeId());
+        main.setCategoryCode(uploadVo.getCategoryCode());
+        main.setTitle(uploadVo.getTitle());
+        main.setSort(0);
+        main.setStatus(1);
+        save(main);
+
+        // 3. 新增明细item
+        MaterialItem item = new MaterialItem();
+        item.setMainId(main.getId());
+        item.setSpecCode(uploadVo.getSpecCode());
+        item.setLangCode(uploadVo.getLangCode());
+        item.setFileUrl(sysFile.getFileUrl());
+        item.setFileName(sysFile.getFileName());
+        item.setFileSize(sysFile.getFileSize());
+        item.setIsLanding(0);
+        materialItemService.save(item);
+        return main;
+    }
+
+    /**
+     * 着陆页新增
+     */
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public Result<MaterialMain> addLandingMaterial(LandingMaterialReqVo landingMaterialReqVo) {
+        // 主表
+        MaterialMain main = new MaterialMain();
+        main.setBizTypeId(landingMaterialReqVo.getBizTypeId());
+        main.setCategoryCode("LANDING");
+        main.setTitle(landingMaterialReqVo.getTitle());
+        main.setSort(0);
+        main.setStatus(1);
+        save(main);
+
+        // 明细
+        MaterialItem item = new MaterialItem();
+        item.setMainId(main.getId());
+        item.setSpecCode(null);
+        item.setLangCode(landingMaterialReqVo.getLangCode());
+        item.setFileUrl(landingMaterialReqVo.getLinkUrl());
+        item.setIsLanding(1);
+        materialItemService.save(item);
+
+        return Result.success("着陆页新增成功", main);
+    }
+
+    /**
+     * 联动删除:主表 + 所有子项
+     */
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public Result<Boolean> removeMaterialAll(Long mainId) {
+        // 删除子项
+        materialItemService.remove(Wrappers.lambdaQuery(MaterialItem.class)
+                .eq(MaterialItem::getMainId, mainId));
+        // 删除主表
+        boolean del = removeById(mainId);
+        return Result.success(del);
+    }
+
+    @Override
+    public PageInfo search(MaterialMainSearchReqVo searchVo) {
+        PageHelper.startPage(searchVo.getPageNo(), searchVo.getPageSize());
+        LambdaQueryWrapper<MaterialMain> queryWrapper = new LambdaQueryWrapper<>();
+        queryWrapper.eq(MaterialMain::getCategoryCode,searchVo.getCategoryCode());
+        if (searchVo.getBizTypeId() != null) {
+            queryWrapper.eq(MaterialMain::getBizTypeId, searchVo.getBizTypeId());
+        }
+        List<MaterialMain> mainList = list(queryWrapper);
+        PageInfo pageInfo = new PageInfo(mainList);
+        Set<Long> idSet = mainList.stream()
+                .map(MaterialMain::getId)
+                .collect(Collectors.toSet());
+        LambdaQueryWrapper<MaterialItem> itemQueryWrapper = new LambdaQueryWrapper<>();
+        itemQueryWrapper.in(MaterialItem::getMainId, idSet);
+        // 尺寸
+        if (StrUtil.isNotBlank(searchVo.getSpecCode())) {
+            List<String> list = StrUtil.splitTrim(searchVo.getSpecCode(), ",");
+            itemQueryWrapper.in(MaterialItem::getSpecCode, list);
+        }
+        // 语言
+        if (StrUtil.isNotBlank(searchVo.getLangCode())) {
+            List<String> list = StrUtil.splitTrim(searchVo.getLangCode(), ",");
+            itemQueryWrapper.in(MaterialItem::getLangCode, list);
+        }
+        List<MaterialItem> itemList = materialItemService.list(itemQueryWrapper);
+        Map<Long,List<MaterialItem>> itemMap = itemList.stream()
+                .collect(Collectors.groupingBy(MaterialItem::getMainId));
+        List<MaterialMainSearchRespVo> voList = new LinkedList<>();
+        mainList.stream().forEach(v -> {
+            MaterialMainSearchRespVo vo = new MaterialMainSearchRespVo();
+            vo.setId(v.getId());
+            // 素材类型 股票 金属
+            vo.setBizTypeName(queryBizTypeName(v.getBizTypeId()));
+            // 尺寸
+            vo.setSpecNameList(querySpecNames(itemMap.get(v.getId())));
+            // 语言
+            vo.setLangNameList(queryLangNames(itemMap.get(v.getId())));
+            vo.setItemList(itemMap.get(v.getId()));
+            voList.add(vo);
+        });
+        pageInfo.setList(voList);
+        return pageInfo;
+    }
+
+
+    private String queryLangNames(List<MaterialItem> list) {
+        Set<String> langNames = new HashSet<>();
+        for (MaterialItem materialItem : list) {
+            langNames.add(LanguageEnum.getDescByCode(materialItem.getLangCode()));
+        }
+        return StrUtil.join(",", langNames);
+    }
+
+    private String querySpecNames(List<MaterialItem> list) {
+        Set<String> specNames = new HashSet<>();
+        for (MaterialItem materialItem : list) {
+            if (StrUtil.isBlank(materialItem.getSpecCode())) {
+                continue;
+            }
+            specNames.add(SizeEnum.getDescByCode(materialItem.getSpecCode()));
+        }
+        if(CollectionUtil.isEmpty(specNames)){
+            return null;
+        }
+        return StrUtil.join(",", specNames);
+    }
+
+    private String queryBizTypeName(Long bizTypeId) {
+        List<BizType> bizTypes = bizTypeService.list();
+        Map<Long, BizType> bizTypeMap = bizTypes.stream()
+                .collect(Collectors.toMap(BizType::getId, v -> v));
+        return bizTypeMap.get(bizTypeId).getTypeName();
+    }
+}

+ 34 - 0
src/main/java/com/exness/promotion/service/impl/SpecDictServiceImpl.java

@@ -0,0 +1,34 @@
+package com.exness.promotion.service.impl;
+
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.exness.promotion.mapper.SpecDictMapper;
+import com.exness.promotion.pojo.SpecDict;
+import com.exness.promotion.service.SpecDictService;
+import org.springframework.stereotype.Service;
+import java.util.List;
+
+@Service
+public class SpecDictServiceImpl
+        extends ServiceImpl<SpecDictMapper, SpecDict>
+        implements SpecDictService {
+
+    @Override
+    public List<SpecDict> getList() {
+        return list();
+    }
+
+    @Override
+    public boolean add(SpecDict entity) {
+        return save(entity);
+    }
+
+    @Override
+    public boolean edit(SpecDict entity) {
+        return updateById(entity);
+    }
+
+    @Override
+    public boolean del(Long id) {
+        return removeById(id);
+    }
+}

+ 73 - 0
src/main/java/com/exness/promotion/service/impl/SysFileServiceImpl.java

@@ -0,0 +1,73 @@
+package com.exness.promotion.service.impl;
+
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.exness.promotion.mapper.SysFileMapper;
+import com.exness.promotion.pojo.SysFile;
+import com.exness.promotion.service.SysFileService;
+import com.exness.promotion.util.MinioUtil;
+import lombok.RequiredArgsConstructor;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.time.LocalDateTime;
+
+@Service
+@RequiredArgsConstructor
+public class SysFileServiceImpl extends ServiceImpl<SysFileMapper, SysFile> implements SysFileService {
+
+    private final MinioUtil minioUtil;
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public SysFile upload(MultipartFile file) {
+        try {
+            String url = minioUtil.upload(file);
+            String fileName = url.substring(url.lastIndexOf("/") + 1);
+
+            return saveFile(file, fileName, url, 1);
+        } catch (Exception e) {
+            throw new RuntimeException("上传失败:" + e.getMessage());
+        }
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public boolean deleteFile(Long id) {
+        SysFile sysFile = getById(id);
+        if (sysFile == null) return false;
+
+        try {
+            // 1. 删除 MinIO 中的文件
+//            minioUtil.delete(sysFile.getFileUrl().substring(sysFile.getFileUrl().lastIndexOf("/") + 1));
+
+            // 2. 逻辑删除数据库记录
+            return removeById(id);
+        } catch (Exception e) {
+            throw new RuntimeException("删除失败:" + e.getMessage());
+        }
+    }
+
+    // 保存文件信息到数据库
+    private SysFile saveFile(MultipartFile file, String minioName, String url, int isPublic) {
+        SysFile sysFile = new SysFile();
+        sysFile.setFileName(file.getOriginalFilename());
+        sysFile.setFileSuffix(getSuffix(file.getOriginalFilename()));
+        sysFile.setFileUrl(url);
+        sysFile.setFileSize(file.getSize());
+        sysFile.setBucketName(minioUtil.getBucketName());
+        sysFile.setIsPublic(isPublic);
+        sysFile.setCreateBy("admin");
+        sysFile.setCreateTime(LocalDateTime.now());
+        sysFile.setDelFlag(0);
+
+        save(sysFile);
+        return sysFile;
+    }
+
+    // 获取文件后缀
+    private String getSuffix(String fileName) {
+        if (fileName == null || !fileName.contains(".")) return "";
+        return fileName.substring(fileName.lastIndexOf(".") + 1);
+    }
+}

+ 105 - 0
src/main/java/com/exness/promotion/util/MinioUtil.java

@@ -0,0 +1,105 @@
+package com.exness.promotion.util;
+
+import io.minio.*;
+import io.minio.http.Method;
+import lombok.Getter;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Component;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.io.InputStream;
+import java.util.UUID;
+
+@Component
+public class MinioUtil {
+
+    @Autowired
+    private MinioClient minioClient;
+
+    @Getter
+    @Value("${minio.bucketName}")
+    private String bucketName;
+
+    @Value("${minio.endpoint}")
+    private String endpoint;
+
+    /**
+     * 判断桶是否存在,不存在则创建
+     */
+    public void createBucketIfNotExists() throws Exception {
+        boolean exists = minioClient.bucketExists(
+                BucketExistsArgs.builder().bucket(bucketName).build()
+        );
+        if (!exists) {
+            minioClient.makeBucket(
+                    MakeBucketArgs.builder().bucket(bucketName).build()
+            );
+        }
+    }
+
+    /**
+     * 上传文件
+     */
+    public String upload(MultipartFile file) throws Exception {
+        createBucketIfNotExists();
+
+        // 生成唯一文件名
+        String originalName = file.getOriginalFilename();
+        String fileName = UUID.randomUUID() + "_" + originalName;
+        InputStream inputStream = file.getInputStream();
+
+        minioClient.putObject(
+                PutObjectArgs.builder()
+                        .bucket(bucketName)
+                        .object(fileName)
+                        .stream(inputStream, file.getSize(), -1)
+                        .contentType(file.getContentType())
+                        .build()
+        );
+
+        return fileName;
+    }
+
+    /**
+     * 获取文件预览/下载地址(7天有效期)
+     */
+    public String getPreviewUrl(String fileName) throws Exception {
+        return minioClient.getPresignedObjectUrl(
+                GetPresignedObjectUrlArgs.builder()
+                        .method(Method.GET)
+                        .bucket(bucketName)
+                        .object(fileName)
+                        .expiry(7, java.util.concurrent.TimeUnit.DAYS)
+                        .build()
+        );
+    }
+
+    /**
+     * 下载文件(获取文件流)
+     * @param fileName 存储在minio中的文件名
+     * @return 文件输入流
+     */
+    public InputStream downloadFile(String fileName) throws Exception {
+        // 获取文件流
+        return minioClient.getObject(
+                GetObjectArgs.builder()
+                        .bucket(bucketName)
+                        .object(fileName)
+                        .build()
+        );
+    }
+
+    /**
+     * 删除文件
+     */
+    public void delete(String fileName) throws Exception {
+        minioClient.removeObject(
+                RemoveObjectArgs.builder()
+                        .bucket(bucketName)
+                        .object(fileName)
+                        .build()
+        );
+    }
+
+}

+ 9 - 0
src/main/java/com/exness/promotion/vo/BaseVo.java

@@ -0,0 +1,9 @@
+package com.exness.promotion.vo;
+
+import lombok.Data;
+
+@Data
+public class BaseVo {
+    private Integer pageNo = 1;
+    private Integer pageSize = 10;
+}

+ 13 - 0
src/main/java/com/exness/promotion/vo/LabelValueVO.java

@@ -0,0 +1,13 @@
+package com.exness.promotion.vo;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+public class LabelValueVO {
+    private String label;
+    private String value;
+}

+ 44 - 0
src/main/java/com/exness/promotion/vo/req/FileMaterialUploadReqVo.java

@@ -0,0 +1,44 @@
+package com.exness.promotion.vo.req;
+
+import lombok.Data;
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.NotNull;
+@Data
+public class FileMaterialUploadReqVo {
+
+    /**
+     * 业务类型 股票
+     */
+    @NotNull(message = "业务类型 bizTypeId 不能为空")
+    private Long bizTypeId;
+
+    /**
+     * 素材分类编码 网幅广告 视频
+     */
+    @NotBlank(message = "素材分类编码 categoryCode 不能为空")
+    private String categoryCode;
+
+    /**
+     * 素材标题
+     */
+    @NotBlank(message = "素材标题 title 不能为空")
+    private String title;
+
+    /**
+     * 规格编码(尺寸/比例)
+     */
+    @NotBlank(message = "规格编码 specCode 不能为空")
+    private String specCode;
+
+    /**
+     * 语言编码
+     */
+    @NotBlank(message = "语言编码 langCode 不能为空")
+    private String langCode;
+
+    /**
+     * 文件ID
+     */
+    @NotNull(message = "文件ID fileId 不能为空")
+    private Long fileId;
+}

+ 22 - 0
src/main/java/com/exness/promotion/vo/req/LandingMaterialReqVo.java

@@ -0,0 +1,22 @@
+package com.exness.promotion.vo.req;
+
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.NotNull;
+import lombok.Data;
+
+/**
+ * 着陆页
+ */
+@Data
+public class LandingMaterialReqVo {
+    // 业务类型 股票
+    @NotNull(message = "业务类型 bizTypeId 不能为空")
+    private Long bizTypeId;
+    // 素材标题
+    private String title;
+    // 语言编码
+    @NotBlank(message = "语言编码 langCode 不能为空")
+    private String langCode;
+    @NotBlank(message = "着陆页链接 linkUrl 不能为空")
+    private String linkUrl;
+}

+ 20 - 0
src/main/java/com/exness/promotion/vo/req/MaterialMainSearchReqVo.java

@@ -0,0 +1,20 @@
+package com.exness.promotion.vo.req;
+
+import com.exness.promotion.vo.BaseVo;
+import jakarta.validation.constraints.NotBlank;
+import lombok.Data;
+
+@Data
+public class MaterialMainSearchReqVo extends BaseVo {
+    /** 业务类型ID */
+    private Long bizTypeId;
+    // 规格编码(尺寸/比例)
+    private String specCode;
+    /**
+     * 素材分类编码 网幅广告 视频
+     */
+    @NotBlank(message = "素材分类编码 categoryCode 不能为空")
+    private String categoryCode;
+    // 语言编码
+    private String langCode;
+}

+ 12 - 0
src/main/java/com/exness/promotion/vo/resp/BizTypeSearchRespVo.java

@@ -0,0 +1,12 @@
+package com.exness.promotion.vo.resp;
+
+import lombok.Data;
+
+import java.util.Map;
+
+@Data
+public class BizTypeSearchRespVo {
+    private Long id;
+    private String typeName;
+    private Map<String, Long> countMap;
+}

+ 20 - 0
src/main/java/com/exness/promotion/vo/resp/MaterialMainSearchRespVo.java

@@ -0,0 +1,20 @@
+package com.exness.promotion.vo.resp;
+
+import com.exness.promotion.pojo.MaterialItem;
+import lombok.Data;
+
+import java.util.List;
+
+@Data
+public class MaterialMainSearchRespVo{
+    // 素材ID
+    private Long id;
+    // 素材类型
+    private String bizTypeName;
+    // 语言
+    private String langNameList;
+    // 尺寸
+    private String specNameList;
+    // 素材明细
+    private List<MaterialItem> itemList;
+}

+ 34 - 0
src/main/resources/application.yml

@@ -0,0 +1,34 @@
+spring:
+  # 数据库配置
+  datasource:
+    driver-class-name: com.mysql.cj.jdbc.Driver
+    url: jdbc:mysql://103.214.175.29:28571/promotion?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai&allowMultiQueries=true&useSSL=false
+    username: root
+    password: NSH01Y0GTmUNjgg6xw80qg==
+  servlet:
+    multipart:
+      max-file-size: 100MB
+      max-request-size: 100MB
+
+# MyBatis-Plus 配置
+mybatis-plus:
+  # mapper.xml 文件路径
+  mapper-locations: classpath:mapper/*.xml
+  # 实体类别名包
+  type-aliases-package: com.exness.promotion.pojo
+  configuration:
+    # 数据库下划线转驼峰
+    map-underscore-to-camel-case: true
+    # 打印SQL语句
+    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
+  global-config:
+    db-config:
+      # 主键自增
+      id-type: auto
+
+# MinIO 配置
+minio:
+  endpoint: http://103.214.175.29:9002    # MinIO服务地址
+  accessKey: admin                   # 用户名
+  secretKey: 12345678            # 密码
+  bucketName: test             # 存储桶名称

+ 7 - 0
src/main/resources/mapper/MaterialMainMapper.xml

@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE mapper
+        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
+        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.exness.promotion.mapper.MaterialMainMapper">
+
+</mapper>

+ 13 - 0
src/test/java/com/exness/promotion/PromotionApplicationTests.java

@@ -0,0 +1,13 @@
+package com.exness.promotion;
+
+import org.junit.jupiter.api.Test;
+import org.springframework.boot.test.context.SpringBootTest;
+
+@SpringBootTest
+class PromotionApplicationTests {
+
+    @Test
+    void contextLoads() {
+    }
+
+}