This commit is contained in:
jiangh277
2025-12-24 14:17:19 +08:00
parent 3eb445291f
commit 4c7d59f87b
89 changed files with 3525 additions and 311 deletions

View File

@@ -2,8 +2,10 @@ 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 feign.RequestInterceptor;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@@ -14,4 +16,22 @@ public class FeignConfig {
public Encoder feignEncoder() {
return new SpringFormEncoder();
}
@Bean
public RequestInterceptor userHeaderInterceptor() {
return template -> {
RequestAttributes attrs = RequestContextHolder.getRequestAttributes();
if (attrs instanceof ServletRequestAttributes servletRequestAttributes) {
var req = servletRequestAttributes.getRequest();
String userId = req.getHeader("X-User-Id");
String username = req.getHeader("X-Username");
if (userId != null) {
template.header("X-User-Id", userId);
}
if (username != null) {
template.header("X-Username", username);
}
}
};
}
}

View File

@@ -0,0 +1,36 @@
package com.timeline.story.controller;
import com.timeline.common.response.ResponseEntity;
import com.timeline.story.dto.StoryItemActivityDto;
import com.timeline.story.entity.StoryActivity;
import com.timeline.story.service.StoryActivityService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
@Slf4j
@RestController
@RequestMapping("/story/activity")
public class StoryActivityController {
@Autowired
private StoryActivityService storyActivityService;
@GetMapping("/my-and-friends")
public ResponseEntity<List<StoryActivity>> myAndFriends() {
return ResponseEntity.success(storyActivityService.listMyAndFriendsActivities());
}
/**
* 查询当前用户拥有权限的 story 中storyItem 的创建/更新动态
*/
@GetMapping("/authorized-items")
public ResponseEntity<List<StoryItemActivityDto>> authorizedItemUpdates() {
return ResponseEntity.success(storyActivityService.listAuthorizedItemUpdates());
}
}

View File

@@ -6,6 +6,7 @@ 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.cloud.openfeign.SpringQueryMap;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@@ -59,4 +60,10 @@ public class StoryController {
List<Story> stories = storyService.getStoriesByOwnerId(ownerId);
return ResponseEntity.success(stories);
}
@GetMapping("/list")
public ResponseEntity<List<Story>> getStories(@SpringQueryMap StoryVo storyVo) {
log.info("查询故事列表, 用户ID: {}", storyVo.getOwnerId());
List<Story> stories = storyService.getStories(storyVo);
return ResponseEntity.success(stories);
}
}

View File

@@ -4,8 +4,11 @@ import com.alibaba.fastjson.JSONObject;
import com.timeline.common.response.ResponseEntity;
import com.timeline.story.entity.StoryItem;
import com.timeline.story.service.StoryItemService;
import com.timeline.story.service.StoryService;
import com.timeline.story.vo.StoryItemAddVo;
import com.timeline.story.vo.StoryItemVo;
import com.timeline.story.vo.StoryVo;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.openfeign.SpringQueryMap;
@@ -31,12 +34,10 @@ public class StoryItemController {
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);
@PutMapping("")
public ResponseEntity<String> updateItem(@RequestParam("storyItem") String storyItemVoString, @RequestParam(value = "images", required = false) List<MultipartFile> images) {
log.info("更新 StoryItem: {}", storyItemVoString);
storyItemService.updateItem(JSONObject.parseObject(storyItemVoString, StoryItemAddVo.class), images);
return ResponseEntity.success("StoryItem 更新成功");
}

View File

