# SQLite 数据迁移设计方案 ## 概述 将 junboV2 项目的数据存储从 YAML/Excel 文件迁移到 SQLite 数据库,同时提供 Web 管理界面和双向同步能力。 ## 技术选型 | 组件 | 技术 | |------|------| | 后端框架 | Spring Boot | | 数据访问 | Spring Data JPA + Hibernate | | 数据库 | SQLite | | 前端框架 | Vue 3 | | UI 组件库 | Element Plus | | 使用场景 | 个人使用,无需认证 | ## 架构设计 ### 整体架构 ``` ┌─────────────────────────────────────────────────────────┐ │ Vue 3 Web 界面 │ └─────────────────────┬───────────────────────────────────┘ │ REST API ┌─────────────────────▼───────────────────────────────────┐ │ Spring Boot 服务层 │ │ AttendanceService / OrganizationService / ... │ └───┬─────────────────┼─────────────────┬─────────────────┘ │ │ │ ┌───▼───┐ ┌────▼────┐ ┌────▼────┐ │ Excel │ │ YAML │ │ SQLite │ │ DAO │ │ DAO │ │ DAO │ └───┬───┘ └────┬────┘ └────┬────┘ │ │ │ ┌───▼───┐ ┌────▼────┐ ┌────▼────┐ │ .xlsx │ │ .yml │ │ .db │ └───────┘ └─────────┘ └─────────┘ ``` ### 设计要点 - 多 DAO 并存:保留现有 Excel/YAML DAO,新增 SQLite DAO - 业务层可按需选择数据源 - 双向同步通过导入/导出功能实现 ## 数据库设计 ### 组织架构模块 ```sql -- 员工表 CREATE TABLE employee ( id INTEGER PRIMARY KEY AUTOINCREMENT, name VARCHAR(50) NOT NULL UNIQUE, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); -- 员工别名表 CREATE TABLE employee_alias ( id INTEGER PRIMARY KEY AUTOINCREMENT, employee_id INTEGER NOT NULL, alias VARCHAR(50) NOT NULL, FOREIGN KEY (employee_id) REFERENCES employee(id) ); -- 部门表 CREATE TABLE department ( id INTEGER PRIMARY KEY AUTOINCREMENT, name VARCHAR(100) NOT NULL UNIQUE ); -- 部门层级关系表 CREATE TABLE department_relation ( id INTEGER PRIMARY KEY AUTOINCREMENT, parent_id INTEGER NOT NULL, child_id INTEGER NOT NULL, effective_date DATE NOT NULL, expiry_date DATE, FOREIGN KEY (parent_id) REFERENCES department(id), FOREIGN KEY (child_id) REFERENCES department(id) ); -- 员工部门关系表 CREATE TABLE employee_department ( id INTEGER PRIMARY KEY AUTOINCREMENT, employee_id INTEGER NOT NULL, department_id INTEGER NOT NULL, effective_date DATE NOT NULL, expiry_date DATE, FOREIGN KEY (employee_id) REFERENCES employee(id), FOREIGN KEY (department_id) REFERENCES department(id) ); ``` ### 考勤模块 ```sql -- 工作日历表 CREATE TABLE work_calendar ( id INTEGER PRIMARY KEY AUTOINCREMENT, date DATE NOT NULL UNIQUE, is_workday BOOLEAN NOT NULL, is_weekend BOOLEAN NOT NULL, is_holiday BOOLEAN NOT NULL, holiday_name VARCHAR(50), remark VARCHAR(200) ); -- 签到记录表 (每次打卡一条记录) CREATE TABLE sign_record ( id INTEGER PRIMARY KEY AUTOINCREMENT, employee_id INTEGER NOT NULL, date DATE NOT NULL, time TIME NOT NULL, is_outside BOOLEAN DEFAULT FALSE, remark VARCHAR(500), FOREIGN KEY (employee_id) REFERENCES employee(id) ); CREATE INDEX idx_sign_employee_date ON sign_record(employee_id, date); -- 请假记录表 CREATE TABLE leave_record ( id INTEGER PRIMARY KEY AUTOINCREMENT, employee_id INTEGER NOT NULL, date DATE NOT NULL, start_time TIME NOT NULL, end_time TIME NOT NULL, leave_type VARCHAR(20) NOT NULL, reason VARCHAR(500), approval_status VARCHAR(20), approval_result VARCHAR(20), remark VARCHAR(500), FOREIGN KEY (employee_id) REFERENCES employee(id) ); ``` ### 业务模块 ```sql -- Sprint 迭代表 CREATE TABLE sprint ( id INTEGER PRIMARY KEY AUTOINCREMENT, name VARCHAR(100) NOT NULL, start_date DATE NOT NULL, end_date DATE NOT NULL, remark VARCHAR(500) ); -- Story 用户故事表 CREATE TABLE story ( id INTEGER PRIMARY KEY AUTOINCREMENT, sprint_id INTEGER NOT NULL, title VARCHAR(200) NOT NULL, story_point DECIMAL(5,1), status VARCHAR(20), remark VARCHAR(500), FOREIGN KEY (sprint_id) REFERENCES sprint(id) ); -- Story 角色关联表 CREATE TABLE story_member ( id INTEGER PRIMARY KEY AUTOINCREMENT, story_id INTEGER NOT NULL, employee_id INTEGER NOT NULL, role VARCHAR(20) NOT NULL, -- PO/DEV/TEST/ACCEPT FOREIGN KEY (story_id) REFERENCES story(id), FOREIGN KEY (employee_id) REFERENCES employee(id) ); -- KPA 记录主表 CREATE TABLE kpa_record ( id INTEGER PRIMARY KEY AUTOINCREMENT, year INTEGER NOT NULL, month INTEGER NOT NULL, title VARCHAR(200), description TEXT, remark VARCHAR(500), UNIQUE(year, month, title) ); -- KPA 员工得分表 CREATE TABLE kpa_employee_score ( id INTEGER PRIMARY KEY AUTOINCREMENT, kpa_record_id INTEGER NOT NULL, employee_id INTEGER NOT NULL, score DECIMAL(5,2), level VARCHAR(10), remark VARCHAR(500), FOREIGN KEY (kpa_record_id) REFERENCES kpa_record(id), FOREIGN KEY (employee_id) REFERENCES employee(id) ); -- 激励记录表 CREATE TABLE incentive_record ( id INTEGER PRIMARY KEY AUTOINCREMENT, employee_id INTEGER NOT NULL, date DATE NOT NULL, type VARCHAR(50), amount DECIMAL(10,2), reason VARCHAR(500), FOREIGN KEY (employee_id) REFERENCES employee(id) ); ``` ## JPA Entity 设计 ### 组织架构模块 ```java @Entity @Table(name = "employee") @Getter @Setter public class Employee { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @Column(nullable = false, unique = true) private String name; @OneToMany(mappedBy = "employee", cascade = CascadeType.ALL) private List aliases = new ArrayList<>(); @OneToMany(mappedBy = "employee") private List departmentRelations = new ArrayList<>(); private LocalDateTime createdAt; } @Entity @Table(name = "employee_alias") @Getter @Setter public class EmployeeAlias { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @ManyToOne @JoinColumn(name = "employee_id", nullable = false) private Employee employee; @Column(nullable = false) private String alias; } @Entity @Table(name = "department") @Getter @Setter public class Department { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @Column(nullable = false, unique = true) private String name; } @Entity @Table(name = "employee_department") @Getter @Setter public class EmployeeDepartment { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @ManyToOne @JoinColumn(name = "employee_id", nullable = false) private Employee employee; @ManyToOne @JoinColumn(name = "department_id", nullable = false) private Department department; @Column(nullable = false) private LocalDate effectiveDate; private LocalDate expiryDate; } ``` ### 考勤模块 ```java @Entity @Table(name = "work_calendar") @Getter @Setter public class WorkCalendar { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @Column(nullable = false, unique = true) private LocalDate date; private boolean workday; private boolean weekend; private boolean holiday; private String holidayName; private String remark; } @Entity @Table(name = "sign_record") @Getter @Setter public class SignRecord { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @ManyToOne @JoinColumn(name = "employee_id", nullable = false) private Employee employee; @Column(nullable = false) private LocalDate date; @Column(nullable = false) private LocalTime time; private boolean outside; private String remark; } @Entity @Table(name = "leave_record") @Getter @Setter public class LeaveRecord { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @ManyToOne @JoinColumn(name = "employee_id", nullable = false) private Employee employee; @Column(nullable = false) private LocalDate date; @Column(nullable = false) private LocalTime startTime; @Column(nullable = false) private LocalTime endTime; @Enumerated(EnumType.STRING) @Column(nullable = false) private LeaveType leaveType; private String reason; private String approvalStatus; private String approvalResult; private String remark; } public enum LeaveType { SICK, PERSONAL, VACATION, CHANGE_REST, OUT, MATERNITY, FUNERAL, MARRIAGE } ``` ### 业务模块 ```java @Entity @Table(name = "sprint") @Getter @Setter public class Sprint { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @Column(nullable = false) private String name; @Column(nullable = false) private LocalDate startDate; @Column(nullable = false) private LocalDate endDate; private String remark; @OneToMany(mappedBy = "sprint") private List stories = new ArrayList<>(); } @Entity @Table(name = "story") @Getter @Setter public class Story { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @ManyToOne @JoinColumn(name = "sprint_id", nullable = false) private Sprint sprint; @Column(nullable = false) private String title; private BigDecimal storyPoint; private String status; private String remark; @OneToMany(mappedBy = "story", cascade = CascadeType.ALL, orphanRemoval = true) private List members = new ArrayList<>(); } @Entity @Table(name = "story_member") @Getter @Setter public class StoryMember { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @ManyToOne @JoinColumn(name = "story_id", nullable = false) private Story story; @ManyToOne @JoinColumn(name = "employee_id", nullable = false) private Employee employee; @Enumerated(EnumType.STRING) @Column(nullable = false) private StoryRole role; } public enum StoryRole { PO, DEV, TEST, ACCEPT } @Entity @Table(name = "kpa_record") @Getter @Setter public class KpaRecord { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @Column(nullable = false) private Integer year; @Column(nullable = false) private Integer month; private String title; @Column(columnDefinition = "TEXT") private String description; private String remark; @OneToMany(mappedBy = "kpaRecord", cascade = CascadeType.ALL, orphanRemoval = true) private List employeeScores = new ArrayList<>(); } @Entity @Table(name = "kpa_employee_score") @Getter @Setter public class KpaEmployeeScore { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @ManyToOne @JoinColumn(name = "kpa_record_id", nullable = false) private KpaRecord kpaRecord; @ManyToOne @JoinColumn(name = "employee_id", nullable = false) private Employee employee; private BigDecimal score; private String level; private String remark; } @Entity @Table(name = "incentive_record") @Getter @Setter public class IncentiveRecord { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @ManyToOne @JoinColumn(name = "employee_id", nullable = false) private Employee employee; @Column(nullable = false) private LocalDate date; private String type; private BigDecimal amount; private String reason; } ``` ## Service 层设计 ### 多数据源架构 ```java public enum DataSourceType { SQLITE, EXCEL, YAML } @Service @RequiredArgsConstructor public class SignServiceImpl implements SignService { private final SignRecordRepository signRecordRepository; // SQLite private final SignExcelDao signExcelDao; // Excel private final EmployeeRepository employeeRepository; @Value("${app.datasource.sign:SQLITE}") private DataSourceType defaultSource; public List getSignRecords(Employee employee, LocalDate start, LocalDate end, DataSourceType source) { return switch (source) { case SQLITE -> loadFromSqlite(employee, start, end); case EXCEL -> loadFromExcel(employee, start, end); default -> throw new IllegalArgumentException("Unsupported source: " + source); }; } @Transactional public void importFromExcel(String filePath) { List excelData = signExcelDao.read2Bo(); excelData.forEach(this::saveSignRecord); } public void exportToExcel(String filePath, LocalDate start, LocalDate end) { List records = signRecordRepository .findByDateBetweenOrderByEmployeeAscDateAscTimeAsc(start, end); // 调用 Excel 写入工具 } } ``` ## REST API 设计 | 方法 | 路径 | 说明 | |------|------|------| | GET | /api/employees | 员工列表 | | POST | /api/employees | 新增员工 | | PUT | /api/employees/{id} | 更新员工 | | DELETE | /api/employees/{id} | 删除员工 | | GET | /api/departments | 部门列表 | | GET | /api/sign | 签到记录列表 | | POST | /api/sign | 新增签到记录 | | GET | /api/leaves | 请假记录列表 | | POST | /api/leaves | 新增请假记录 | | GET | /api/calendar | 工作日历 | | GET | /api/sprints | Sprint 列表 | | GET | /api/stories | Story 列表 | | GET | /api/kpa | KPA 记录列表 | | GET | /api/incentives | 激励记录列表 | | POST | /api/sync/import/yaml | 从 YAML 导入 | | POST | /api/sync/import/excel | 从 Excel 导入 | | POST | /api/sync/export/yaml | 导出到 YAML | | POST | /api/sync/export/excel | 导出到 Excel | ## 前端设计 ### 项目结构 ``` junbo-web/ ├── src/ │ ├── api/ # API 调用 │ │ ├── employee.js │ │ ├── sign.js │ │ ├── leave.js │ │ └── sync.js │ ├── views/ # 页面 │ │ ├── employee/ │ │ ├── attendance/ │ │ ├── scrum/ │ │ ├── kpa/ │ │ └── sync/ │ ├── components/ # 公共组件 │ ├── router/ │ └── App.vue └── package.json ``` ### 导航菜单 | 菜单 | 页面 | |------|------| | 组织架构 | 员工管理、部门管理 | | 考勤管理 | 签到记录、请假记录、工作日历 | | Scrum | Sprint、Story | | 绩效 | KPA 记录、激励记录 | | 数据同步 | 导入导出面板 | ## 实现阶段 | 阶段 | 内容 | 优先级 | |------|------|--------| | 1 | 基础设施:Spring Boot 项目、SQLite 配置、JPA Entity | 高 | | 2 | 组织架构模块:员工、部门 CRUD + YAML 导入 | 高 | | 3 | 考勤模块:签到、请假 CRUD + Excel 导入 | 高 | | 4 | Vue 前端:基础框架 + 组织架构页面 | 中 | | 5 | 其他业务模块:Sprint、Story、KPA、Incentive | 中 | | 6 | 数据同步:完整的导入导出功能 | 低 |