feat(open-api): 新增对外开放接口及项目日报同步功能
- 新增项目日报表及其防重唯一索引,支持外部系统同步日报数据 - 添加项目日报实体类及对应 Mapper 和 XML 配置 - 新增对外开放接口控制器 OpenApiController,实现项目列表查询及日报同步接口 - 实现 OpenApiService 服务及其实现类,包含用户项目查询和日报防重同步逻辑 - 扩展 ProjectMapper,支持根据用户名查询用户关联项目列表 - 配置 SaToken 过滤白名单,放行 /api/open/** 路径无登录验证 - 引入 spring-boot-starter-validation 依赖,支持请求参数校验 - 创建数据传输对象 DailyReportSyncDTO,带参数校验注解 - 日志记录和异常处理增强,保证数据同步和查询的健壮性
This commit is contained in:
@@ -1337,4 +1337,30 @@ INSERT INTO sys_config (config_key, config_value, config_type, description) VALU
|
|||||||
('ai.embedding.model', 'text-embedding-v4', 'ai', '向量嵌入模型'),
|
('ai.embedding.model', 'text-embedding-v4', 'ai', '向量嵌入模型'),
|
||||||
('ai.embedding.dimension', '1024', 'ai', '向量维度'),
|
('ai.embedding.dimension', '1024', 'ai', '向量维度'),
|
||||||
('ai.rag.top_k', '5', 'ai', 'RAG检索返回数量'),
|
('ai.rag.top_k', '5', 'ai', 'RAG检索返回数量'),
|
||||||
('ai.rag.similarity_threshold', '0.7', 'ai', 'RAG相似度阈值');
|
('ai.rag.similarity_threshold', '0.7', 'ai', 'RAG相似度阈値');
|
||||||
|
|
||||||
|
-- =====================================================
|
||||||
|
-- 项目日报表(供外部系统同步日报数据)
|
||||||
|
-- 防重键: (project_id, report_date, submitter_username)
|
||||||
|
-- =====================================================
|
||||||
|
CREATE TABLE IF NOT EXISTS project_daily_report (
|
||||||
|
id BIGSERIAL PRIMARY KEY,
|
||||||
|
project_id BIGINT NOT NULL,
|
||||||
|
submitter_username VARCHAR(100) NOT NULL,
|
||||||
|
submitter_id BIGINT,
|
||||||
|
report_date DATE NOT NULL,
|
||||||
|
work_content TEXT,
|
||||||
|
tomorrow_plan TEXT,
|
||||||
|
work_intensity INTEGER CHECK (work_intensity BETWEEN 1 AND 5),
|
||||||
|
need_help BOOLEAN DEFAULT FALSE,
|
||||||
|
help_content TEXT,
|
||||||
|
create_time TIMESTAMP DEFAULT NOW(),
|
||||||
|
update_time TIMESTAMP DEFAULT NOW(),
|
||||||
|
deleted INTEGER DEFAULT 0,
|
||||||
|
CONSTRAINT uq_daily_report UNIQUE (project_id, report_date, submitter_username)
|
||||||
|
);
|
||||||
|
|
||||||
|
COMMENT ON TABLE project_daily_report IS '项目日报(外部同步)';
|
||||||
|
COMMENT ON COLUMN project_daily_report.submitter_username IS '提交人用户名 (对应 sys_user.username)';
|
||||||
|
COMMENT ON COLUMN project_daily_report.work_intensity IS '工作强度: 1-轻松 2-较轻 3-适中 4-繁忙 5-非常繁忙';
|
||||||
|
|
||||||
|
|||||||
6
pom.xml
6
pom.xml
@@ -34,6 +34,12 @@
|
|||||||
<artifactId>spring-boot-starter-web</artifactId>
|
<artifactId>spring-boot-starter-web</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<!-- Validation -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-validation</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
<!-- Lombok -->
|
<!-- Lombok -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.projectlombok</groupId>
|
<groupId>org.projectlombok</groupId>
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ public class SaTokenConfig implements WebMvcConfigurer {
|
|||||||
"/api/v1/auth/login",
|
"/api/v1/auth/login",
|
||||||
"/api/v1/auth/register",
|
"/api/v1/auth/register",
|
||||||
"/api/v1/auth/feishu/login",
|
"/api/v1/auth/feishu/login",
|
||||||
|
"/api/open/**",
|
||||||
"/error",
|
"/error",
|
||||||
"/swagger-ui/**",
|
"/swagger-ui/**",
|
||||||
"/v3/api-docs/**"
|
"/v3/api-docs/**"
|
||||||
|
|||||||
@@ -0,0 +1,72 @@
|
|||||||
|
package cn.yinlihupo.controller.open;
|
||||||
|
|
||||||
|
import cn.yinlihupo.common.core.BaseResponse;
|
||||||
|
import cn.yinlihupo.common.util.ResultUtils;
|
||||||
|
import cn.yinlihupo.domain.dto.DailyReportSyncDTO;
|
||||||
|
import cn.yinlihupo.domain.vo.OpenProjectVO;
|
||||||
|
import cn.yinlihupo.service.open.OpenApiService;
|
||||||
|
import jakarta.validation.Valid;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 对外开放接口
|
||||||
|
* 路径前缀 /api/open/** 已在 SaTokenConfig 中放行,无需登录鉴权
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/api/open")
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class OpenApiController {
|
||||||
|
|
||||||
|
private final OpenApiService openApiService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据用户ID查询所在的项目列表
|
||||||
|
* <p>
|
||||||
|
* 请求示例:GET /api/open/projects/by-user?userId=zhangsan
|
||||||
|
*
|
||||||
|
* @param userId 用户标识(对应 sys_user.username)
|
||||||
|
* @return 项目列表
|
||||||
|
*/
|
||||||
|
@GetMapping("/projects/by-user")
|
||||||
|
public BaseResponse<List<OpenProjectVO>> getProjectsByUser(
|
||||||
|
@RequestParam("userId") String userId) {
|
||||||
|
log.info("[OpenApi] 查询用户项目列表, userId={}", userId);
|
||||||
|
List<OpenProjectVO> projects = openApiService.getProjectsByUserId(userId);
|
||||||
|
return ResultUtils.success("查询成功", projects);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 同步日报数据
|
||||||
|
* <p>
|
||||||
|
* 请求示例:POST /api/open/daily-report/sync
|
||||||
|
* <pre>
|
||||||
|
* {
|
||||||
|
* "projectId": 123,
|
||||||
|
* "userId": "zhangsan",
|
||||||
|
* "reportDate": "2026-03-31",
|
||||||
|
* "workContent": "完成了XXX功能开发",
|
||||||
|
* "tomorrowPlan": "继续YYY模块",
|
||||||
|
* "workIntensity": 3,
|
||||||
|
* "needHelp": false,
|
||||||
|
* "helpContent": null
|
||||||
|
* }
|
||||||
|
* </pre>
|
||||||
|
* 防重规则:同一项目+同一日期+同一用户只能提交一次,重复提交返回错误提示
|
||||||
|
*
|
||||||
|
* @param dto 日报数据
|
||||||
|
* @return 操作结果
|
||||||
|
*/
|
||||||
|
@PostMapping("/daily-report/sync")
|
||||||
|
public BaseResponse<String> syncDailyReport(
|
||||||
|
@RequestBody @Valid DailyReportSyncDTO dto) {
|
||||||
|
log.info("[OpenApi] 日报同步请求, projectId={}, userId={}, reportDate={}",
|
||||||
|
dto.getProjectId(), dto.getUserId(), dto.getReportDate());
|
||||||
|
String result = openApiService.syncDailyReport(dto);
|
||||||
|
return ResultUtils.success(result, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
package cn.yinlihupo.domain.dto;
|
||||||
|
|
||||||
|
import jakarta.validation.constraints.NotBlank;
|
||||||
|
import jakarta.validation.constraints.NotNull;
|
||||||
|
import jakarta.validation.constraints.Max;
|
||||||
|
import jakarta.validation.constraints.Min;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.time.LocalDate;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 日报数据同步 DTO(供外部系统调用)
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class DailyReportSyncDTO {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 项目ID(必填)
|
||||||
|
*/
|
||||||
|
@NotNull(message = "项目ID不能为空")
|
||||||
|
private Long projectId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户标识(对应 sys_user.username,必填)
|
||||||
|
*/
|
||||||
|
@NotBlank(message = "用户ID不能为空")
|
||||||
|
private String userId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 日报日期(必填)
|
||||||
|
*/
|
||||||
|
@NotNull(message = "日报日期不能为空")
|
||||||
|
private LocalDate reportDate;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 工作内容(必填)
|
||||||
|
*/
|
||||||
|
@NotBlank(message = "工作内容不能为空")
|
||||||
|
private String workContent;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 明日计划
|
||||||
|
*/
|
||||||
|
private String tomorrowPlan;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 工作强度 1-5 (1-轻松, 2-较轻, 3-适中, 4-繁忙, 5-非常繁忙)
|
||||||
|
*/
|
||||||
|
@Min(value = 1, message = "工作强度最小为1")
|
||||||
|
@Max(value = 5, message = "工作强度最大为5")
|
||||||
|
private Integer workIntensity;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否需要协助
|
||||||
|
*/
|
||||||
|
private Boolean needHelp;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 协助内容(needHelp 为 true 时填写)
|
||||||
|
*/
|
||||||
|
private String helpContent;
|
||||||
|
}
|
||||||
@@ -0,0 +1,83 @@
|
|||||||
|
package cn.yinlihupo.domain.entity;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.annotation.*;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.time.LocalDate;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 项目日报实体类
|
||||||
|
* 对应数据库表: project_daily_report
|
||||||
|
* 防重键: (project_id, report_date, submitter_username)
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@TableName("project_daily_report")
|
||||||
|
public class ProjectDailyReport {
|
||||||
|
|
||||||
|
@TableId(type = IdType.AUTO)
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 项目ID
|
||||||
|
*/
|
||||||
|
private Long projectId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 提交人用户名 (对应 sys_user.username)
|
||||||
|
*/
|
||||||
|
private String submitterUsername;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 提交人ID (冗余,方便查询)
|
||||||
|
*/
|
||||||
|
private Long submitterId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 日报日期
|
||||||
|
*/
|
||||||
|
private LocalDate reportDate;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 工作内容
|
||||||
|
*/
|
||||||
|
private String workContent;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 明日计划
|
||||||
|
*/
|
||||||
|
private String tomorrowPlan;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 工作强度 1-5 (1-轻松, 2-较轻, 3-适中, 4-繁忙, 5-非常繁忙)
|
||||||
|
*/
|
||||||
|
private Integer workIntensity;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否需要协助
|
||||||
|
*/
|
||||||
|
private Boolean needHelp;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 协助内容
|
||||||
|
*/
|
||||||
|
private String helpContent;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建时间
|
||||||
|
*/
|
||||||
|
@TableField(fill = FieldFill.INSERT)
|
||||||
|
private LocalDateTime createTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新时间
|
||||||
|
*/
|
||||||
|
@TableField(fill = FieldFill.INSERT_UPDATE)
|
||||||
|
private LocalDateTime updateTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除标记
|
||||||
|
*/
|
||||||
|
@TableLogic
|
||||||
|
private Integer deleted;
|
||||||
|
}
|
||||||
62
src/main/java/cn/yinlihupo/domain/vo/OpenProjectVO.java
Normal file
62
src/main/java/cn/yinlihupo/domain/vo/OpenProjectVO.java
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
package cn.yinlihupo.domain.vo;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.time.LocalDate;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 开放接口-项目列表 VO(对外简化字段,供外部系统使用)
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class OpenProjectVO {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 项目ID
|
||||||
|
*/
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 项目编号
|
||||||
|
*/
|
||||||
|
private String projectCode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 项目名称
|
||||||
|
*/
|
||||||
|
private String projectName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 项目类型
|
||||||
|
*/
|
||||||
|
private String projectType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 项目状态: draft-草稿, planning-规划中, ongoing-进行中, paused-暂停, completed-已完成, cancelled-已取消
|
||||||
|
*/
|
||||||
|
private String status;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 优先级: critical-关键, high-高, medium-中, low-低
|
||||||
|
*/
|
||||||
|
private String priority;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 计划开始日期
|
||||||
|
*/
|
||||||
|
private LocalDate planStartDate;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 计划结束日期
|
||||||
|
*/
|
||||||
|
private LocalDate planEndDate;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 项目进度百分比
|
||||||
|
*/
|
||||||
|
private Integer progress;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 该用户在项目中的角色: manager-项目经理, leader-负责人, member-成员, observer-观察者
|
||||||
|
*/
|
||||||
|
private String myRole;
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
package cn.yinlihupo.mapper;
|
||||||
|
|
||||||
|
import cn.yinlihupo.domain.entity.ProjectDailyReport;
|
||||||
|
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||||
|
import org.apache.ibatis.annotations.Mapper;
|
||||||
|
import org.apache.ibatis.annotations.Param;
|
||||||
|
|
||||||
|
import java.time.LocalDate;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 项目日报 Mapper 接口
|
||||||
|
*/
|
||||||
|
@Mapper
|
||||||
|
public interface ProjectDailyReportMapper extends BaseMapper<ProjectDailyReport> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 防重检查:查询同项目+同日期+同用户的日报数量
|
||||||
|
*
|
||||||
|
* @param projectId 项目ID
|
||||||
|
* @param reportDate 日报日期
|
||||||
|
* @param submitterUsername 提交人用户名
|
||||||
|
* @return 记录数量,大于0表示已存在
|
||||||
|
*/
|
||||||
|
int countByUniqueKey(@Param("projectId") Long projectId,
|
||||||
|
@Param("reportDate") LocalDate reportDate,
|
||||||
|
@Param("submitterUsername") String submitterUsername);
|
||||||
|
}
|
||||||
@@ -45,4 +45,13 @@ public interface ProjectMapper extends BaseMapper<Project> {
|
|||||||
* 查询即将超期的项目(N天内)
|
* 查询即将超期的项目(N天内)
|
||||||
*/
|
*/
|
||||||
List<Project> selectAboutToExpire(@Param("days") int days);
|
List<Project> selectAboutToExpire(@Param("days") int days);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据 sys_user.username 联查用户所在的项目列表
|
||||||
|
* (包含作为项目经理或项目成员的项目)
|
||||||
|
*
|
||||||
|
* @param username 用户名 (对应 sys_user.username)
|
||||||
|
* @return 项目列表
|
||||||
|
*/
|
||||||
|
List<Project> selectProjectsByUsername(@Param("username") String username);
|
||||||
}
|
}
|
||||||
|
|||||||
28
src/main/java/cn/yinlihupo/service/open/OpenApiService.java
Normal file
28
src/main/java/cn/yinlihupo/service/open/OpenApiService.java
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
package cn.yinlihupo.service.open;
|
||||||
|
|
||||||
|
import cn.yinlihupo.domain.dto.DailyReportSyncDTO;
|
||||||
|
import cn.yinlihupo.domain.vo.OpenProjectVO;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 开放接口服务(供外部系统调用,无需登录鉴权)
|
||||||
|
*/
|
||||||
|
public interface OpenApiService {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据用户标识 (sys_user.username) 查询用户所在的项目列表
|
||||||
|
*
|
||||||
|
* @param userId 用户标识(对应 sys_user.username)
|
||||||
|
* @return 项目列表
|
||||||
|
*/
|
||||||
|
List<OpenProjectVO> getProjectsByUserId(String userId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 同步日报数据到库(带防重设计)
|
||||||
|
*
|
||||||
|
* @param dto 日报数据
|
||||||
|
* @return 操作结果描述
|
||||||
|
*/
|
||||||
|
String syncDailyReport(DailyReportSyncDTO dto);
|
||||||
|
}
|
||||||
@@ -0,0 +1,134 @@
|
|||||||
|
package cn.yinlihupo.service.open.impl;
|
||||||
|
|
||||||
|
import cn.yinlihupo.common.exception.BusinessException;
|
||||||
|
import cn.yinlihupo.domain.dto.DailyReportSyncDTO;
|
||||||
|
import cn.yinlihupo.domain.entity.Project;
|
||||||
|
import cn.yinlihupo.domain.entity.ProjectDailyReport;
|
||||||
|
import cn.yinlihupo.domain.entity.SysUser;
|
||||||
|
import cn.yinlihupo.domain.vo.OpenProjectVO;
|
||||||
|
import cn.yinlihupo.mapper.ProjectDailyReportMapper;
|
||||||
|
import cn.yinlihupo.mapper.ProjectMapper;
|
||||||
|
import cn.yinlihupo.mapper.ProjectMemberMapper;
|
||||||
|
import cn.yinlihupo.mapper.SysUserMapper;
|
||||||
|
import cn.yinlihupo.service.open.OpenApiService;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.util.StringUtils;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 开放接口服务实现
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@Service
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class OpenApiServiceImpl implements OpenApiService {
|
||||||
|
|
||||||
|
private final SysUserMapper sysUserMapper;
|
||||||
|
private final ProjectMapper projectMapper;
|
||||||
|
private final ProjectMemberMapper projectMemberMapper;
|
||||||
|
private final ProjectDailyReportMapper projectDailyReportMapper;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据用户标识 (sys_user.username) 查询用户所在的项目列表
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public List<OpenProjectVO> getProjectsByUserId(String userId) {
|
||||||
|
if (!StringUtils.hasText(userId)) {
|
||||||
|
return new ArrayList<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 1. 先验证用户是否存在
|
||||||
|
SysUser user = sysUserMapper.selectByUsername(userId);
|
||||||
|
if (user == null) {
|
||||||
|
log.warn("[OpenApi] 用户不存在, username={}", userId);
|
||||||
|
return new ArrayList<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 联查用户所在的项目列表(通过 username 关联 sys_user -> project/project_member)
|
||||||
|
List<Project> projects = projectMapper.selectProjectsByUsername(userId);
|
||||||
|
if (projects == null || projects.isEmpty()) {
|
||||||
|
return new ArrayList<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. 转换为 OpenProjectVO 并填充用户角色
|
||||||
|
List<OpenProjectVO> result = new ArrayList<>();
|
||||||
|
for (Project project : projects) {
|
||||||
|
OpenProjectVO vo = new OpenProjectVO();
|
||||||
|
vo.setId(project.getId());
|
||||||
|
vo.setProjectCode(project.getProjectCode());
|
||||||
|
vo.setProjectName(project.getProjectName());
|
||||||
|
vo.setProjectType(project.getProjectType());
|
||||||
|
vo.setStatus(project.getStatus());
|
||||||
|
vo.setPriority(project.getPriority());
|
||||||
|
vo.setPlanStartDate(project.getPlanStartDate());
|
||||||
|
vo.setPlanEndDate(project.getPlanEndDate());
|
||||||
|
vo.setProgress(project.getProgress());
|
||||||
|
|
||||||
|
// 4. 填充用户在该项目的角色
|
||||||
|
if (project.getManagerId() != null && project.getManagerId().equals(user.getId())) {
|
||||||
|
vo.setMyRole("manager");
|
||||||
|
} else {
|
||||||
|
String role = projectMemberMapper.selectRoleByUserAndProject(project.getId(), user.getId());
|
||||||
|
vo.setMyRole(role);
|
||||||
|
}
|
||||||
|
result.add(vo);
|
||||||
|
}
|
||||||
|
|
||||||
|
log.info("[OpenApi] 查询用户项目列表成功, username={}, 项目数={}", userId, result.size());
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 同步日报数据到库(带防重设计)
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public String syncDailyReport(DailyReportSyncDTO dto) {
|
||||||
|
String username = dto.getUserId();
|
||||||
|
|
||||||
|
// 1. 校验用户是否存在
|
||||||
|
SysUser user = sysUserMapper.selectByUsername(username);
|
||||||
|
if (user == null) {
|
||||||
|
log.warn("[OpenApi] 日报同步失败,用户不存在, username={}", username);
|
||||||
|
throw new BusinessException("用户不存在: " + username);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 校验项目是否存在
|
||||||
|
Project project = projectMapper.selectById(dto.getProjectId());
|
||||||
|
if (project == null || project.getDeleted() == 1) {
|
||||||
|
log.warn("[OpenApi] 日报同步失败,项目不存在, projectId={}", dto.getProjectId());
|
||||||
|
throw new BusinessException("项目不存在: " + dto.getProjectId());
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. 防重检查:同一用户同一天同一项目只能提交一条日报
|
||||||
|
int count = projectDailyReportMapper.countByUniqueKey(
|
||||||
|
dto.getProjectId(), dto.getReportDate(), username);
|
||||||
|
if (count > 0) {
|
||||||
|
log.warn("[OpenApi] 日报重复提交, projectId={}, reportDate={}, username={}",
|
||||||
|
dto.getProjectId(), dto.getReportDate(), username);
|
||||||
|
throw new BusinessException("日报已提交,请勿重复提交(项目ID: "
|
||||||
|
+ dto.getProjectId() + ",日期: " + dto.getReportDate() + ")");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. 入库保存
|
||||||
|
ProjectDailyReport report = new ProjectDailyReport();
|
||||||
|
report.setProjectId(dto.getProjectId());
|
||||||
|
report.setSubmitterUsername(username);
|
||||||
|
report.setSubmitterId(user.getId());
|
||||||
|
report.setReportDate(dto.getReportDate());
|
||||||
|
report.setWorkContent(dto.getWorkContent());
|
||||||
|
report.setTomorrowPlan(dto.getTomorrowPlan());
|
||||||
|
report.setWorkIntensity(dto.getWorkIntensity());
|
||||||
|
report.setNeedHelp(dto.getNeedHelp());
|
||||||
|
report.setHelpContent(dto.getHelpContent());
|
||||||
|
|
||||||
|
projectDailyReportMapper.insert(report);
|
||||||
|
|
||||||
|
log.info("[OpenApi] 日报同步成功, projectId={}, reportDate={}, username={}",
|
||||||
|
dto.getProjectId(), dto.getReportDate(), username);
|
||||||
|
return "日报同步成功";
|
||||||
|
}
|
||||||
|
}
|
||||||
16
src/main/resources/mapper/ProjectDailyReportMapper.xml
Normal file
16
src/main/resources/mapper/ProjectDailyReportMapper.xml
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||||
|
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||||
|
<mapper namespace="cn.yinlihupo.mapper.ProjectDailyReportMapper">
|
||||||
|
|
||||||
|
<!-- 防重检查:查询同项目+同日期+同用户的日报数量(不含已逻辑删除的记录) -->
|
||||||
|
<select id="countByUniqueKey" resultType="int">
|
||||||
|
SELECT COUNT(*)
|
||||||
|
FROM project_daily_report
|
||||||
|
WHERE project_id = #{projectId}
|
||||||
|
AND report_date = #{reportDate}
|
||||||
|
AND submitter_username = #{submitterUsername}
|
||||||
|
AND deleted = 0
|
||||||
|
</select>
|
||||||
|
|
||||||
|
</mapper>
|
||||||
@@ -107,4 +107,18 @@
|
|||||||
ORDER BY plan_end_date ASC
|
ORDER BY plan_end_date ASC
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
|
<!-- 根据 sys_user.username 联查用户所在的项目列表 -->
|
||||||
|
<select id="selectProjectsByUsername" resultMap="BaseResultMap">
|
||||||
|
SELECT DISTINCT p.*
|
||||||
|
FROM project p
|
||||||
|
LEFT JOIN project_member pm ON p.id = pm.project_id AND pm.deleted = 0 AND pm.status = 1
|
||||||
|
INNER JOIN sys_user u ON u.username = #{username} AND u.deleted = 0 AND u.status = 1
|
||||||
|
WHERE p.deleted = 0
|
||||||
|
AND (
|
||||||
|
p.manager_id = u.id
|
||||||
|
OR pm.user_id = u.id
|
||||||
|
)
|
||||||
|
ORDER BY p.create_time DESC
|
||||||
|
</select>
|
||||||
|
|
||||||
</mapper>
|
</mapper>
|
||||||
|
|||||||
Reference in New Issue
Block a user