@@ -9,6 +9,9 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
@RestController
@RequestMapping("/story/permission")
@@ -31,6 +34,12 @@ public class StoryPermissionController {
storyPermissionService.updatePermission(permissionVo);
return ResponseEntity.success("权限更新成功");
}
@PostMapping("/authorize")
public ResponseEntity<String> authorizePermission(@RequestBody StoryPermissionVo permissionVo) {
log.info("授权权限: {}", permissionVo);
storyPermissionService.createPermission(permissionVo);
return ResponseEntity.success("权限授权成功");
}
@DeleteMapping("/{permissionId}")
public ResponseEntity<String> deletePermission(@PathVariable String permissionId) {

View File

@@ -0,0 +1,62 @@
package com.timeline.story.controller;
import com.timeline.common.response.ResponseEntity;
import com.timeline.common.utils.UserContextUtils;
import com.timeline.story.dto.ShareStoryRequest;
import com.timeline.story.entity.Story;
import com.timeline.story.entity.StoryActivity;
import com.timeline.story.service.StoryActivityService;
import com.timeline.story.service.StoryPermissionService;
import com.timeline.story.service.StoryService;
import com.timeline.story.vo.StoryPermissionVo;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.ArrayList;
import java.util.List;
@Slf4j
@RestController
@RequestMapping("/story/share")
public class StoryShareController {
@Autowired
private StoryPermissionService storyPermissionService;
@Autowired
private StoryService storyService;
@Autowired
private StoryActivityService storyActivityService;
@PostMapping("/{storyId}")
public ResponseEntity<String> shareStory(@PathVariable String storyId, @RequestBody ShareStoryRequest req) {
StoryPermissionVo vo = new StoryPermissionVo();
vo.setStoryInstanceId(storyId);
vo.setUserId(req.getFriendId());
vo.setPermissionType(req.getPermissionType());
storyPermissionService.createPermission(vo);
StoryActivity activity = new StoryActivity();
activity.setActorId(UserContextUtils.getCurrentUserId());
activity.setAction("share_story");
activity.setStoryInstanceId(storyId);
activity.setRemark("分享给 " + req.getFriendId());
storyActivityService.logActivity(activity);
return ResponseEntity.success("已授权好友");
}
@GetMapping("/friends")
public ResponseEntity<List<Story>> friendStories() {
String uid = UserContextUtils.getCurrentUserId();
var permissions = storyPermissionService.getPermissionsByUserId(uid);
List<Story> stories = new ArrayList<>();
for (var p : permissions) {
try {
stories.add(storyService.getStoryByInstanceId(p.getStoryInstanceId()));
} catch (Exception ignored) {
}
}
return ResponseEntity.success(stories);
}
}

View File

@@ -0,0 +1,21 @@
package com.timeline.story.dao;
import com.timeline.story.entity.StoryActivity;
import com.timeline.story.dto.StoryItemActivityDto;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;
@Mapper
public interface StoryActivityMapper {
void insert(StoryActivity activity);
List<StoryActivity> selectByActors(@Param("actorIds") List<String> actorIds);
/**
* 查询当前用户拥有权限的 story 中,与 storyItem 相关的最新动态,携带 storyItem 内容
*/
List<StoryItemActivityDto> selectAuthorizedItemUpdates(@Param("userId") String userId);
}

View File

@@ -12,5 +12,5 @@ public interface StoryMapper {
void deleteByInstanceId(String instanceId);
Story selectByInstanceId(String instanceId);
List<Story> selectByOwnerId(String ownerId);
void touchUpdate(String instanceId, String updateId);
}

View File

@@ -0,0 +1,10 @@
package com.timeline.story.dto;
import lombok.Data;
@Data
public class ShareStoryRequest {
private String friendId;
private Integer permissionType; // 1=读,2=写,3=管理
}

View File

@@ -0,0 +1,31 @@
package com.timeline.story.dto;
import lombok.Data;
import java.time.LocalDateTime;
/**
* 带有 storyItem 内容的动态信息,用于“好友动态 / 有权限的 storyItem 更新”接口
*/
@Data
public class StoryItemActivityDto {
// activity 基本信息
private Long id;
private String actorId;
private String actorName;
private String action;
private String storyInstanceId;
private String storyInstanceName;
private String storyItemId;
private String storyItemName;
private String remark;
private LocalDateTime activityTime;
// storyItem 内容摘要
private String itemTitle;
private String itemDescription;
private String itemLocation;
private LocalDateTime itemTime;
}

View File

@@ -0,0 +1,17 @@
package com.timeline.story.entity;
import lombok.Data;
import java.time.LocalDateTime;
@Data
public class StoryActivity {
private Long id;
private String actorId;
private String action; // create_story, update_story_item, share_story, etc.
private String storyInstanceId;
private String storyItemId;
private String remark;
private LocalDateTime createTime;
}

View File

@@ -22,4 +22,5 @@ public class StoryItem {
private Integer isDelete;
private String coverInstanceId;
private StoryItem[] subItems;
private String updateId;
}

View File

@@ -0,0 +1,19 @@
package com.timeline.story.feign;
import com.timeline.common.response.ResponseEntity;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import java.util.List;
import java.util.Map;
@FeignClient(name = "timeline.user", url = "${user.service.url}")
public interface UserServiceClient {
@GetMapping("/user/friend/ids")
ResponseEntity<List<String>> getFriendIds();
@GetMapping("/{userId}")
ResponseEntity<Map> getUserByUserId(@PathVariable String userId);
}

View File

@@ -0,0 +1,18 @@
package com.timeline.story.service;
import com.timeline.story.entity.StoryActivity;
import com.timeline.story.dto.StoryItemActivityDto;
import java.util.List;
public interface StoryActivityService {
void logActivity(StoryActivity activity);
List<StoryActivity> listMyAndFriendsActivities();
/**
* 查询当前用户拥有权限的 story 中storyItem 的更新/创建动态
*/
List<StoryItemActivityDto> listAuthorizedItemUpdates();
}

View File

@@ -12,7 +12,7 @@ import java.util.Map;
public interface StoryItemService {
void createStoryItem(StoryItemAddVo storyItemVo, List<MultipartFile> images);
StoryItemWithCoverVo getStoryItemWithCover(String itemId);
void updateItem(String itemId, String description, String location);
void updateItem(StoryItemAddVo storyItemVo, List<MultipartFile> images);
void deleteItem(String itemId);
StoryItem getItemById(String itemId);
Map getItemsByMasterItem(StoryItemVo storyItemVo);

View File

@@ -11,5 +11,6 @@ public interface StoryService {
void deleteStory(String storyId);
Story getStoryByInstanceId(String storyId);
List<Story> getStoriesByOwnerId(String ownerId);
List<Story> getStories(StoryVo storyVo);
}

View File

@@ -0,0 +1,64 @@
package com.timeline.story.service.impl;
import com.timeline.common.exception.CustomException;
import com.timeline.common.response.ResponseEnum;
import com.timeline.common.utils.UserContextUtils;
import com.timeline.story.dao.StoryActivityMapper;
import com.timeline.story.dto.StoryItemActivityDto;
import com.timeline.story.entity.StoryActivity;
import com.timeline.story.feign.UserServiceClient;
import com.timeline.story.service.StoryActivityService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
@Slf4j
@Service
public class StoryActivityServiceImpl implements StoryActivityService {
@Autowired
private StoryActivityMapper storyActivityMapper;
@Autowired
private UserServiceClient userServiceClient;
private String currentUser() {
String uid = UserContextUtils.getCurrentUserId();
if (uid == null || uid.isEmpty()) {
throw new CustomException(ResponseEnum.UNAUTHORIZED, "未获取到用户身份");
}
return uid;
}
@Override
public void logActivity(StoryActivity activity) {
activity.setCreateTime(LocalDateTime.now());
storyActivityMapper.insert(activity);
}
@Override
public List<StoryActivity> listMyAndFriendsActivities() {
String uid = currentUser();
List<String> actorIds = new ArrayList<>();
actorIds.add(uid);
try {
var resp = userServiceClient.getFriendIds();
if (resp != null && resp.getData() != null) {
actorIds.addAll(resp.getData());
}
} catch (Exception e) {
log.warn("获取好友列表失败,按仅自己查询", e);
}
return storyActivityMapper.selectByActors(actorIds);
}
@Override
public List<StoryItemActivityDto> listAuthorizedItemUpdates() {
String uid = currentUser();
return storyActivityMapper.selectAuthorizedItemUpdates(uid);
}
}

View File

@@ -2,15 +2,19 @@ package com.timeline.story.service.impl;
import com.timeline.common.constants.CommonConstants;
import com.timeline.common.utils.PageUtils;
import com.timeline.common.utils.UserContextUtils;
import com.timeline.story.dao.CommonRelationMapper;
import com.timeline.common.dto.CommonRelationDTO;
import com.timeline.common.exception.CustomException;
import com.timeline.common.response.ResponseEntity;
import com.timeline.common.response.ResponseEnum;
import com.timeline.story.dao.StoryItemMapper;
import com.timeline.story.dao.StoryMapper;
import com.timeline.story.entity.StoryItem;
import com.timeline.story.entity.StoryActivity;
import com.timeline.story.feign.FileServiceClient;
import com.timeline.story.service.StoryItemService;
import com.timeline.story.service.StoryActivityService;
import com.timeline.story.vo.StoryItemAddVo;
import com.timeline.story.vo.StoryItemVo;
import com.timeline.story.vo.StoryItemWithCoverVo;
@@ -33,14 +37,33 @@ public class StoryItemServiceImpl implements StoryItemService {
@Autowired
private StoryItemMapper storyItemMapper;
@Autowired
private StoryMapper storyMapper;
@Autowired
private FileServiceClient fileServiceClient;
@Autowired
private CommonRelationMapper commonRelationMapper;
@Autowired
private StoryActivityService storyActivityService;
private String currentUserId() {
String userId = UserContextUtils.getCurrentUserId();
if (userId == null || userId.isEmpty()) {
throw new CustomException(ResponseEnum.UNAUTHORIZED, "用户未登录");
}
return userId;
}
private String currentUsername() {
String username = UserContextUtils.getCurrentUsername();
if (username == null || username.isEmpty()) {
throw new CustomException(ResponseEnum.UNAUTHORIZED, "用户未登录");
}
return username;
}
@Override
public void createStoryItem(StoryItemAddVo storyItemVo, List<MultipartFile> images) {
try {
String currentUserId = currentUserId();
// 2. 创建 StoryItem 实体
StoryItem item = new StoryItem();
item.setInstanceId(IdUtils.randomUuidUpper());
@@ -49,10 +72,20 @@ public class StoryItemServiceImpl implements StoryItemService {
item.setTitle(storyItemVo.getTitle());
item.setDescription(storyItemVo.getDescription());
item.setLocation(storyItemVo.getLocation());
item.setCreateId("createId");
item.setCreateId(currentUserId);
item.setUpdateId(currentUserId);
item.setStoryItemTime(storyItemVo.getStoryItemTime());
item.setIsDelete(CommonConstants.NOT_DELETED);
storyItemMapper.insert(item);
storyMapper.touchUpdate(storyItemVo.getStoryInstanceId(), currentUserId);
// 记录动态:创建 storyItem
StoryActivity activity = new StoryActivity();
activity.setActorId(currentUserId);
activity.setAction("create_story_item");
activity.setStoryInstanceId(storyItemVo.getStoryInstanceId());
activity.setStoryItemId(item.getInstanceId());
activity.setRemark("创建故事条目");
storyActivityService.logActivity(activity);
if (storyItemVo.getRelatedImageInstanceIds() != null && !storyItemVo.getRelatedImageInstanceIds().isEmpty()) {
for (String imageInstanceId : storyItemVo.getRelatedImageInstanceIds()) {
log.info("关联现有图像 {} - {}", imageInstanceId, item.getInstanceId());
@@ -92,16 +125,48 @@ public class StoryItemServiceImpl implements StoryItemService {
}
@Override
public void updateItem(String itemId, String description, String location) {
public void updateItem(StoryItemAddVo storyItemVo, List<MultipartFile> images) {
try {
StoryItem item = storyItemMapper.selectById(itemId);
String currentUserId = UserContextUtils.getCurrentUserId();
StoryItem item = storyItemMapper.selectById(storyItemVo.getInstanceId());
if (item == null) {
throw new RuntimeException("StoryItem 不存在");
}
item.setDescription(description);
item.setLocation(location);
item.setDescription(storyItemVo.getDescription());
item.setLocation(storyItemVo.getLocation());
item.setStoryItemTime(storyItemVo.getStoryItemTime());
item.setTitle(storyItemVo.getTitle());
item.setUpdateTime(LocalDateTime.now());
item.setUpdateId(currentUserId);
storyItemMapper.update(item);
storyMapper.touchUpdate(item.getStoryInstanceId(), currentUserId);
// 记录动态:更新 storyItem
StoryActivity activity = new StoryActivity();
activity.setActorId(currentUserId);
activity.setAction("update_story_item");
activity.setStoryInstanceId(item.getStoryInstanceId());
activity.setStoryItemId(storyItemVo.getInstanceId());
activity.setRemark("更新故事条目");
storyActivityService.logActivity(activity);
if (storyItemVo.getRelatedImageInstanceIds() != null && !storyItemVo.getRelatedImageInstanceIds().isEmpty()) {
// 删除所有关联图像
commonRelationMapper.deleteRelationByRelaId(item.getInstanceId());
for (String imageInstanceId : storyItemVo.getRelatedImageInstanceIds()) {
log.info("关联现有图像 {} - {}", imageInstanceId, item.getInstanceId());
// 3. 建立 StoryItem 与图像关系
buildStoryItemImageRelation(item.getInstanceId(), imageInstanceId);
}
}
if (images != null) {
log.info("上传 StoryItem 关联图像");
for (MultipartFile image : images) {
ResponseEntity<String> response = fileServiceClient.uploadImage(image);
String key = response.getData();
log.info("上传成功文件instanceId:{}", key);
// 4. 建立图像与StoryItem 关系
buildStoryItemImageRelation(item.getInstanceId(), key);
}
}
} catch (Exception e) {
log.error("更新 StoryItem 失败", e);
throw new RuntimeException("更新 StoryItem 失败");
@@ -111,12 +176,23 @@ public class StoryItemServiceImpl implements StoryItemService {
@Override
public void deleteItem(String itemId) {
try {
String currentUserId = UserContextUtils.getCurrentUserId();
StoryItem item = storyItemMapper.selectById(itemId);
if (item == null) {
throw new RuntimeException("StoryItem 不存在");
}
storyItemMapper.deleteByItemId(itemId);
commonRelationMapper.deleteRelationByRelaId(itemId);
storyMapper.touchUpdate(item.getStoryInstanceId(), currentUserId);
// 记录动态:删除 storyItem
StoryActivity activity = new StoryActivity();
activity.setActorId(currentUserId);
activity.setAction("delete_story_item");
activity.setStoryInstanceId(item.getStoryInstanceId());
activity.setStoryItemId(itemId);
activity.setRemark("删除故事条目");
storyActivityService.logActivity(activity);
} catch (Exception e) {
log.error("删除 StoryItem 失败", e);
throw new RuntimeException("删除 StoryItem 失败");

View File

@@ -2,10 +2,13 @@ package com.timeline.story.service.impl;
import com.timeline.common.constants.CommonConstants;
import com.timeline.common.exception.CustomException;
import com.timeline.common.response.ResponseEntity;
import com.timeline.common.response.ResponseEnum;
import com.timeline.common.utils.IdUtils;
import com.timeline.common.utils.UserContextUtils;
import com.timeline.story.dao.StoryPermissionMapper;
import com.timeline.story.entity.StoryPermission;
import com.timeline.story.feign.UserServiceClient;
import com.timeline.story.service.StoryPermissionService;
import com.timeline.story.vo.StoryPermissionVo;
import lombok.extern.slf4j.Slf4j;
@@ -15,6 +18,7 @@ import org.springframework.stereotype.Service;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Map;
@Slf4j
@Service
@@ -22,10 +26,40 @@ public class StoryPermissionServiceImpl implements StoryPermissionService {
@Autowired
private StoryPermissionMapper storyPermissionMapper;
@Autowired
private UserServiceClient userServiceClient;
private String getCurrentUserId() {
String uid = UserContextUtils.getCurrentUserId();
if (uid == null) {
throw new CustomException(ResponseEnum.UNAUTHORIZED, "未获取到用户身份");
}
return uid;
}
@Override
public void createPermission(StoryPermissionVo permissionVo) {
try {
String currentUserId = getCurrentUserId();
if (currentUserId.equals(permissionVo.getUserId()) && permissionVo.getPermissionType() != 1) {
throw new CustomException(ResponseEnum.BAD_REQUEST, "不能授权给自己");
}
StoryPermission selectByStoryAndUser = storyPermissionMapper.selectByStoryAndUser(permissionVo.getStoryInstanceId(), permissionVo.getUserId());;
if (selectByStoryAndUser != null) {
log.info("用户已有该故事权限,更新当前权限为:{}", permissionVo.getPermissionType());
selectByStoryAndUser.setPermissionType(permissionVo.getPermissionType());;
selectByStoryAndUser.setUpdateTime(LocalDateTime.now());
storyPermissionMapper.update(selectByStoryAndUser);
return;
}
// 远程调用user服务判断用户是否存在 feign 调用
ResponseEntity<Map> response = userServiceClient.getUserByUserId(permissionVo.getUserId());
log.info("响应结果: {}", response.toString());
if (response.getCode() != 200) {
throw new CustomException(ResponseEnum.BAD_REQUEST, "远程调用失败");
} else if (response.getData() == null) {
throw new CustomException(ResponseEnum.BAD_REQUEST, "用户不存在");
}
log.info("新建故事{} 授权 {} 给 {}", permissionVo.getStoryInstanceId(), permissionVo.getPermissionType(), permissionVo.getUserId());
StoryPermission permission = new StoryPermission();
BeanUtils.copyProperties(permissionVo, permission);
permission.setPermissionId(IdUtils.randomUuidUpper());
@@ -33,7 +67,11 @@ public class StoryPermissionServiceImpl implements StoryPermissionService {
permission.setUpdateTime(LocalDateTime.now());
permission.setIsDeleted(CommonConstants.NOT_DELETED);
storyPermissionMapper.insert(permission);
} catch (Exception e) {
} catch(CustomException e) {
throw e;
}
catch (Exception e) {
log.error("创建权限失败", e);
throw new CustomException(ResponseEnum.INTERNAL_SERVER_ERROR, "创建权限失败");
}

View File

@@ -1,14 +1,19 @@
package com.timeline.story.service.impl;
import com.timeline.common.constants.CommonConstants;
import com.timeline.common.exception.CustomException;
import com.timeline.common.response.ResponseEnum;
import com.timeline.story.entity.Story;
import com.timeline.story.entity.StoryActivity;
import com.timeline.story.dao.StoryMapper;
import com.timeline.story.service.StoryPermissionService;
import com.timeline.story.service.StoryService;
import com.timeline.story.service.StoryActivityService;
import com.timeline.story.vo.StoryPermissionVo;
import com.timeline.story.vo.StoryVo;
import com.timeline.common.utils.IdUtils;
import com.timeline.common.utils.UserContextUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@@ -25,11 +30,19 @@ public class StoryServiceImpl implements StoryService {
@Autowired
private StoryPermissionService storyPermissionService;
@Autowired
private StoryActivityService storyActivityService;
@Override
public void createStory(StoryVo storyVo) {
try {
String currentUserId = UserContextUtils.getCurrentUserId();
if (currentUserId == null || currentUserId.isEmpty()) {
throw new CustomException(ResponseEnum.UNAUTHORIZED, "未获取到用户身份");
}
Story story = new Story();
story.setOwnerId("test11");
story.setOwnerId(currentUserId);
story.setUpdateId(currentUserId);
story.setTitle(storyVo.getTitle());
story.setInstanceId(IdUtils.randomUuidUpper());
story.setDescription(storyVo.getDescription());
@@ -42,9 +55,16 @@ public class StoryServiceImpl implements StoryService {
// 自动添加创建者权限
StoryPermissionVo permissionVo = new StoryPermissionVo();
permissionVo.setStoryInstanceId(story.getInstanceId());
permissionVo.setUserId(storyVo.getOwnerId());
permissionVo.setPermissionType(1); // 创建者权限
permissionVo.setUserId(currentUserId);
permissionVo.setPermissionType(CommonConstants.STORY_PERMISSION_TYPE_OWNER); // 创建者权限
storyPermissionService.createPermission(permissionVo);
StoryActivity activity = new StoryActivity();
activity.setActorId(currentUserId);
activity.setAction("create_story");
activity.setStoryInstanceId(story.getInstanceId());
activity.setRemark("创建故事");
storyActivityService.logActivity(activity);
} catch (Exception e) {
log.error("创建故事失败", e);
throw new CustomException(500, "创建故事失败: " + e.toString());
@@ -65,12 +85,19 @@ public class StoryServiceImpl implements StoryService {
story.setUpdateTime(LocalDateTime.now());
story.setLogo(storyVo.getLogo());
// 如果传入了 updateId则更新 updateId todo: 使用线程获取用户ID
if (storyVo.getOwnerId() != null && !storyVo.getOwnerId().isEmpty()) {
story.setUpdateId(storyVo.getOwnerId());
String currentUserId = UserContextUtils.getCurrentUserId();
if (currentUserId != null && !currentUserId.isEmpty()) {
story.setUpdateId(currentUserId);
}
storyMapper.update(story);
StoryActivity activity = new StoryActivity();
activity.setActorId(currentUserId);
activity.setAction("update_story");
activity.setStoryInstanceId(storyId);
activity.setRemark("更新故事");
storyActivityService.logActivity(activity);
}
@Override
@@ -100,4 +127,18 @@ public class StoryServiceImpl implements StoryService {
throw new CustomException(500, "查询用户故事列表失败");
}
}
@Override
public List<Story> getStories(StoryVo storyVo) {
try {
String currentUserId = UserContextUtils.getCurrentUserId();
if (currentUserId == null || currentUserId.isEmpty()) {
throw new CustomException(ResponseEnum.UNAUTHORIZED, "未获取到用户身份");
}
return storyMapper.selectByOwnerId(currentUserId);
} catch (Exception e) {
log.error("查询用户故事列表失败", e);
throw new CustomException(500, "查询用户故事列表失败");
}
}
}

View File

@@ -14,5 +14,10 @@ public class StoryItemVo extends CommonVo {
private String location;
private LocalDateTime storyItemTime;
private String storyInstanceId;
private String createId;
private String createName;
private LocalDateTime createTime;
private String updateId;
private String updateName;
private LocalDateTime updateTime;
}

View File

@@ -1,25 +1,52 @@
spring.application.name=timeline.story
spring.datasource.url=jdbc:mysql://8.137.148.196:33306/timeline?serverTimezone=UTC&allowPublicKeyRetrieval=true
spring.datasource.url=jdbc:mysql://59.80.22.43:33306/timeline?serverTimezone=UTC&allowPublicKeyRetrieval=true
spring.datasource.username=root
spring.datasource.password=your_password
spring.datasource.password=WoCloud@9ol7uj
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.mapper-locations=classpath:mapper/*.xml
mybatis.type-aliases-package=com.timeline.user.entity
mybatis.configuration.mapUnderscoreToCamelCase=true
# ??????????
logging.level.org.mybatis.spring=DEBUG
logging.level.org.apache.ibatis=DEBUG
mybatis.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
logging.level.com.timeline.user.dao=DEBUG
logging.level.org.mybatis=DEBUG
# LocalDateTime ???????
spring.jackson.date-format=yyyy-MM-dd HH:mm:ss
spring.jackson.time-zone=UTC
file.service.url=http://localhost:30002/file/
user.service.url=http://localhost:30003/user/
spring.servlet.multipart.enabled=true
spring.servlet.multipart.max-file-size=10MB
spring.servlet.multipart.max-request-size=10MB
spring.servlet.multipart.max-request-size=10MB
spring.data.redis.host=127.0.0.1
spring.data.redis.port=36379
spring.data.redis.password=123456
spring.data.redis.timeout=5000
# HikariCP tuning to avoid stale/closed MySQL connections
spring.datasource.hikari.max-lifetime=600000
# 10 minutes, below MySQL wait_timeout
spring.datasource.hikari.idle-timeout=300000
# 5 minutes, recycle idle connections
spring.datasource.hikari.validation-timeout=3000
# fast validation timeout
spring.datasource.hikari.connection-timeout=30000
# wait up to 30s for a connection
spring.datasource.hikari.connection-test-query=SELECT 1
spring.datasource.hikari.test-on-borrow=true
spring.datasource.hikari.test-while-idle=true
# Ensure UTF-8 encoding for logs and web layer to avoid garbled Chinese output
logging.charset.console=UTF-8
logging.charset.file=UTF-8
server.tomcat.uri-encoding=UTF-8
server.servlet.encoding.charset=UTF-8
server.servlet.encoding.enabled=true
server.servlet.encoding.force=true

View File

@@ -0,0 +1,55 @@
<?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.StoryActivityMapper">
<insert id="insert" parameterType="com.timeline.story.entity.StoryActivity">
INSERT INTO story_activity (actor_id, action, story_instance_id, story_item_id, remark, create_time)
VALUES (#{actorId}, #{action}, #{storyInstanceId}, #{storyItemId}, #{remark}, #{createTime})
</insert>
<select id="selectByActors" resultType="com.timeline.story.entity.StoryActivity">
SELECT * FROM story_activity
WHERE actor_id IN
<foreach collection="actorIds" item="id" open="(" close=")" separator=",">
#{id}
</foreach>
ORDER BY create_time DESC
</select>
<!-- 查询当前用户拥有权限的 story 中,与 storyItem 相关的动态,联表返回 storyItem 内容 -->
<select id="selectAuthorizedItemUpdates" resultType="com.timeline.story.dto.StoryItemActivityDto">
SELECT
sa.id,
sa.actor_id AS actorId,
u.username AS actorName,
sa.action,
sa.story_instance_id AS storyInstanceId,
s.title AS storyInstanceName,
sa.story_item_id AS storyItemId,
si.title AS storyItemName,
sa.remark,
sa.create_time AS activityTime,
si.title AS itemTitle,
si.description AS itemDescription,
si.location AS itemLocation,
si.story_item_time AS itemTime
FROM story_activity sa
INNER JOIN story_permission sp
ON sa.story_instance_id = sp.story_instance_id
LEFT JOIN story_item si
ON sa.story_item_id = si.instance_id
LEFT JOIN story s
ON sa.story_instance_id = s.instance_id
LEFT JOIN user u
ON sa.actor_id = u.user_id
WHERE sp.user_id = #{userId}
AND sp.is_deleted = 0
AND sa.story_item_id IS NOT NULL
<!-- AND sa.action IN ('create_story_item', 'update_story_item') -->
ORDER BY sa.create_time DESC
</select>
</mapper>

View File

@@ -5,8 +5,8 @@
<mapper namespace="com.timeline.story.dao.StoryItemMapper">
<insert id="insert">
INSERT INTO story_item (instance_id, master_item_id, description, location, title, create_id, story_instance_id, is_delete, story_item_time)
VALUES (#{instanceId}, #{masterItemId}, #{description}, #{location}, #{title},#{createId}, #{storyInstanceId}, #{isDelete}, #{storyItemTime})
INSERT INTO story_item (instance_id, master_item_id, description, location, title, create_id, story_instance_id, is_delete, story_item_time, update_id)
VALUES (#{instanceId}, #{masterItemId}, #{description}, #{location}, #{title},#{createId}, #{storyInstanceId}, #{isDelete}, #{storyItemTime}, #{updateId})
</insert>
<update id="update">
@@ -14,7 +14,8 @@
SET description = #{description},
location = #{location},
create_id = #{createId},
update_time = NOW()
update_time = NOW(),
update_id = #{updateId}
WHERE instance_id = #{instanceId}
</update>
@@ -35,9 +36,30 @@
<select id="selectImagesByItemId" resultType="java.lang.String">
SELECT sub_rela_id FROM common_relation WHERE rela_id = #{instanceId} AND rela_type = 5 AND is_delete = 0
</select>
<select id="selectStoryItemByStoryInstanceId" resultType="com.timeline.story.entity.StoryItem">
SELECT * FROM story_item WHERE story_instance_id = #{storyInstanceId} AND is_delete = 0
ORDER BY story_item_time DESC
<select id="selectStoryItemByStoryInstanceId" resultType="com.timeline.story.vo.StoryItemVo">
SELECT
instance_id,
si.description,
si.location,
title,
story_instance_id,
si.story_item_time as story_item_time,
si.update_time,
si.create_id AS create_id,
si.create_time AS create_time,
u1.username AS create_name,
si.update_id AS update_id,
si.update_time AS update_time,
u2.username AS update_name
FROM
story_item si
LEFT JOIN user u1 ON si.create_id = u1.user_id
LEFT JOIN `user` u2 ON si.update_id = u2.user_id
WHERE
story_instance_id = #{storyInstanceId}
AND is_delete = 0
ORDER BY
story_item_time DESC
</select>
<select id="countByStoryId" resultType="int">

View File

@@ -27,8 +27,8 @@
<select id="selectByInstanceId" resultType="com.timeline.story.entity.Story">
SELECT
s.*,
u1.user_name as owner_name,
u2.user_name as update_name,
u1.username as owner_name,
u2.username as update_name,
(SELECT COUNT(*) FROM story_item si WHERE si.story_instance_id = s.instance_id AND si.is_delete = 0) as item_count
FROM story s
@@ -39,15 +39,29 @@
</select>
<select id="selectByOwnerId" resultType="com.timeline.story.entity.Story">
SELECT s.*,
u1.user_name as owner_name,
u2.user_name as update_name,
(SELECT COUNT(*) FROM story_item si WHERE si.story_instance_id = s.instance_id AND si.is_delete = 0) as item_count
FROM story s
LEFT JOIN user u1
ON s.owner_id = u1.user_id AND u1.is_deleted = 0
LEFT JOIN user u2 ON s.update_id = u2.user_id AND u2.is_deleted = 0
WHERE s.owner_id = #{ownerId} AND s.is_delete = 0
SELECT
s.*,
u1.username AS owner_name,
u2.username AS update_name,
sp.permission_type AS permission_type,
( SELECT COUNT(*) FROM story_item si WHERE si.story_instance_id = s.instance_id AND si.is_delete = 0 ) AS item_count
FROM
story s
LEFT JOIN user u1 ON s.owner_id = u1.user_id
AND u1.is_deleted = 0
LEFT JOIN user u2 ON s.update_id = u2.user_id
AND u2.is_deleted = 0
LEFT JOIN story_permission sp ON s.instance_id = sp.story_instance_id AND sp.user_id = #{owerId}
WHERE
s.instance_id IN ( SELECT story_instance_id FROM story_permission WHERE user_id = #{owerId} )
AND s.is_delete = 0
</select>
<update id="touchUpdate">
UPDATE story
SET update_id = #{updateId},
update_time = NOW()
WHERE instance_id = #{instanceId}
</update>
</mapper>

View File

@@ -7,8 +7,7 @@
<insert id="insert">
INSERT INTO story_permission (permission_id, story_instance_id, user_id, permission_type)
VALUES (#{permissionId}, #{storyInstanceId}, #{userId}, #{permissionType})
</insert>
</insert>
<update id="update">
UPDATE story_permission
SET permission_type = #{permissionType},