This commit is contained in:
jiangh277
2025-07-22 23:00:39 +08:00
commit f8fb9b561c
59 changed files with 2456 additions and 0 deletions

View 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>

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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();
}
}

View File

@@ -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"));
}
}

View File

@@ -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");
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}

View File

@@ -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);
}

View File

@@ -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);
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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);
}

View File

@@ -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);
}

View File

@@ -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);
}

View File

@@ -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);
}
}

View File

@@ -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, "查询用户故事列表失败");
}
}
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>