init
This commit is contained in:
91
timeline-story-service/pom.xml
Normal file
91
timeline-story-service/pom.xml
Normal file
@@ -0,0 +1,91 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>com.timeline</groupId>
|
||||
<artifactId>timeline</artifactId>
|
||||
<version>0.0.1-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>timeline-story-service</artifactId>
|
||||
|
||||
<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>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.mybatis</groupId>
|
||||
<artifactId>mybatis</artifactId>
|
||||
<version>3.5.17</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.timeline</groupId>
|
||||
<artifactId>timeline-component-common</artifactId>
|
||||
<version>0.0.1-SNAPSHOT</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<!-- Spring Boot Actuator -->
|
||||
<!-- Spring Boot -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-actuator</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-starter-openfeign</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
|
||||
</dependency>
|
||||
<!-- Spring Cloud -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-starter-bootstrap</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-actuator</artifactId>
|
||||
</dependency>
|
||||
<!-- 数据库相关 -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-data-jdbc</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.mybatis.spring.boot</groupId>
|
||||
<artifactId>mybatis-spring-boot-starter</artifactId>
|
||||
<version>3.0.4</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-databind</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.alibaba</groupId>
|
||||
<artifactId>fastjson</artifactId>
|
||||
<version>1.2.83</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.mysql</groupId>
|
||||
<artifactId>mysql-connector-j</artifactId>
|
||||
<version>8.2.0</version>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
@@ -0,0 +1,14 @@
|
||||
package com.timeline.story;
|
||||
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.cloud.openfeign.EnableFeignClients;
|
||||
import org.springframework.context.annotation.ComponentScan;
|
||||
|
||||
@SpringBootApplication
|
||||
@ComponentScan(basePackages = {"com.timeline"})
|
||||
@EnableFeignClients(basePackages = {"com.timeline"})
|
||||
public class TimelineStoryServiceApplication {
|
||||
public static void main(String[] args) {
|
||||
org.springframework.boot.SpringApplication.run(TimelineStoryServiceApplication.class, args);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package com.timeline.story.config;
|
||||
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.web.servlet.config.annotation.CorsRegistry;
|
||||
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
|
||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||
|
||||
@Configuration
|
||||
@EnableWebMvc
|
||||
public class CorsConfig implements WebMvcConfigurer {
|
||||
|
||||
@Override
|
||||
public void addCorsMappings(CorsRegistry registry) {
|
||||
registry.addMapping("/api/**")
|
||||
.allowedOrigins("http://localhost:8000")
|
||||
.allowedMethods("GET", "POST", "PUT", "DELETE")
|
||||
.allowCredentials(true)
|
||||
.maxAge(3600);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package com.timeline.story.config;
|
||||
|
||||
import feign.codec.Encoder;
|
||||
import feign.form.spring.SpringFormEncoder;
|
||||
import org.springframework.boot.autoconfigure.http.HttpMessageConverters;
|
||||
import org.springframework.cloud.openfeign.support.SpringEncoder;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
@Configuration
|
||||
public class FeignConfig {
|
||||
|
||||
@Bean
|
||||
public Encoder feignEncoder() {
|
||||
return new SpringFormEncoder();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package com.timeline.story.config;
|
||||
|
||||
import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
|
||||
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.time.format.DateTimeFormatterBuilder;
|
||||
import java.util.Locale;
|
||||
|
||||
@Configuration
|
||||
public class JacksonConfig implements Jackson2ObjectMapperBuilderCustomizer {
|
||||
@Override
|
||||
public void customize(Jackson2ObjectMapperBuilder jackson2ObjectMapperBuilder) {
|
||||
DateTimeFormatter formatter = new DateTimeFormatterBuilder()
|
||||
.appendPattern("yyyy-MM-dd HH:mm:ss")
|
||||
.toFormatter(Locale.CHINA);
|
||||
|
||||
jackson2ObjectMapperBuilder.simpleDateFormat("yyyy-MM-dd HH:mm:ss");
|
||||
jackson2ObjectMapperBuilder.timeZone(java.util.TimeZone.getTimeZone("UTC"));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package com.timeline.story.controller;
|
||||
|
||||
import com.timeline.response.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/ping")
|
||||
public class HealthController {
|
||||
@GetMapping
|
||||
public ResponseEntity<String> ping(){
|
||||
return ResponseEntity.success("pong");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
package com.timeline.story.controller;
|
||||
|
||||
import com.timeline.response.ResponseEntity;
|
||||
import com.timeline.story.entity.Story;
|
||||
import com.timeline.story.service.StoryService;
|
||||
import com.timeline.story.vo.StoryVo;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/story")
|
||||
@Slf4j
|
||||
public class StoryController {
|
||||
|
||||
@Autowired
|
||||
private StoryService storyService;
|
||||
@GetMapping
|
||||
public ResponseEntity<String> hello() {
|
||||
return ResponseEntity.success("hello");
|
||||
}
|
||||
@GetMapping("/test")
|
||||
public ResponseEntity<String> test() {
|
||||
return ResponseEntity.success("Test endpoint");
|
||||
}
|
||||
@PostMapping("/add")
|
||||
public ResponseEntity<String> createStory(@RequestBody StoryVo storyVo) {
|
||||
log.info("创建故事: {}", storyVo);
|
||||
storyService.createStory(storyVo);
|
||||
return ResponseEntity.success("故事创建成功");
|
||||
}
|
||||
|
||||
@PutMapping("/{storyId}")
|
||||
public ResponseEntity<String> updateStory(@RequestBody StoryVo storyVo, @PathVariable String storyId) {
|
||||
log.info("更新故事: {},ID: {}", storyVo, storyId);
|
||||
storyService.updateStory(storyVo, storyId);
|
||||
return ResponseEntity.success("故事更新成功");
|
||||
}
|
||||
|
||||
@DeleteMapping("/{storyId}")
|
||||
public ResponseEntity<String> deleteStory(@PathVariable String storyId) {
|
||||
log.info("删除故事, ID: {}", storyId);
|
||||
storyService.deleteStory(storyId);
|
||||
return ResponseEntity.success("故事删除成功");
|
||||
}
|
||||
|
||||
@GetMapping("/{storyId}")
|
||||
public ResponseEntity<Story> getStoryById(@PathVariable String storyId) {
|
||||
log.info("获取故事详情, ID: {}", storyId);
|
||||
Story story = storyService.getStoryById(storyId);
|
||||
return ResponseEntity.success(story);
|
||||
}
|
||||
|
||||
@GetMapping("/owner/{ownerId}")
|
||||
public ResponseEntity<List<Story>> getStoriesByOwnerId(@PathVariable String ownerId) {
|
||||
log.info("查询用户故事列表, 用户ID: {}", ownerId);
|
||||
List<Story> stories = storyService.getStoriesByOwnerId(ownerId);
|
||||
return ResponseEntity.success(stories);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
package com.timeline.story.controller;
|
||||
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import com.timeline.response.ResponseEntity;
|
||||
import com.timeline.story.entity.StoryItem;
|
||||
import com.timeline.story.service.StoryItemService;
|
||||
import com.timeline.story.vo.StoryItemVo;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/story/item")
|
||||
@Slf4j
|
||||
public class StoryItemController {
|
||||
|
||||
@Autowired
|
||||
private StoryItemService storyItemService;
|
||||
|
||||
@PostMapping()
|
||||
public ResponseEntity<String> createItem(@RequestParam("storyItem") String storyItemVoString, @RequestParam("cover") MultipartFile cover, @RequestParam("images") List<MultipartFile> images) {
|
||||
|
||||
log.info("创建 StoryItem,{}", storyItemVoString);
|
||||
storyItemService.createItemWithCover(JSONObject.parseObject(storyItemVoString, StoryItemVo.class), cover, images);
|
||||
return ResponseEntity.success("StoryItem 创建成功");
|
||||
}
|
||||
|
||||
@PutMapping("/{itemId}")
|
||||
public ResponseEntity<String> updateItem(@PathVariable String itemId,
|
||||
@RequestParam String description,
|
||||
@RequestParam String location) {
|
||||
log.info("更新 StoryItem: {}", itemId);
|
||||
storyItemService.updateItem(itemId, description, location);
|
||||
return ResponseEntity.success("StoryItem 更新成功");
|
||||
}
|
||||
|
||||
@DeleteMapping("/{itemId}")
|
||||
public ResponseEntity<String> deleteItem(@PathVariable String itemId) {
|
||||
log.info("删除 StoryItem: {}", itemId);
|
||||
storyItemService.deleteItem(itemId);
|
||||
return ResponseEntity.success("StoryItem 删除成功");
|
||||
}
|
||||
|
||||
@GetMapping("/{itemId}")
|
||||
public ResponseEntity<StoryItem> getItemById(@PathVariable String itemId) {
|
||||
log.info("获取 StoryItem 详情: {}", itemId);
|
||||
StoryItem item = storyItemService.getItemById(itemId);
|
||||
return ResponseEntity.success(item);
|
||||
}
|
||||
|
||||
@GetMapping("/list")
|
||||
public ResponseEntity<List<StoryItem>> getItemsByMasterItem(@RequestParam String masterItemId) {
|
||||
log.info("查询 StoryItem 列表,masterItemId: {}", masterItemId);
|
||||
List<StoryItem> items = storyItemService.getItemsByMasterItem(masterItemId);
|
||||
return ResponseEntity.success(items);
|
||||
}
|
||||
@GetMapping("/images/{itemId}")
|
||||
public ResponseEntity<List<String>> getStoryItemImages(@PathVariable String itemId) {
|
||||
log.info("获取 StoryItem 图片列表,itemId: {}", itemId);
|
||||
List<String> images = storyItemService.getStoryItemImages(itemId);
|
||||
return ResponseEntity.success(images);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package com.timeline.story.dao;
|
||||
|
||||
import com.timeline.dto.CommonRelationDTO;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Mapper
|
||||
public interface CommonRelationMapper {
|
||||
void insertImageStoryItemRelation(String imageInstanceId, String storyItemId, String userId);
|
||||
void insertRelation(CommonRelationDTO relationData);
|
||||
List<String> getImagesByStoryItemId(String storyItemId);
|
||||
|
||||
List<String> getStoryItemsByImageInstanceId(String imageInstanceId);
|
||||
|
||||
void deleteImageStoryItemRelation(String imageInstanceId, String storyItemId);
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package com.timeline.story.dao;
|
||||
|
||||
import com.timeline.story.entity.StoryItem;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Mapper
|
||||
public interface StoryItemMapper {
|
||||
void insert(StoryItem storyItem);
|
||||
void update(StoryItem storyItem);
|
||||
void deleteById(String itemId);
|
||||
StoryItem selectById(String itemId);
|
||||
List<StoryItem> selectByMasterItem(String masterItemId);
|
||||
List<String> selectImagesByItemId(String itemId);
|
||||
List<StoryItem> selectByMasterItemWithSubItems(String masterItemId);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package com.timeline.story.dao;
|
||||
|
||||
import com.timeline.story.entity.Story;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Mapper
|
||||
public interface StoryMapper {
|
||||
void insert(Story story);
|
||||
void update(Story story);
|
||||
void deleteByInstanceId(String instanceId);
|
||||
Story selectById(String instanceId);
|
||||
List<Story> selectByOwnerId(String ownerId);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package com.timeline.story.entity;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Data
|
||||
public class CommonRelationDTO {
|
||||
private Integer id;
|
||||
private String relaId;
|
||||
private String subRelaId;
|
||||
private Integer relationType;
|
||||
private String userId;
|
||||
private LocalDateTime createTime;
|
||||
private LocalDateTime updateTime;
|
||||
private Integer isDeleted;
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package com.timeline.story.entity;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Data
|
||||
public class Story {
|
||||
private String instanceId;
|
||||
private String title;
|
||||
private String description;
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private LocalDateTime createTime;
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private LocalDateTime updateTime;
|
||||
private String updateId;
|
||||
private Integer isDelete;
|
||||
private String ownerId;
|
||||
private String status;
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package com.timeline.story.entity;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import lombok.Data;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Data
|
||||
public class StoryItem {
|
||||
private String itemId;
|
||||
private String masterItem;
|
||||
private String title;
|
||||
private String description;
|
||||
private String location;
|
||||
private String createId;
|
||||
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private LocalDateTime createTime;
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private LocalDateTime updateTime;
|
||||
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private LocalDateTime storyItemTime;
|
||||
private Integer isRoot;
|
||||
private Integer isDelete;
|
||||
private String coverInstanceId;
|
||||
private StoryItem[] subItems;
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package com.timeline.story.feign;
|
||||
|
||||
import com.timeline.response.ResponseEntity;
|
||||
import org.springframework.cloud.openfeign.FeignClient;
|
||||
import org.springframework.core.io.InputStreamResource;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.util.List;
|
||||
|
||||
@FeignClient(name = "timeline.file", url = "${file.service.url}")
|
||||
public interface FileServiceClient {
|
||||
|
||||
@PostMapping(value = "/upload-cover", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
|
||||
ResponseEntity<String> uploadCover(@RequestPart("cover") MultipartFile cover);
|
||||
|
||||
@GetMapping("/download/cover/{coverKey}")
|
||||
InputStreamResource downloadCover(@PathVariable String coverKey);
|
||||
|
||||
@PostMapping("/associate-image")
|
||||
ResponseEntity<String> associateImageWithStoryItem(
|
||||
@RequestParam String imageInstanceId,
|
||||
@RequestParam String storyItemId,
|
||||
@RequestParam String userId);
|
||||
|
||||
@GetMapping("/story-item/images/{storyItemId}")
|
||||
ResponseEntity<List<String>> getStoryItemImages(@PathVariable String storyItemId);
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package com.timeline.story.service;
|
||||
|
||||
import com.timeline.story.entity.StoryItem;
|
||||
import com.timeline.story.vo.StoryItemVo;
|
||||
import com.timeline.story.vo.StoryItemWithCoverVo;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface StoryItemService {
|
||||
void createItem(StoryItemVo storyItemVo);
|
||||
void createItemWithCover(StoryItemVo storyItemVo, MultipartFile cover, List<MultipartFile> images);
|
||||
StoryItemWithCoverVo getStoryItemWithCover(String itemId);
|
||||
void updateItem(String itemId, String description, String location);
|
||||
void deleteItem(String itemId);
|
||||
StoryItem getItemById(String itemId);
|
||||
List<StoryItem> getItemsByMasterItem(String masterItemId);
|
||||
List<String> getStoryItemImages(String storyItemId);
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package com.timeline.story.service;
|
||||
|
||||
import com.timeline.story.entity.Story;
|
||||
import com.timeline.story.vo.StoryVo;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface StoryService {
|
||||
void createStory(StoryVo storyVo);
|
||||
void updateStory(StoryVo storyVo, String storyId);
|
||||
void deleteStory(String storyId);
|
||||
Story getStoryById(String storyId);
|
||||
List<Story> getStoriesByOwnerId(String ownerId);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,174 @@
|
||||
package com.timeline.story.service.impl;
|
||||
|
||||
import com.timeline.constants.CommonConstants;
|
||||
import com.timeline.story.dao.CommonRelationMapper;
|
||||
import com.timeline.dto.CommonRelationDTO;
|
||||
import com.timeline.exception.CustomException;
|
||||
import com.timeline.response.ResponseEntity;
|
||||
import com.timeline.response.ResponseEnum;
|
||||
import com.timeline.story.dao.StoryItemMapper;
|
||||
import com.timeline.story.entity.StoryItem;
|
||||
import com.timeline.story.feign.FileServiceClient;
|
||||
import com.timeline.story.service.StoryItemService;
|
||||
import com.timeline.story.vo.StoryItemVo;
|
||||
import com.timeline.story.vo.StoryItemWithCoverVo;
|
||||
import com.timeline.utils.IdUtils;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.core.io.InputStreamResource;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
|
||||
@Slf4j
|
||||
@Service
|
||||
public class StoryItemServiceImpl implements StoryItemService {
|
||||
|
||||
@Autowired
|
||||
private StoryItemMapper storyItemMapper;
|
||||
@Autowired
|
||||
private FileServiceClient fileServiceClient;
|
||||
@Autowired
|
||||
private CommonRelationMapper commonRelationMapper;
|
||||
|
||||
@Override
|
||||
public void createItem(StoryItemVo storyItemVo) {
|
||||
try {
|
||||
StoryItem item = new StoryItem();
|
||||
item.setItemId(IdUtils.randomUuidUpper());
|
||||
item.setMasterItem(storyItemVo.getMasterItemId());
|
||||
item.setDescription(storyItemVo.getDescription());
|
||||
item.setTitle(storyItemVo.getTitle());
|
||||
item.setLocation(storyItemVo.getLocation());
|
||||
item.setCreateId("createId");
|
||||
item.setIsRoot(storyItemVo.getIsRoot());
|
||||
item.setIsDelete(0);
|
||||
item.setCreateTime(LocalDateTime.now());
|
||||
item.setUpdateTime(LocalDateTime.now());
|
||||
item.setStoryItemTime(storyItemVo.getStoryItemTime());
|
||||
storyItemMapper.insert(item);
|
||||
} catch (Exception e) {
|
||||
log.error("创建 StoryItem 失败", e);
|
||||
throw new RuntimeException("创建 StoryItem 失败");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void createItemWithCover(StoryItemVo storyItemVo, MultipartFile cover, List<MultipartFile> images) {
|
||||
try {
|
||||
// 1. 上传封面到 file 服务
|
||||
ResponseEntity<String> coverResponse = fileServiceClient.uploadCover(cover);
|
||||
String coverKey = coverResponse.getData();
|
||||
log.info("上传成功,文件instanceId:{}", coverKey);
|
||||
|
||||
// 2. 创建 StoryItem 实体
|
||||
StoryItem item = new StoryItem();
|
||||
item.setItemId(IdUtils.randomUuidUpper());
|
||||
item.setMasterItem(storyItemVo.getMasterItemId());
|
||||
item.setTitle(storyItemVo.getTitle());
|
||||
item.setDescription(storyItemVo.getDescription());
|
||||
item.setLocation(storyItemVo.getLocation());
|
||||
item.setCreateId("createId");
|
||||
item.setStoryItemTime(storyItemVo.getStoryItemTime());
|
||||
item.setIsRoot(storyItemVo.getIsRoot());
|
||||
item.setIsDelete(0);
|
||||
item.setCoverInstanceId(coverKey);
|
||||
if (storyItemVo.getIsRoot() == CommonConstants.STORY_ITEM_IS_ROOT){
|
||||
item.setIsRoot(CommonConstants.STORY_ITEM_IS_ROOT);
|
||||
} else {
|
||||
item.setIsRoot(CommonConstants.STORY_ITEM_IS_NOT_ROOT);
|
||||
}
|
||||
|
||||
|
||||
// 3. 上传所有图片并建立关联
|
||||
for (MultipartFile image : images) {
|
||||
ResponseEntity<String> response = fileServiceClient.uploadCover(image);
|
||||
String key = response.getData();
|
||||
log.info("上传成功,文件instanceId:{}", key);
|
||||
// 4. 保存封面与 StoryItem 的关联关系
|
||||
buildStoryItemImageRelation(item.getItemId(), key);
|
||||
}
|
||||
// 4. 记录封面与 StoryItem 的关联关系
|
||||
buildStoryItemImageRelation(item.getItemId(), coverKey);
|
||||
// 3. 插入到 story_item 表
|
||||
storyItemMapper.insert(item);
|
||||
} catch (Exception e) {
|
||||
log.error("创建 StoryItem 并上传封面失败", e);
|
||||
throw new CustomException(ResponseEnum.INTERNAL_SERVER_ERROR, "上传封面失败");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public StoryItemWithCoverVo getStoryItemWithCover(String itemId) {
|
||||
StoryItem item = storyItemMapper.selectById(itemId);
|
||||
if (item == null) {
|
||||
throw new CustomException(ResponseEnum.NOT_FOUND, "未找到 StoryItem ");
|
||||
}
|
||||
InputStreamResource coverStream = null;
|
||||
if (item.getCoverInstanceId() != null) {
|
||||
// 从 file 服务下载封面流
|
||||
coverStream = fileServiceClient.downloadCover(item.getCoverInstanceId());
|
||||
|
||||
}
|
||||
return new StoryItemWithCoverVo(item, coverStream);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateItem(String itemId, String description, String location) {
|
||||
try {
|
||||
StoryItem item = storyItemMapper.selectById(itemId);
|
||||
if (item == null) {
|
||||
throw new RuntimeException("StoryItem 不存在");
|
||||
}
|
||||
item.setDescription(description);
|
||||
item.setLocation(location);
|
||||
item.setUpdateTime(LocalDateTime.now());
|
||||
storyItemMapper.update(item);
|
||||
} catch (Exception e) {
|
||||
log.error("更新 StoryItem 失败", e);
|
||||
throw new RuntimeException("更新 StoryItem 失败");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteItem(String itemId) {
|
||||
try {
|
||||
StoryItem item = storyItemMapper.selectById(itemId);
|
||||
if (item == null) {
|
||||
throw new RuntimeException("StoryItem 不存在");
|
||||
}
|
||||
storyItemMapper.deleteById(itemId);
|
||||
} catch (Exception e) {
|
||||
log.error("删除 StoryItem 失败", e);
|
||||
throw new RuntimeException("删除 StoryItem 失败");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public StoryItem getItemById(String itemId) {
|
||||
return storyItemMapper.selectById(itemId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<StoryItem> getItemsByMasterItem(String masterItemId) {
|
||||
return storyItemMapper.selectByMasterItemWithSubItems(masterItemId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getStoryItemImages(String storyItemId) {
|
||||
return storyItemMapper.selectImagesByItemId(storyItemId);
|
||||
}
|
||||
|
||||
private void buildStoryItemImageRelation(String storyItemId, String imageIds) {
|
||||
CommonRelationDTO relationDTO = new CommonRelationDTO();
|
||||
relationDTO.setRelaId(storyItemId);
|
||||
relationDTO.setSubRelaId(imageIds);
|
||||
relationDTO.setRelationType(CommonConstants.RELATION_STORY_ITEM_AND_IMAGE);
|
||||
relationDTO.setUserId("9999");
|
||||
relationDTO.setCreateTime(LocalDateTime.now());
|
||||
relationDTO.setUpdateTime(LocalDateTime.now());
|
||||
commonRelationMapper.insertRelation(relationDTO);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
package com.timeline.story.service.impl;
|
||||
|
||||
import com.timeline.exception.CustomException;
|
||||
import com.timeline.response.ResponseEnum;
|
||||
import com.timeline.story.entity.Story;
|
||||
import com.timeline.story.dao.StoryMapper;
|
||||
import com.timeline.story.service.StoryService;
|
||||
import com.timeline.story.vo.StoryVo;
|
||||
import com.timeline.utils.IdUtils;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
|
||||
@Slf4j
|
||||
@Service
|
||||
public class StoryServiceImpl implements StoryService {
|
||||
|
||||
@Autowired
|
||||
private StoryMapper storyMapper;
|
||||
|
||||
@Override
|
||||
public void createStory(StoryVo storyVo) {
|
||||
try {
|
||||
Story story = new Story();
|
||||
story.setOwnerId("test11");
|
||||
story.setTitle(storyVo.getTitle());
|
||||
story.setInstanceId(IdUtils.randomUuidUpper());
|
||||
story.setDescription(storyVo.getDescription());
|
||||
story.setStatus(storyVo.getStatus());
|
||||
story.setCreateTime(LocalDateTime.now());
|
||||
story.setUpdateTime(LocalDateTime.now());
|
||||
story.setIsDelete(0);
|
||||
storyMapper.insert(story);
|
||||
} catch (Exception e) {
|
||||
log.error("创建故事失败", e);
|
||||
throw new CustomException(500, "创建故事失败: " + e.toString());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateStory(StoryVo storyVo, String storyId) {
|
||||
|
||||
Story story = storyMapper.selectById(storyId);
|
||||
if (story == null) {
|
||||
throw new CustomException(ResponseEnum.NOT_FOUND);
|
||||
}
|
||||
|
||||
story.setTitle(storyVo.getTitle());
|
||||
story.setDescription(storyVo.getDescription());
|
||||
story.setStatus(storyVo.getStatus());
|
||||
story.setUpdateTime(LocalDateTime.now());
|
||||
storyMapper.update(story);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteStory(String storyId) {
|
||||
Story story = storyMapper.selectById(storyId);
|
||||
if (story == null) {
|
||||
throw new CustomException(ResponseEnum.NOT_FOUND);
|
||||
}
|
||||
storyMapper.deleteByInstanceId(storyId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Story getStoryById(String storyId) {
|
||||
Story story = storyMapper.selectById(storyId);
|
||||
if (story == null) {
|
||||
throw new CustomException(ResponseEnum.NOT_FOUND);
|
||||
}
|
||||
return story;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Story> getStoriesByOwnerId(String ownerId) {
|
||||
try {
|
||||
return storyMapper.selectByOwnerId(ownerId);
|
||||
} catch (Exception e) {
|
||||
log.error("查询用户故事列表失败", e);
|
||||
throw new CustomException(500, "查询用户故事列表失败");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package com.timeline.story.vo;
|
||||
|
||||
import lombok.Data;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
@Data
|
||||
public class StoryItemAddVo extends StoryItemVo{
|
||||
private MultipartFile cover;
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package com.timeline.story.vo;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Data
|
||||
|
||||
public class StoryItemVo {
|
||||
private String instanceId;
|
||||
private String masterItemId;
|
||||
private String title;
|
||||
private String description;
|
||||
private String location;
|
||||
private LocalDateTime storyItemTime;
|
||||
private Integer isRoot;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package com.timeline.story.vo;
|
||||
|
||||
import com.timeline.story.entity.StoryItem;
|
||||
import lombok.AllArgsConstructor;
|
||||
import org.springframework.core.io.InputStreamResource;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
public class StoryItemWithCoverVo {
|
||||
private StoryItem storyItem;
|
||||
private InputStreamResource coverStream;
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package com.timeline.story.vo;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class StoryVo {
|
||||
private String id;
|
||||
private String instanceId;
|
||||
private String ownerId;
|
||||
private String title;
|
||||
private String description;
|
||||
private String status;
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
spring.application.name=timeline.story
|
||||
spring.datasource.url=jdbc:mysql://8.137.148.196:33306/timeline?serverTimezone=UTC&allowPublicKeyRetrieval=true
|
||||
spring.datasource.username=root
|
||||
spring.datasource.password=your_password
|
||||
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
|
||||
# MyBatis ??
|
||||
mybatis.mapper-locations=classpath:mapper/*.xml
|
||||
mybatis.type-aliases-package=com.timeline.story.entity
|
||||
mybatis.configuration.log4j=true
|
||||
server.port=30001
|
||||
spring.web.mvc.use-trailing-slash=true
|
||||
mybatis.configuration.mapUnderscoreToCamelCase=true
|
||||
# ??????????
|
||||
logging.level.org.mybatis.spring=DEBUG
|
||||
logging.level.org.apache.ibatis=DEBUG
|
||||
|
||||
# LocalDateTime ???????
|
||||
spring.jackson.date-format=yyyy-MM-dd HH:mm:ss
|
||||
spring.jackson.time-zone=UTC
|
||||
|
||||
file.service.url=http://localhost:30002/file/
|
||||
|
||||
spring.servlet.multipart.enabled=true
|
||||
spring.servlet.multipart.max-file-size=10MB
|
||||
spring.servlet.multipart.max-request-size=10MB
|
||||
@@ -0,0 +1,33 @@
|
||||
<?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.timeline.story.dao.CommonRelationMapper">
|
||||
|
||||
<insert id="insertImageStoryItemRelation">
|
||||
INSERT INTO common_relation (rela_id, sub_rela_id, rela_type, user_id)
|
||||
VALUES (#{imageInstanceId}, #{storyItemId}, 1, #{userId})
|
||||
</insert>
|
||||
|
||||
<select id="getImagesByStoryItemId" resultType="string">
|
||||
SELECT rela_id
|
||||
FROM common_relation
|
||||
WHERE sub_rela_id = #{storyItemId} AND rela_type = 1 AND is_delete = 0
|
||||
</select>
|
||||
|
||||
<select id="getStoryItemsByImageInstanceId" resultType="string">
|
||||
SELECT sub_rela_id
|
||||
FROM common_relation
|
||||
WHERE rela_id = #{imageInstanceId} AND rela_type = 1 AND is_delete = 0
|
||||
</select>
|
||||
|
||||
<update id="deleteImageStoryItemRelation">
|
||||
UPDATE common_relation
|
||||
SET is_delete = 1
|
||||
WHERE rela_id = #{imageInstanceId} AND sub_rela_id = #{storyItemId}
|
||||
</update>
|
||||
|
||||
<insert id="insertRelation">
|
||||
INSERT INTO common_relation (rela_id, sub_rela_id, rela_type, user_id)
|
||||
VALUES (#{relaId}, #{subRelaId}, #{relationType}, #{userId})
|
||||
</insert>
|
||||
</mapper>
|
||||
@@ -0,0 +1,88 @@
|
||||
<?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.timeline.story.dao.StoryItemMapper">
|
||||
|
||||
<insert id="insert">
|
||||
INSERT INTO story_item (item_id, master_item, description, location, title, create_id, is_root, is_delete, story_item_time, cover_instance_id)
|
||||
VALUES (#{itemId}, #{masterItem}, #{description}, #{location}, #{title},#{createId}, #{isRoot}, #{isDelete}, #{storyItemTime}, #{coverInstanceId})
|
||||
</insert>
|
||||
|
||||
<update id="update">
|
||||
UPDATE story_item
|
||||
SET description = #{description},
|
||||
location = #{location},
|
||||
create_id = #{createId},
|
||||
update_time = NOW()
|
||||
WHERE item_id = #{itemId}
|
||||
</update>
|
||||
|
||||
<delete id="deleteById">
|
||||
DELETE FROM story_item WHERE item_id = #{itemId}
|
||||
</delete>
|
||||
|
||||
<select id="selectById" resultType="com.timeline.story.entity.StoryItem">
|
||||
SELECT * FROM story_item WHERE item_id = #{itemId}
|
||||
</select>
|
||||
|
||||
<select id="selectByMasterItem" resultType="com.timeline.story.entity.StoryItem">
|
||||
SELECT * FROM story_item WHERE master_item = #{masterItemId} AND is_delete = 0
|
||||
</select>
|
||||
|
||||
<select id="selectImagesByItemId" resultType="java.lang.String">
|
||||
SELECT sub_rela_id FROM common_relation WHERE rela_id = #{itemId} AND rela_type = 5 AND is_delete = 0
|
||||
</select>
|
||||
<select id="selectByMasterItemWithSubItems" resultType="com.timeline.story.entity.StoryItem">
|
||||
WITH RECURSIVE StoryItemTree AS (
|
||||
SELECT
|
||||
item_id,
|
||||
master_item,
|
||||
title,
|
||||
description,
|
||||
location,
|
||||
create_id,
|
||||
create_time,
|
||||
update_time,
|
||||
story_item_time,
|
||||
is_root,
|
||||
is_delete,
|
||||
cover_instance_id,
|
||||
0 AS level,
|
||||
CAST(item_id AS CHAR(255)) AS path
|
||||
FROM
|
||||
story_item
|
||||
WHERE
|
||||
master_item = #{masterItemId}
|
||||
AND is_delete = 0
|
||||
|
||||
UNION ALL
|
||||
|
||||
SELECT
|
||||
si.item_id,
|
||||
si.master_item,
|
||||
si.title,
|
||||
si.description,
|
||||
si.location,
|
||||
si.create_id,
|
||||
si.create_time,
|
||||
si.update_time,
|
||||
si.story_item_time,
|
||||
si.is_root,
|
||||
si.is_delete,
|
||||
si.cover_instance_id,
|
||||
t.level + 1,
|
||||
CONCAT(t.path, '->', si.item_id)
|
||||
FROM
|
||||
story_item si
|
||||
INNER JOIN
|
||||
StoryItemTree t ON si.master_item = t.item_id
|
||||
WHERE
|
||||
si.is_delete = 0
|
||||
)
|
||||
SELECT * FROM StoryItemTree
|
||||
ORDER BY story_item_time ASC;
|
||||
</select>
|
||||
|
||||
|
||||
</mapper>
|
||||
@@ -0,0 +1,34 @@
|
||||
<?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.timeline.story.dao.StoryMapper">
|
||||
|
||||
<insert id="insert">
|
||||
INSERT INTO story (instance_id, title, description, owner_id, status)
|
||||
VALUES (#{instanceId}, #{title}, #{description}, #{ownerId}, #{status})
|
||||
</insert>
|
||||
|
||||
<update id="update">
|
||||
UPDATE story
|
||||
SET title = #{title},
|
||||
description = #{description},
|
||||
update_id = #{updateId},
|
||||
status = #{status},
|
||||
update_time = NOW()
|
||||
WHERE instance_id = #{instanceId}
|
||||
</update>
|
||||
|
||||
<delete id="deleteByInstanceId">
|
||||
UPDATE story SET story.is_delete = 1 WHERE instance_id = #{instanceId}
|
||||
</delete>
|
||||
|
||||
<select id="selectById" resultType="com.timeline.story.entity.Story">
|
||||
SELECT * FROM story WHERE instance_id = #{instanceId}
|
||||
</select>
|
||||
|
||||
<select id="selectByOwnerId" resultType="com.timeline.story.entity.Story">
|
||||
SELECT * FROM story WHERE owner_id = #{ownerId} AND is_delete = 0
|
||||
</select>
|
||||
|
||||
</mapper>
|
||||
Reference in New Issue
Block a user